一、概述
- 通过java.util.concurrent.ExecutorService接口对象来执行任务,该接口对象通过工具类java.util.concurrent.Executors的静态方法来创建。
- Executors此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
- ExecutorService提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以关闭 ExecutorService,这将导致其停止接受新任务。关闭后,执行程序将最后终止,这时没有任务在执行,也没有任务在等待执行,并且无法提交新任务。
Java通过Executors创建线程池,目前有
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newWorkStealingPool(jdk1.8 新增):创建指定并行数的线程池,使用多重队列减少竞争,不能保证线程执行的顺序。默认并行数为最大CPU内核数
注意:线程池只是为了控制应用中处理某项业务中防止高并发问题带来的线程不安全的发生的概率。线程不可以重用,每次新创建一个线程用来处理业务。可以通过线程池指定处理这项业务最大的同步线程数。
执行任务流程
- 创建ExecutorService
通过工具类java.util.concurrent.Executors的静态方法来创建。 - 将任务添加到线程去执行
当将一个任务添加到线程池中的时候,线程池会为每个任务创建一个线程,该线程会在之后的某个时刻自动执行。
二、几种线程池详解
线程示例
public class ExecutorsThread implements Runnable {
private int index;
public ExecutorsThread(int index) {
this.index = index;
}
@Override
public void run() {
try {
System.out.println("开始处理线程!");
Thread.sleep(100 * index);
System.out.println(String.format("我是线程%s[%s]", Thread.currentThread().getName(), this.toString()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1、newCachedThreadPool
创建一个可缓存线程池,应用中存在的线程数最大是 231-1
@Test
public void testNewCachedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=1; i<=10; i++) {
ExecutorsThread executorsThread = new ExecutorsThread(i);
executorService.execute(executorsThread);
}
Thread.sleep(1000*60);
}
2、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。Executors.newFixedThreadPool(3);在线程池中保持三个线程可以同时执行,但是注意,并不是说线程池中永远都是这三个线程,只是说可以同时存在的线程数,当某个线程执行结束后,会有新的线程进来。
newFixedThreadPool.execute(new ThreadForpools());这句话的含义并不是添加新的线程,而是添加新的处理业务请求进来。当该线程获取CPU时间后开始执行。
@Test
public void testNewFixedThreadPool() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i=1; i<=10; i++) {
ExecutorsThread executorsThread = new ExecutorsThread(i);
executorService.execute(executorsThread);
}
Thread.sleep(1000*60);
}
3、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
@Test
public void testNewScheduledThreadPool() throws InterruptedException {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
for (int i=1; i<=10; i++) {
ExecutorsThread executorsThread = new ExecutorsThread(i);
// executorService.execute(executorsThread);
executorService.schedule(executorsThread, 1, TimeUnit.SECONDS);
}
Thread.sleep(1000*60);
}
4、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
@Test
public void testNewSingleThreadExecutor() throws InterruptedException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i=1; i<=10; i++) {
ExecutorsThread executorsThread = new ExecutorsThread(i);
executorService.submit(executorsThread);
}
Thread.sleep(1000*60);
}
5、newWorkStealingPool
创建一个指定并行数的线程池,使用多重队列减少竞争,不能保证线程执行的顺序。活动的线程数可以动态地增长或收缩。
@Test
public void testNewWorkStealingPool() throws InterruptedException {
ExecutorService executorService = Executors.newWorkStealingPool(20);
for (int i=1; i<=100; i++) {
ExecutorsThread executorsThread = new ExecutorsThread(i);
executorService.execute(executorsThread);
}
Thread.sleep(1000*60*2);
}
三、两种任务
在Java5之后,任务分两类
1、实现 Runnable 接口的类
2、实现 Callable 接口的类
- 概述:返回结果并且可能抛出异常的任务。
- 实现者定义了一个不带任何参数的叫做 call 的方法。
- Callable 接口类似于,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
- 类包含一些从其他普通形式转换成 Callable 类的实用方法。
- 任务执行返回值Futrue<T> 可以收集返回值和执行过程中遇到的异常信息,对于编写高质量的代码很有帮助。还可以取消任务。
代码实现
public class ExecutorsCallable implements Callable<String> {
private int index;
public ExecutorsCallable(int index) {
this.index = index;
}
@Override
public String call() throws Exception {
System.out.println(String.format("任务[%s] call() 方法被自动调用!", Thread.currentThread().getName()));
if (new Random().nextBoolean()) {
throw new RuntimeException(String.format("任务[%s]执行出错!", Thread.currentThread().getName()));
}
for (int i=10000; i>0; i--);
return String.format("任务[%s] call() 方法被自动调用,任务结果是%d!", Thread.currentThread().getName(), index);
}
}
@Test
public void testCallable() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<>();
for (int i=1; i<=10; i++) {
ExecutorsCallable callable = new ExecutorsCallable(i);
// ExecutorService 执行 Callable 类型的任务,并将结果保存在 future 变量中
Future<String> future = executorService.submit(callable);
// 将执行结果存储到 list 中
resultList.add(future);
}
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
executorService.shutdown();
for (Future<String> future : resultList) {
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
Thread.sleep(1000*60*2);
}
实现 Runnable 接口的类与实现 Callable 接口的类的区别
相同点
- 都可以被ExecutorService执行
不同点
- Runnable 任务执行 run 方法,而 Callable 执行 call() 方法
- Runnable 任务没有返回值,而 Callable 任务有返回值
- Runnable 任务不抛出异常,而 Callable 可以抛出检查的异常
- Callable 的 call() 方法只能通过 ExecutorService的 (<T> task) 方法来执行,并且返回一个 <T><T>,是表示任务等待完成的 Future。
- 将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。
将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。
submit与execute的区别
- 接收的参数不一样,submit 可以接收 Runnable 或 Callable 接口参数,execute 只能接收 Runnable 接口参数。
- submit 有返回值,execute 没有。
- submit 可以对 Exception 处理
结束语
1.清楚Runnable 和 Callable 任务的创建方式
2.ExecutorService 创建线程池的几种方式与区别
3.ExecutorService submit 和 execute 的区别
4.Future<T> 的合理利用
参考资料
https://www.cnblogs.com/ljp-sun/p/6580147.html
https://www.cnblogs.com/wanqieddy/p/3853863.html