前言
最近在看并发编程艺术这本书,对看书的一些笔记及个人工作中的总结。
Java的线程既是工作单元,也是执行机制。从JDK 5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
看一个最简单的demo:
public class ExecutorTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0;i<100;i++){
executorService.submit(() -> System.out.println("run start"));
}
}
}
Executor框架的结构
Executor框架主要由3大部分组成如下:
任务 :包括被执行任务需要实现的接口:Runnable接口或Callable接口。
任务的执行 :包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
异步计算的结果 :包括接口Future和实现Future接口的FutureTask类。
Executor是一个接口,它是Executor框架的基础,它将任务的提交与任务的执行分离开来。
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
ScheduledThreadPoolExecutor是一个实现类,可以在给定的延迟后运行命令,或者定期执行命令。ScheduledThreadPoolExecutor比Timer更灵活,功能更强大。
Future接口和实现Future接口的FutureTask类,代表异步计算的结果。
Runnable接口和Callable接口的实现类,都可以被ThreadPoolExecutor或Scheduled-ThreadPoolExecutor执行。
Executor框架的成员
ThreadPoolExecutor
ThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。
FixedThreadPool:该方法返回一个固定数量的线程池,该方法的线程数始终不变,当一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个队列
中等待有空闲的线程去执行。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
原理说明:
第一个参数是corePoolSize,第二个参数是maximumPoolSize,第三个参数是多余的空闲线程等待新任务的最大时间,第四个即时间单位,第五个是多余任务加入的队列,这里使用的是无界队列(LinkedBlockingQueue)
1)当运行的线程小于corePoolSize,则创建新线程来执行任务。
2)当运行线程达到corePoolSize,则加入到阻塞队列中,当第一步执行完任务后会从队列中反复取出任务来执行。
上篇博客自定义线程池 http://www.jianshu.com/p/60fbe6d6ca5c
我们知道如果构造函数的队列是无界队列,所以多余的任务会加入到队列中,使得maximumPoolSize参数没有作用,拒绝策略也不起作用。
SingleThreadExecutor:创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
构造函数与FixedThreadPool差不多,差别在于corePoolSize和maximumPoolSize都为1,原理类似,这边不过多解释了。
CachedThreadPool:返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程,并且每一个空闲线程会在
60s后自动回收。
底层使用的是SynchronousQueue,之前的博客讲过SynchronousQueue是不存储元素的阻塞队列
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor通常使用工厂类Executors来创建
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public class ScheduledJob {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//在指定的时间后执行任务
//scheduledExecutorService.schedule(() -> System.out.println("定时任务"),1, TimeUnit.SECONDS);
//在1s后每隔1s执行一次定时任务
scheduledExecutorService.scheduleWithFixedDelay(() -> System.out.println("定时任务"),2,1,TimeUnit.SECONDS);
}
}
源码分析:
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
* @param corePoolSize the number of threads to keep in the pool,
* even if they are idle
* @return a newly created scheduled thread pool
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//从这边我们就知道底层是维护的DelayedWorkQueue(带有延迟时间的queue)
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
FutureTask,Runnable,Callable
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。
FutureTask类继承Future,也继承Runnable继承。
当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。
当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);当FutureTask处于已完成状态时,执行FutureTask.cancel(…)方法将返回false。
当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用FutureTask。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才能继续执行。
看一个demo吧:
//调用get方法,主线程阻塞,等task线程执行完毕后才能往下之后
public class FutureTaskDemo {
public static void main(String[] args) {
try {
fun();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void fun() throws Exception{
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask task = new FutureTask<>(new Callable() {
@Override
public String call() throws Exception {
long b = new Date().getTime();
System.out.println("call begin " + b);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append(i).append(",");
}
System.out.println("call end " + (new Date().getTime() - b));
//System.out.println(sb.toString());
return sb.toString();
}
});
executor.execute(task);
long begin = new Date().getTime();
System.out.println("begin:" + begin);//用来计算执行时间的 try {
task.get();
System.out.println("end:" + (new Date().getTime() - begin));
executor.shutdown();
}
}
Runnable:不返回结果
Callable:返回结果,但是只能使用ExecutorService来调用。
FutureTask:实现了Runnable和Future,所以兼顾两者优点,既可以使用ExecutorService,也可以使用Thread
再看一个demo:
public class FutureTaskTest {
public static void main(String[] args) throws Exception{
FutureTask task = new FutureTask<>(new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(5000);
String str ="aaaa";
System.out.println(str);
return str;
}
});
new Thread(task).start();
//task.get();
task.cancel(true);
System.out.println("bbbb");
}
}