Java高并发编程中Executors静态工厂的使用及详细介绍-刘宇
作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。
一、Executors静态工厂的作用
我们不可能通过创建ThreadPoolExecutor的实例来配置线程池,而Java中为我们提供了更加便捷的方法,就是Executors静态工厂。严格意义上讲Executors并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。下面我们就逐个讲解Executors中创建不同线程池的方法。
二、newCachedThreadPool
1、newCachedThreadPool的特点
创建一个可缓存线程池,线程池初始的线程数为0,使用SynchronousQueue队列存储任务,这个队列只能存储一个任务,该线程池的最大线程数为Integer的最大值。因为队列中只能存储一个任务,所以当队列处于饱和状态时,该线程池就会创建新的线程。空闲的线程会在60秒后关闭,因为线程的coreThread为0,当空闲线程销毁为0的时候该线程池就会关闭。大概是如下几个特点:
- 初始化线程大小为0
- 使用的是SynchronousQueue队列,只能存储一个任务
- 最大线程数为Integer的最大值
- 60秒后将空闲线程关闭后,当线程池内线程为0时会自动关闭线程池
2、适用场景
- 只适合短暂的任务,如果是非常耗时的任务不建议使用,因为每次提交一个任务都会开启一个线程
- 用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。
3、演示案例
package com.brycen.part3.threadpool;
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class ExecutorsExample {
public static void main(String[] args) throws InterruptedException {
useCachedThreadPool();
}
private static void useCachedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
//查看线程池中的线程数
System.out.println("poolSize: "+((ThreadPoolExecutor)executorService).getPoolSize() );
IntStream.rangeClosed(1,10).forEach(i->executorService.execute(()->{
try {
//休眠10秒
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName()+" ["+i+"]");
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
//休眠100毫秒,确保任务全部提交进去
TimeUnit.MILLISECONDS.sleep(100);
//查看线程池中的线程数
System.out.println("poolSize: "+((ThreadPoolExecutor)executorService).getPoolSize() );
}
}
运行结果:
- 60秒后当线程减为0时,线程池关闭,程序关闭。
poolSize: 0
poolSize: 10
pool-1-thread-1 [1]
pool-1-thread-7 [7]
pool-1-thread-4 [4]
pool-1-thread-6 [6]
pool-1-thread-5 [5]
pool-1-thread-3 [3]
pool-1-thread-2 [2]
pool-1-thread-8 [8]
pool-1-thread-9 [9]
pool-1-thread-10 [10]
Process finished with exit code 0
三、newFixedThreadPool
1、newFixedThreadPool的特点
该线程池的线程数在创建时就被固定了,不可以扩大。线程池中的每个线程都是处于活动状态,该线程池会一直存在,直到调用shutdown。该线程池试用的是LinkedBlockingQueue队列,最大任务数为Integer的最大值。该线程池的空闲时间为0,因为不可以扩大线程数,所以也就不存在回收线程。大概是如下几个特点:
- 初始化时该线程池中的线程数就定了,是固定的,不可以扩大。
- 使用的是LinkedBlockingQueue队列,最大数为Integer最大值
- 该线程池的空闲线程空闲时间为0,因为不可以扩大线程数,所以也就不存在回收线程
- 当线程处于空闲,且任务队列为空时,线程池也不会关闭
2、适用场景
主要适用于固定大小的线程池,因为它是无界的阻塞队列,那么线程池中的线程不会扩大,适用与可以预测线程数的场景中,或者服务器的负载很高,需要对线程数量进行严格控制的场景中。
3、演示案例
package com.brycen.part3.threadpool;
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class ExecutorsExample {
public static void main(String[] args) throws InterruptedException {
useFixedThreadPool();
}
private static void useFixedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
//查看线程池中的线程数,默认情况下,只有当线程池接受到任务时才开始创建线程,所以这边输出0
System.out.println("poolSize: "+((ThreadPoolExecutor)executorService).getPoolSize() );
IntStream.rangeClosed(1,10).forEach(i->executorService.execute(()->{
try {
//休眠10秒
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName()+" ["+i+"]");
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
//休眠100毫秒,确保任务全部提交进去
TimeUnit.MILLISECONDS.sleep(100);
//查看线程池中的线程数
System.out.println("poolSize: "+((ThreadPoolExecutor)executorService).getPoolSize() );
}
}
运行结果:
- 只能有5个线程执行任务,后续的任务只能等待前面的任务执行完,该线程池不会自动关闭。
poolSize: 0
poolSize: 5
pool-1-thread-5 [5]
pool-1-thread-2 [2]
pool-1-thread-3 [3]
pool-1-thread-4 [4]
pool-1-thread-1 [1]
pool-1-thread-2 [7]
pool-1-thread-4 [9]
pool-1-thread-3 [8]
pool-1-thread-5 [6]
pool-1-thread-1 [10]
四、newSingleThreadExecutor
1、newSingleThreadExecutor的特点
该线程池只会有一个线程处于活动状态,所以任务只能一个一个被执行。其实内部还是使用的newFixedThreadPool,只不过现在了只有一个线程,与newFixedThreadPool(1)不同是不能重新配置加入线程,因为它返回的是一个代理的包装过的ExecutorService。该线程池能够保证任务的执行顺序,先提交的先执行。如果在执行期间线程出现异常,那么会创建一个新的线程替换。SingleThreadExecutor与单独new出来的线程区别在于new出来的Thread在任务结束之后也就会销毁,而且也不可以submit提交任务到队列。总结有如下几点:
- 其实内部还是使用的FixedThreadPool,只不过限制了只有1个线程而已
- 该线程池里的任务只能被一个一个执行
- newSingleThreadExecutor返回的是一个经过代理的ExecutorService,不能转换为ThreadPoolExecutor,这也就意味着它只有一些ExecutorService的基本方法
- 能够保证任务的执行顺序
- 当线程出现异常时会重新创建一个线程替换
- SingleThreadExecutor与单独new出来的Thread区别在于,单独new出来的Thread任务结束之后线程也就会随着结束,而且不可以submit提交任务到队列
2、适用场景
适用于需要保证任务被按顺序执行,并且在任何时候都不会出现多个线程的情况。
3、演示案例
package com.brycen.part3.threadpool;
import java.util.concurrent.*;
import java.util.stream.IntStream;
public class ExecutorsExample {
public static void main(String[] args) throws InterruptedException {
useSingleThreadPool();
}
private static void useSingleThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
//因为不能转换为ThreadPoolExecutor,所以也就没有getPoolSize等一些方法
//System.out.println("poolSize: "+((ThreadPoolExecutor)executorService).getPoolSize() );
IntStream.rangeClosed(1,10).forEach(i->executorService.execute(()->{
try {
//休眠10秒
TimeUnit.SECONDS.sleep(10);
System.out.println(Thread.currentThread().getName()+" ["+i+"]");
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
}
}
运行结果:
- 按顺序一个一个执行,并且在执行所有任务后该线程池不会自动关闭。
pool-1-thread-1 [1]
pool-1-thread-1 [2]
pool-1-thread-1 [3]
pool-1-thread-1 [4]
pool-1-thread-1 [5]
pool-1-thread-1 [6]
pool-1-thread-1 [7]
pool-1-thread-1 [8]
pool-1-thread-1 [9]
pool-1-thread-1 [10]
五、newWorkStealingPool
1、newWorkStealingPool的特点
- 该方法会根据你的CPU核数来创建线程个数,也可指定线程数
- newWorkStealingPool其内部使用的是ForkJoinPool,在任务全部执行完之后该线程池会自动关闭
2、适用场景
创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行
3、演示案例
package com.brycen.part3.threadpool;
import java.util.List;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class ExecutorsExample2 {
public static void main(String[] args) throws InterruptedException {
//我的CPU核数是4,所以创建了4个线程
ExecutorService executorService = Executors.newWorkStealingPool();
//生成10个callable任务
List<Callable<String>> callableList = IntStream.rangeClosed(1, 10).boxed().map(i -> (Callable<String>) () -> {
System.out.println("Thread " + Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(2);
return "Task" + i;
}).collect(Collectors.toList());
//执行list集合中所有的callable并进行阻塞
List<Future<String>> futureList = executorService.invokeAll(callableList);
//获取callable执行完的返回值
for (Future<String> future:futureList) {
try {
//进行阻塞
String result = future.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
运行结果:
- 执行完成后会自动关闭线程池
Thread ForkJoinPool-1-worker-1
Thread ForkJoinPool-1-worker-2
Thread ForkJoinPool-1-worker-3
Thread ForkJoinPool-1-worker-0
Thread ForkJoinPool-1-worker-1
Thread ForkJoinPool-1-worker-0
Thread ForkJoinPool-1-worker-2
Thread ForkJoinPool-1-worker-3
Thread ForkJoinPool-1-worker-3
Thread ForkJoinPool-1-worker-2
Task1
Task2
Task3
Task4
Task5
Task6
Task7
Task8
Task9
Task10
Process finished with exit code 0