线程池的优势:
- 通过复用已有的线程,降低线程创建的销毁的系统开销
- 提高响应速度,复用已有的线程避免了创建线程的开销
- 方便线程数量的管控,如果创建的线程过多,咋可能导致系统化新能的下降或者oom的发生。、
- 线程池提供了定时等功能,并且方便创建
我们可以使用new ThreadPoolExecutor()来创建一个线程池
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 这个参数意思是核心线程的数量,他们一直存在于线程池中,即使处于闲置状态也不会被销毁,除非设置了allowCoreThreadTimeOut这个参数,则在闲置状态超过了这个参数keepAliveTime 则被销毁
- maximumPoolSize 线程池中的最大线程数量,如果活动的线程数超过这个值的话,后续的线程就会被阻塞。 最大数量指的是核心线程数量和非核心线程数量
- keepAliveTime 非核心线程闲置存在的时长,意思当非核心线程闲置时间超过这个值就会被回收
- TimeUnit 是keepAliveTime的时间单位,是个枚举类型,包含TimeUnit.MILLISECONDS 毫秒,SECONDS秒,天等等
-
workQueue 阻塞队列。 所有提交的Runnable线程都存放到该队列中,常用的阻塞队列有:
- threadFactory 线程 工厂,为线程池提供线程的创建,一般使用默认的即可Executors.defaultThreadFactory()
- handler 当任务队列已满,并且线程池达到了最大线程数的时候规定了接下来的线程该如何处理。
ThreadPoolExecutor提供了多钟构造方法,我们可以使用他默认的一些方法来创建,比如:
ThreadPoolExecutor executor = new
ThreadPoolExecutor(
10, 10, 1000, TimeUnit.SECONDS,
new LinkedBlockingDeque<>());
ThreadPoolExecutor 提供了execute和submit两个方法提交线程任务,比如:
executor.submit(()->{});
executor.execute(()->{});
但是submit提供了一分返回值,所以如果们用Callable的话,那么我们就可以获得一个返回值,如果不知道Callable请看我的另外一篇文章 《Java第三种线程创建方法》
Future<String> submit = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "";
}
});
submit.get();
如果使用Lambda更简单:
Future<String> submit = executor.submit(() -> "")
submit.get(); 可以得到call中返回的值。
其实系统为我们提供了集中线程池的创建,所以不用直接使用ThreadPoolExecutor这个类来创建线程池:
newFixedThreadPool
Executors.newFixedThreadPool(2);
//源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newFixedThreadPool,固定线程数量的线程池,核心线程池数和非核心线程数量相等并且由自己定义,不存在超时机制,任务队列理论是上可以无限大
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
可以看到 核心线程数为0,而线程池的最大数量可以认为无限大,超时时间60秒,并且是不缓存线程的SynchronousQueue队列,所以可以来多少线程就去执行多少线程,没有任务了60秒之后就全部销毁
ScheduledExecutorService
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
定时执行线程任务的线程池:核心线程数由自己定义,最大线程数量为无限大,超时间为0秒,所以非核心线程闲置立刻被销毁,用到了延迟队列,可以设置线程延迟多少时间执行,或者多长时间重复一次,比如:
scheduledExecutorService.schedule(() -> {}, 10, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(()->{}, 20, 10, TimeUnit.SECONDS);
第一表示10秒之后执行线程,第二个表示20秒之后每10秒执行一次线程
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看到核心线程数和最大线程数都是1,其实就是单线程了,所以也就不用考虑并发啊什么的了。
线程池的执行过程
线程池执行的过程也挺有意思:
- 提交一个线程,如果核心线程数量没有满的话,就启动一个线程来执行。
- 如果核心线程已满,那么就插入到阻塞队列里面去
- 如果阻塞队列已经满的话,就启动一个非核心线程
- 如果非核心线程也满了,那就调用handler,报异常或者给替换掉了
原本我以为,如果核心线程已满,就会立即启动非核心线程来执行线程,实际上是加到阻塞队列里面,只有阻塞队列满了之后,才会启动非核心线程。可以简单的举个例子:
ThreadPoolExecutor service = new ThreadPoolExecutor(2, 10,
1000, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
try {
Thread.sleep(2000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.execute(() -> {
System.out.println(3);
});
上面定义了一个2个核心线程数量和2个非核心线程数量的线程池,然后提交3个线程,前两个沉睡2秒,结果总是:1 3 2
所以是等到核心线程执行结束又执行的第3个线程。
那么对于ScheduledExecutorService 这个线程池如果也是符合这个理论的话,岂不是设置的定时可能永远不会执行到了么? 我们做个试验:
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()->{
try {
Thread.sleep(3000);
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 0, TimeUnit.MILLISECONDS );
service.schedule(()-> System.out.println(3), 1000, TimeUnit.MILLISECONDS );
但是结果却始终是 3 1 2. 所以对于这个定时的线程池来说这个理论并不符合
最后当应用退出,别忘了关系线程池:
service.shutdown();
service.shutdownNow();
service.isShutdown();
其中isShutdown表示线程池关闭是否完成。
shutdown 表示中断所有没有执行的线程任务
shutdownNow表示中断执行所有任务线程,包括正在执行的线程,他会有一个返回值,返回的是那些没有执行的线程列表