java(十二):线程池和ThreadPoolExecutor
来源:互联网 发布:windows dvd 播放器 编辑:程序博客网 时间:2024/05/21 09:06
阅读前请确保对于Thread和Runnable熟悉,可以参考多线程,Thread和Runnable
本博客从源码层面讲起,到最后常用的四个线程池。
Executor
这是线程池实现的最底层的接口,只包含一个方法:
public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the {@code Executor} implementation. */ void execute(Runnable command);}
可以看出,Executor是执行一个线程的(Runnable)的一个单元,即将Runnable放在Executor中执行。这意味着不需要在显式的声明一个Thread来执行一个线程了。
既然如此,那么重要的就在于如何实现execute方法了。
ExecutorService
接口,实现了Executor接口,另外提供了“停下”(shut down)的操作,并停止接受新的任务。
public interface ExecutorService extends Executor { /** * 官方解释是,停止接受新任务,已提交的任务则按序执行完毕,原话: * allow previously submitted tasks to execute before terminating, but no new tasks will be accepted */ void shutdown(); /** * 即在shutdown上做的跟进一步,已提交的任务停止执行,返回等待的任务 * 并尝试停下当前在执行的任务,原话是: * prevents waiting tasks from starting and attempts to stop currently executing tasks * 注意"attempt",该方法并不能保证能停下正在执行的任务 */ List<Runnable> shutdownNow(); //提交一个任务,并返回该任务的Future类 Future<?> submit(Runnable task); //执行给定的任务,所有执行完毕后,返回包含这些任务状态和结果的列表 <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks); //执行给定的任务,任何一个执行完毕,则返回该执行完的任务的状态和结果,并取消的没执行完的任务 <T> T invokeAny(Collection<? extends Callable<T>> tasks)
AbstractExecutorService
抽象类,主要是提供了submit,invokeAny和invokeAll的具体实现。
public abstract class AbstractExecutorService implements ExecutorService { protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); } public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; }
submit方法,意为“提交”,和execute(执行)方法的区别在于,submit内部先将提交的任务包装成一个RunnableFuture,然后调用execute方法,并返回任务的结果。即submit可以取得任务的执行结果。
返回结果是通过RunnableFuture类实现的。即newTaskFor方法会将提交的任务包装成一个RunnableFuture并返回。
剩下的方法比如invokeAll和invokeAny等的实现都需要额外的类,具体实现就不贴了,没有什么很厉害的算法实现,要说的就是invokeAny的实现是借助ExecutorCompletionService来实现的。该类用于保证一个任务被执行完。具体等下个博客在分析吧。
ThreadPoolExecutor
最后来看看线程池ThreadPoolExecutor的实现,该类有2000多行,还是要花点耐心阅读的。
OK,一开始看看官方文档上对于ThreadPoolExecutor的具体介绍吧:
Thread pools address two different problems: they usually provide improved performance when executing large numbers of asynchronous tasks, due to reduced per-task invocation overhead, and they provide a means of bounding and managing the resources, including threads, consumed when executing a collection of tasks. Each ThreadPoolExecutor also maintains some basic statistics, such as the number of completed tasks.
翻译过来就是:
线程池主要解决了两个问题,1.当需要处理大量异步任务是,提供了更好的性能,原因是(线程池)降低了每个任务调用的消耗(相比使用Thread)2.提供了管理和绑定资源的手段;另外,线程池维护了一些基本的统计信息。
官方文档建议使用更容易的工厂方法,比如newCachedThreadPool,newFixedThreadPool,newSingleThreadExecutor,等来代替直接初始化,也就是我们常用到的四种线程池,这将在后文介绍。
线程池的状态和数量
介绍完该类的作用,直接看源码吧,首先说明线程池的5个状态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED。
具体含义如下:
1. RUNNING:表示可以接受新任务,并处理队列的任务
2. SHUTDOWN:不允许接受新任务,但可以处理队列的任务
3. STOP:不允许接受新任务和处理队列的任务,并打断正在执行的任务
4. TIDYING:所有的任务执行完毕,workerCount为0,转变到TIDYING的线程会执行terminated()方法
5. TERMINATED:terminated()方法执行完毕
使用runState变量来表示。
以上5个状态时可以相互转换的,转换规则如下:
RUNNING -> SHUTDOWN:调用了shutdown()方法,而该方法一般是在finalize()里被调用的
(RUNNING or SHUTDOWN) -> STOP:调用了shutdownNow()方法
SHUTDOWN -> TIDYING:当任务队列为空并且线程池为空,则会发生这种状态的转变
TIDYING -> TERMINATED:terminated()方法执行完毕
需要说明的是,上述的状态转换是不能手动调的,当调用shutdown等方法或者出现其他异常时,这种状态的转换是会自动发生的。
另一个需要首先说明的变量是workerCount,该变量是记录有效线程数。
然后看下源码是如何表示上述两个变量的:
public class ThreadPoolExecutor extends AbstractExecutorService { private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
上述代码的阅读比较困难,需要结合注释观看。
我们知道int在java中是4个字节,因为使用补码,因此,最大可赋予的正整数是2^31-1,这个数大概是20亿。也意味着如果使用int来记录有效线程数,那么最大的线程容量是20亿。
同样我们也可以使用int来表达线程池的5个状态。
但ThreadPoolExecutor的源码是并发的大佬Doug Lea写的,怎么会使用一般的方法呢><.
源码中的解释是,int可以用来表达20亿,但我们不需要这么多,从二进制上来说,我们可以使用低位的信息来保存线程的数量,高位保存线程池的状态。OK,那具体如何实现呢?
我们先看下上述变量的具体大小,Integer.SIZE=32,因此COUNT_BITS = 29,剩下的变量使用二进制表示如下:
RUNNING: 111|00000000000000000000000000000SHUTDOWN: 000|00000000000000000000000000000STOP: 001|00000000000000000000000000000TIDYING: 010|00000000000000000000000000000TERMINATED: 011|00000000000000000000000000000CAPACITY: 000|11111111111111111111111111111
仔细观察会发现,使用了整形的低29位表示线程的最大容量CAPACITY,即2^29-1.
剩下的高三位表示线程池的状态。
当我们需要改变状态时只需要改变高三位,改变线程数时,改变低29位即可。这就实现了将两个信息放在一个变量里面,即ctl。
然后使用逻辑运算,与或非等来取得各runState或者workerCount,源码中相应的方法如下:
public class ThreadPoolExecutor extends AbstractExecutorService { ... // Packing and unpacking ctl private static int runStateOf(int c) { return c & ~CAPACITY; } private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; } /* * Bit field accessors that don't require unpacking ctl. * These depend on the bit layout and on workerCount being never negative. */ private static boolean runStateLessThan(int c, int s) { return c < s; } private static boolean runStateAtLeast(int c, int s) { return c >= s; } private static boolean isRunning(int c) { return c < SHUTDOWN; }
上面的函数我们成为解包或者装包,即可以理解为解析ctl或者封装ctl。
线程池和队列
接着进入ThreadPoolExecutor中最重要的两个变量,线程池和任务队列。
public class ThreadPoolExecutor extends AbstractExecutorService { ... private final BlockingQueue<Runnable> workQueue; private final HashSet<Worker> workers = new HashSet<Worker>();
其中,workQueue是任务队列,workers是线程池。
其实到这里,关于两者间的区别,还有很多疑惑,我们在这里详细讲解下两者的区别。
线程池中包含线程,线程是用来消费任务的,可以认为任务需要线程承接再能执行。
这里简述一种线程池可能的工作状态:
线程池为空,任务队列为空,此时开始有任务到来,没来一个任务创建一个线程用于承接任务,并执行,直到线程数到达a后,将到来的任务放在任务队列中,再次期间任务队列中的任务数增加,线程数不变,直到任务队列满了,然后再次开始创建线程用于消费新来的任务,直到线程数达到b。此时拒绝任务,即新来的任务被忽视掉。
上述描述大致明确了线程和队列的功能。
下面举个实际的例子看下:
public class Test01 { public static void main(String[] args){ ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(4)); for(int i=0;i<10;i++){ MyTask myTask = new MyTask(i); executor.execute(myTask); System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+ executor.getQueue().size()); } executor.shutdown(); }}class MyTask implements Runnable { private int taskNum; public MyTask(int num) { this.taskNum = num; } public void run() { //System.out.println("正在执行task "+taskNum); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }}//执行的结果如下://线程池中线程数目:1,队列中等待执行的任务数目:0//线程池中线程数目:2,队列中等待执行的任务数目:0//线程池中线程数目:3,队列中等待执行的任务数目:0//线程池中线程数目:3,队列中等待执行的任务数目:1//线程池中线程数目:3,队列中等待执行的任务数目:2//线程池中线程数目:3,队列中等待执行的任务数目:3//线程池中线程数目:3,队列中等待执行的任务数目:4//线程池中线程数目:4,队列中等待执行的任务数目:4//线程池中线程数目:5,队列中等待执行的任务数目:4//线程池中线程数目:6,队列中等待执行的任务数目:4
从结果中可以看出首先是线程增加,然后是队列中的任务数增加,队列满后,再次增加线程数。
再次验证如下:
public class Test01 { public static void main(String[] args){ ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(4)); for(int i=0;i<10;i++){ MyTask myTask = new MyTask(i); executor.execute(myTask);// System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+// executor.getQueue().size()); } executor.shutdown(); }}class MyTask implements Runnable { private int taskNum; public MyTask(int num) { this.taskNum = num; } public void run() { System.out.println("正在执行task "+taskNum); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }}//一种可能的执行结果://正在执行task 0//正在执行task 8//正在执行task 7//正在执行task 1//正在执行task 9//正在执行task 2//正在执行task 3//正在执行task 4//正在执行task 5//正在执行task 6
上述只是一种可能的结果,但无论随机性多大,最后四条输出任务绝对是3,4,5,6,这是因为这四条是任务队列中的,只有等线程空闲了才能执行任务队列中的任务。
两个变量的讲述暂时到此为止
补充:锁
源码中还有一行代码:
public class ThreadPoolExecutor extends AbstractExecutorService { ... private final ReentrantLock mainLock = new ReentrantLock();
源码解释,任何对于workers线程池的操作都需要获得mainLock 锁。
为什么不使用synchronized同步方法呢?
有两点原因:1.使用锁更灵活,而synchronized消耗较多资源2.synchronized是非公平锁,即不是先来先得(FIFO)锁,因此可能产生大量的“打断”(interrupt)操作。
因此我们使用显式的锁机制。
其他变量
public class ThreadPoolExecutor extends AbstractExecutorService { ... private volatile ThreadFactory threadFactory; private volatile RejectedExecutionHandler handler; private volatile long keepAliveTime; private volatile boolean allowCoreThreadTimeOut; private volatile int corePoolSize; private volatile int maximumPoolSize;
虽然是其他变量,但这些变量多是我们能初始化的变量。
threadFactory:线程工厂,用于创建线程
handler:拒绝处理方法
keepAliveTime:多出的线程的等待时间
allowCoreThreadTimeOut:是否允许核心线程也按照keepAliveTime而消亡
corePoolSize:核心线程数
maximumPoolSize:最大线程数
理解了以上参数,就不难理解线程池的工作机制了。
源码解析暂时到此为止,等有时间了在
四个线程池
四个线程池都是通过Executors里的静态方法创建的,四个方法如下:
–newCachedThreadPool()
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
需要注意的是SynchronousQueue,SynchronousQueue内部没有数据缓存空间,这意味着可以认为SynchronousQueue长度也为0.
于是newCachedThreadPool返回的线程池的功能就很明确了:
来一个新任务,如果没有空闲的线程来承载该任务,就创建线程,否则用空闲的线程去承载新任务。如果空闲的线程超过60s没有接待新任务,则被销毁,知道线程数为0.
该线程池的特点是会立刻满足每一个任务,不让任何任务等待。
–newFixedThreadPool(n)
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
注意到初始化参数,该防范创建的最大线程数和核心线程数相等,并且使用链表队列,这意味着:
一开始会创建n个线程用于承载任务,任务数超过n后,则将任务塞在链表队列中,链表队列无限增长
–newScheduledThreadPool(n)
创建一个定长线程池,并且支持定时和周期性的执行任务.
这个就不看源码了,直接举例吧:
/** * 定时执行 */ ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(20); // 长度20 // 延迟10s执行scheduledThreadPool.schedule(task, 10, TimeUnit.SECONDS); /** *周期性执行 */ ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(20); // 长度20 // 延迟10s执行,每个5s执行一次。scheduledThreadPool.scheduleAtFixedRate(task,10, 5, TimeUnit.SECONDS);
–newSingleThreadExecutor()
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
到这里,主要的四个线程池就讲解完毕了。
至于ThreadPoolExecutor的核心实现等有时间在总结
最后按照惯例,贴一下参考到的资料:
深入理解java之线程池
java四种线程池的使用
- java(十二):线程池和ThreadPoolExecutor
- java 线程池(ThreadPoolExecutor)
- java 线程池(ThreadPoolExecutor)
- Java Executors 和 ThreadPoolExecutor 线程池
- Java线程池ThreadPoolExecutor使用和分析
- Java 线程池ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- Java 线程池ThreadPoolExecutor
- JAVA线程池:ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- ThreadPoolExecutor java 线程池
- Java 线程池ThreadPoolExecutor
- java 线程池ThreadPoolExecutor
- JAVA线程池ThreadPoolExecutor
- java线程池:ThreadPoolExecutor
- Java线程池ThreadPoolExecutor
- Java 线程池 ThreadPoolExecutor
- Nginx初探
- 看一个接口回调的例子
- mxGraph节点图形设置
- STC12单片机实现自动下载程序
- 内网穿透利器——frp
- java(十二):线程池和ThreadPoolExecutor
- z2
- Vision_MATH_(扩展)欧几里得
- 面试心得
- 一个倒三角形接一个正三角形
- AutoCloseable与Closeable源码翻译
- C++笔记——static数据成员
- Swift从相册选择图片,图文混排并且可以保存、上传数据
- Rancher 2.0 的第一印象