java多线程(主要针对线程池讲解)
1、创建线程的4种方式
第一种: 通过继承Thread类创建线程。(占用继承位,不推荐)
第二种: 通过实现Runnable接口创建线程。 (能更好的做到并发完成同一个任务, 因为访问的是同一个target, 实现了共享数据)
以上两种重写run()方法没有返回值, 所以这两种方式不能获取异步执行的结果。
第三种: 使用Callable接口 和 FutureTask类创建线程。(线程处于并发状态, 默认异步执行)
第四种: 通过线程池创建线程。(在高并发场景中, 频繁创建线程是非常消耗资源的, 通过线程池创建线程可以对已经创建好的线程进行复用)
2、创建线程池的7种方式(6种Executors,1种ThreadPoolExecutor)
1、Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2、Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
3、Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
4、Executors.newScheduledThreadPool:创建一个可以执行延迟任务的线程池;
5、Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
6、Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
7、ThreadPoolExecutor:最原始的创建线程池的方式,它包含了 7 个参数可供设置,后面会详细讲。
3、原始线程池的7大参数
(1)corePoolSize:线程池中的常驻核心线程数。
CPU密集型:
核心线程数 = CPU核数 + 1
IO密集型:
核心线程数 = CPU核数 * 2
核心线程数 = CPU核数 / (1-阻塞系数)
(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1。
maximumPoolSize > corePoolSize + workQueue
(3)keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。
keepAliveTime > 线程中任务的执行时间
(4)unit:keepAliveTime的单位。
TimeUnit.SECOND,
TimeUnit.MINUTES,
TimeUnit.HOURS,
TimeUnit.DAYS,
TimeUnit.MICROSECONDS,
TimeUnit.MILLISECONDS,
TimeUnit.NANOSECONDS
(5)workQueue:任务队列,被提交但尚未被执行的任务。
1、直接提交队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操 作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。
2、有界任务队列:使用ArrayBlockingQueue有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务 加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于 maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将 一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限
3、无界任务队列:使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下 maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入, 则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源 耗尽的问题。
4、优先任务队列:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是 只有一个。通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过 corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。
(6)threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可。
1、 Executors.defaultThreadFactory()
返回用于创建新线程的默认线程工厂。此工厂在同一线程组中创建执行器使用的所有新线程。
2、 Executors.privilegedThreadFactory()
返回一个线程工厂,用于创建与当前线程具有相同权限的新线程。
(7)handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时如何来拒绝请求执行的runnable的策略。
1、AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发 量的时候,能够及时的通过异常发现。
2、CallerRunsPolicy:由调用线程处理该任务。
使用此策略,可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。例如,某些视频网站统计视频的播放量就是采用的这种拒绝策略。
3、DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
此拒绝策略,是一种喜新厌旧的拒绝策略。是否要采用此种拒绝策略,还得根据实际业务是否允许丢弃老任务来认真衡量。
4、DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
此拒绝策略由调用线程(提交任务的线程)直接执行被丢弃的任务的。
4、线程池工作流程
1、有任务的时候会先去使用核心的线程数corePoolSize去执行
2、当线程满了(有任务但是线程被使用完)不会立即扩容,而是放到阻塞队列中,当阻塞队列满了之后才会继续创建线程。
3、如果队列满了,会继续尝试创建线程,直至线程数达到最大线程数则会执行拒绝策略。
4、当线程数大于核心线程数事,超过KeepAliveTime(闲置时间),线程会被回收,最终会保持corePoolSize个线程。
5、代码演示
1、不需要返回值(execute)
public class VideoMonitorServer {
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 30, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(16), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
/**
* 日志
*/
private static final Logger LOG = getLogger(VideoMonitorServer.class);
public List<Map> getRtspUriV2(String uri, String json) {
List<Map> list = new ArrayList<>();
JSONArray cameraInfos = JSONUtil.parseObj(json).getJSONArray("cameraInfos");
CountDownLatch countDownLatch = new CountDownLatch(cameraInfos.size());
for (Object o : cameraInfos) {
//在for循环中开启线程去执行
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//这里面不能出现成员变量,会被加锁,否则下个线程任务开启时获取成员变量可能还存在于上个线程中
..............
}
});
list.add(cameraInfo);
}
countDownLatch.countDown(); //挂起执行完的线程,等待所有线程执行完毕返回。
return list;
}
}
2、需要返回值(sumbit)
public class VideoMonitorServer {
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 30, 10, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(16), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
/**
* 日志
*/
private static final Logger LOG = getLogger(VideoMonitorServer.class);
public List<Map> getRtspUriV2(String uri, String json) {
List<Map> list = new ArrayList<>();
JSONArray cameraInfos = JSONUtil.parseObj(json).getJSONArray("cameraInfos");
CountDownLatch countDownLatch = new CountDownLatch(cameraInfos.size());
for (Object o : cameraInfos) {
//在for循环中开启线程去执行
JSONObject o1 = (JSONObject) o;
JSONObject cameraInfo = o1.getJSONObject("cameraInfo");
Future<JSONObject> submit = threadPoolExecutor.submit(
new Callable<JSONObject>() {
@Override
public JSONObject call() throws Exception {
//这里面不能出现成员变量,会被加锁,否则下个线程任务开启时获取成员变量可能还存在于上个线程中
...........
return cameraInfo;
}
}
);
try {
list.add(submit.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
countDownLatch.countDown(); //挂起执行完的线程,等待所有线程执行完毕返回。
}
return list;
}
}