标签:允许 注释 微信 相互 理解 time tco live res
Java是一门多线程的语言,基本上生产环境的Java项目都离不开多线程。而线程则是其中最重要的系统资源之一,如果这个资源利用得不好,很容易导致程序低效率,甚至是出问题。有以下场景,有个电话拨打系统,有一堆需要拨打的任务要执行,首先肯定是考虑多线程异步去执行。假如我每执行一个拨打任务都new一个Thread去执行,当同时有1万个任务需要执行的时候,那么就会新建1万个线程,加上线程各种初始销毁等操作,这个消耗是巨大的。而其实往往实现这些功能的时候,并不是完全需要实时马上完成,只是希望在可控范围内尽量提高执行的并发性能。
因此线程池技术应用而生,Java中最常用的线程池技术就是ThreadPoolExecutor。接下来就整体看看ThreadPoolExecutor的实现。<!-- more -->
这个类的注解非常多,很多也是重点,所以就不从注解开始看起。先从使用说起,有个概念先。
// 核心线程
int corePoolSize = 5;
// 最大线程
int maximumPoolSize = 10;
// 线程空闲回收时间
int keepAliveTime = 30;
// 线程空闲回调时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 队列大小
int queueSize = 20;
// 队列
BlockingQueue workQueue = new ArrayBlockingQueue<Runnable>(queueSize);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
executor.execute(() -> {
// do something 1
});
executor.execute(() -> {
// do something 2
});
定义好一些必要的参数,构建一个ThreadPoolExecutor对象。然后调用对象的execute()方法即可。
参数说明:
非常多人误解了corePoolSize、maximumPoolSize、workQueue的相互关系。不少人认为无论队列选择什么,corePoolSize和maximumPoolSize一定是有用,定义一定是生效的,其实并不然啊!
看下线程基本规则注解说明
另外,如果想在线程初始化时候就有核心线程,可以调用prestartCoreThread()或prestartAllCoreThread(),前者是初始一个,后者是初始全部。
再看看排队策略
从以上规则可以看出来,核心线程数和最大线程数,还有队列结构是相互影响的,如何排队,队列多大,最大线程是多少都是不一定的。
再看看保持存活机制
当超过核心线程数的线程,线程池会让该线程保持存活keepAliveTime时间,超过该时间则会销毁该线程。
另外默认对非核心线程有效,若想核心线程也适用于这个机制,可以调用allowCoreThreadTimeOut()方法。这样的话就没有核心线程这一说了。
综合以上,线程池在多次执行任务后,会一直维持部分线程存活,即使它是闲置的。这样的目的是为了减少线程销毁创建的开销,下次有个任务需要执行,直接从池子里拿线程就能用了。但核心线程不能维护太多,因为也需要一定开销。最大的线程数保护了整个系统的稳定性,避免并发量大的时候,把线程挤满。工作队列则是保证了任务顺序和暂存,系统的可靠性。线程存活规则的目的和维护核心线程的目的类似,但降低了它的存活的时间。
另外还有拒绝机制,它提供了一些异常情况下的解决方案。
这个ctl变量是整个线程池的核心控制状态。
这个ctl代表了两个变量
runState有以下几种状态:
当调用了shutdown(),状态会从RUNNING变成SHUTDOWN,不再接收新任务,此时会处理完队列里面的任务。
如果调用的是shutdownNow(),状态会直接变成STOP。
当线程或者队列都是空的时候,状态就会变成TIDYING。
当terminated()执行完的时候,就会变成TERMINATED。
带着对上面的规则与机制的认识,现在从就这这个入口开始看看源码,到底整个流程是怎么实现的。
注释基本解释了所有代码,代码也没什么特别的。其中最主要的还是addWoker()这个方法,下面来看看。
先了解下这个方法的整体思路
从描述可知,addwoker失败,会在线程池状态不对、线程满了或者线程工厂创建线程池失败时候发生。
这个方法比较长,分两段看。先看第一段。retry:
这种写法,如果比较少看源码的,应该是前所未见的了。这是个循环的位置标记,是java的语法之一。看回代码,这里面for循环还嵌套里一个for循环,而retry:
是标记第一个for循环的,后面break
和continue
语句都指向到了retry
。说明break
和continue
是都是操作外层的for循环。retry可以是任何变量命名合法的字符。
然后看看外出for循环的if语句
这个if判断想要执行到return false;
,队列为空是一个必要条件。因为addWork()不单只接收新任务会调用到,处理队列中的任务也会调用到。而前面提到SHUTDOWN状态下还会处理队列中的任务的,所以队列不为空是会让它继续执行下去的。
对于内层的for循环
会先判断worker的数据是否符合corePoolSize和maximumPoolSize的定义,不满足则返回失败。
然后尝试CAS让workerCount自增,如果CAS失败还是继续自旋去自增,直到成功。除非线程池状态发生了变化,发退回到外层for循环重新执行,判断线程池的状态。
第一段的代码,就是让workerCount在符合条件下自增
第二段代码
这段比较好理解,先创建一个Worker对象,这个Worker里面包含一个由线程工厂创建的线程,和一个需要执行的任务(可以为空)。如果线程创建成功了,那么就加一个重入锁去把这个新建的Worker对象放到workers成员变量中,在加入之前需要重新判断下线程池的状态和新建线程的状态。如果worker添加到workers成员变量中,就启动这个新建的线程。最后如果添加失败,则执行addWorkFailed(w)
。
如果失败了,加锁操作回滚下wokers、workerCount,然后判断下状态看看是否需要终结线程池。
addWorker()
大概的流程就这样。
对于其他方法,没有什么特别的,在此不再过多的叙述,有兴趣的可以翻翻源码阅读下。
回顾总结下上面的核心要点
retry:
是一种标记循环的语法,retry可以是任何变量命名合法字符。更多技术文章、精彩干货,请关注
博客:zackku.com
微信公众号:Zack说码
你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识
标签:允许 注释 微信 相互 理解 time tco live res
原文地址:http://blog.51cto.com/14073604/2318656