java8之中提供的几种默认的创建线程池的方式
线程池的优点:较少资源浪费,提高线程复用
创建线程池的顶层类 Executors.class
首先打开该类,查看该类实现的方法
总结起来几种实现为:
//单线程池
ExecutorService executor1 = Executors.newSingleThreadExecutor();
//可以定时执行 或者 定期执行的单线程池
ExecutorService executor5 = Executors.newSingleThreadScheduledExecutor();
//定长线程池
ExecutorService executor2 = Executors.newFixedThreadPool(5);
//缓存线程池
ExecutorService executor3 = Executors.newCachedThreadPool();
//定时执行 或者 定期执行的 定长线程池
ExecutorService executor4 = Executors.newScheduledThreadPool(5);
//任务窃取线程池
ExecutorService executor6 = Executors.newWorkStealingPool();
1、单线程池:newSingleThreadExecutor()
单线程池内部调用是如下:可以看到是一个核心线程数和最大线程数都是1的ThreadPoolExecutor,队列采用的无界队列,如果因为在关闭前的执行任务期间出现失败而终止了次单个线程,如果需要的话,一个新的线程将替代它继续执行后续的任务。可保证顺序的执行任务,并且在指定的时间内不会有多个线程是活动的。与等效的newFixedThreadPool(1)不同,可保证无需重新配置即可使用其他的线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
如下测试代码进行测试:
public static void main(String[] args) throws InterruptedException {
int threads = 10;
//单线程池,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务
ExecutorService executor1 = Executors.newSingleThreadExecutor();
for (int i = 0; i < threads; i++) {
int index = i;
Thread.sleep(1000);
executor1.execute(() ->{
System.out.println(Thread.currentThread().getName() + " " + index);
});
}
}
可以看到执行结果为:
2、newSingleThreadScheduledExecutor()
单线程执行器,该执行器可以安排命令在给定延迟后运行,或定期执行
首先看一个定期执行的任务,类似一个达到一个job的效果,主要区别是
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
executorService.scheduleAtFixedRate(new Runnable() {
int i = 0;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行" + i++);
}
}, 3, 2, TimeUnit.SECONDS);
}
执行结果如下
延时任务如下,区别在于
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
public static void main(String[] args){
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
long start = System.currentTimeMillis();
executorService.schedule(() -> {
System.out.println(Thread.currentThread().getName() + "时间:" + (System.currentTimeMillis() - start) + " 执行 " );
}, 500, TimeUnit.MILLISECONDS);
}
执行结果为
所以 newSingleThreadScheduledExecutor 到底是产生延时任务还是周期任务主要就在于提交任务的时候调用的方法。
3、定长线程池 newFixedThreadPool
固定线程数量的线程池,该线程池初始化的核心代码是,下面所示
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到,该线程池内部是对newFixedThreadPool的参数进行相应的初始化得到的,核心线程数和最大线程数一样都是传进来的参数值,也就是初始化newFixedThreadPool传进来的。队列使用的无界队列
public static void main(String[] args) throws InterruptedException {
// 线程数
int threads = 10;
// 用于计数线程是否执行完成
CountDownLatch countDownLatch = new CountDownLatch(threads);
System.out.println("---- start ----");
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0 ; i < threads; i++) {
executorService.submit(() ->{
System.out.println(Thread.currentThread().getName() + "执行");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("---- end ----");
executorService.shutdown();
}
执行结果如下所示:
可以看到最多创建了3个线程。
4、缓存线程池 newCachedThreadPool
带缓存的线程池,该线程池初始化的核心代码是
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到,该线程池内部是对newFixedThreadPool的参数进行相应的初始化得到的,核心线程数为0,最大线程数为Integer.MAX_VALUE,失效时间为60秒的newFixedThreadPool初始化的。
也就是说,每次基本上有新的任务进来都会产生新的线程去执行任务。当没有任务执行之后,60秒以后线程就会被全部回收。具体使用方法和定长线程池newFixedThreadPool基本一样,测试代码如下:
public static void main(String[] args) throws InterruptedException {
// 线程数
int threads = 10;
// 用于计数线程是否执行完成
CountDownLatch countDownLatch = new CountDownLatch(threads);
System.out.println("---- start ----");
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0 ; i < 10; i++) {
executorService.submit(() ->{
System.out.println(Thread.currentThread().getName() + "执行");
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("---- end ----");
executorService.shutdown();
}
运行结果如下:
5、 newScheduledThreadPool
定时运行 或者定期执行的 定长线程池。用法和newSingleThreadScheduledExecutor类似。
其初始化核心代码如下:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
其中ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,并实现了ScheduledExecutorService接口(该接口又是ExecutorService接口的子接口,相当于对ExecutorService进行了扩展),实现了其中的方法,达到定时,或者定期执行的目的
其中该接口的方法如下
类之间关系如下
具体用法和 定时执行 或者 定期执行的单线程池newSingleThreadScheduledExecutor类似
定期执行的代码片段如下:
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
executorService.scheduleAtFixedRate(new Runnable() {
int i = 0;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行" + i++);
}
}, 3, 2, TimeUnit.SECONDS);
}
执行结果如下:
6、newWorkStealingPool
任务窃取线程池。初始化的核心代码如下
/**
* Creates a work-stealing thread pool using all
* {@link Runtime#availableProcessors available processors}
* as its target parallelism level.
* @return the newly created thread pool
* @see #newWorkStealingPool(int)
* @since 1.8
*/
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
可以看到这个初始化线程的方式是从jdk1.8以后 之后才有的,这个线程初始化的核心是初始化一个ForkJoinPool,线程数使用Runtime.getRuntime().availableProcessors()获取到的当前服务器的cpu核心线程数。ForkJoinPool,可以充分利用多核cpu的优势,把一个任务fork成多个“小任务”分发到不同的cpu核心上执行,执行完后再把结果join到一起返回。即所谓的Fork - join。
工作窃取,指的是闲置的线程去处理本不属于它的任务。每个处理器核,都有一个队列存储着需要完成的任务。对于多核的机器来说,当一个核对应的任务处理完毕后,就可以去帮助其他的核处理任务。
示例代码如下:
public static void main(String[] args) throws InterruptedException {
// 线程数
int threads = 10;
// 用于计数线程是否执行完成
CountDownLatch countDownLatch = new CountDownLatch(threads);
System.out.println("---- start ----");
ExecutorService executorService = Executors.newWorkStealingPool();
for (int i = 0; i < threads; i++) {
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
});
}
countDownLatch.await();
System.out.println("---- end ----");
}
其运行结果为:
总结: 常见的几种线程池除了最后一种java8新增的窃取线程池,剩下的内部都是初始化的ThreadPoolExecutor,对其不同的参数进行不同的赋值达到的一种实现。下一篇,着重写一下ThreadPoolExecutor初始化的7个参数的意思,和任务提交时候,线程创建的流程。