根据JDK深入详细学习理解JAVA线程池 笔者之所以写这篇博客是因为很多博客或者书籍在介绍线程池相关内容的时候太理论化直接上结论并不能正确的表述出来所说的每一个结论是为什么怎么理解。所以在此记录一下自己的学习过程大家共勉提前说明本文文章内容有些深入繁杂显得自然就有些混乱所以请静下心来慢慢理解首先进行这样一个思考线程池是什么作用是什么为什么我们需要使用线程池这个问题相信很多地方都直接告诉你线程池的好处但是你有没有仔细想过那些所谓的好处是怎么实现的话不多说回到问题。想一下这样一个问题假如有一组客户端请求同时访问服务器服务器为了解决并发问题就需要为每一个请求开启一个线程保证客户端都能够及时的得到处理。那么这个过程产生一个这样的问题每个请求的处理很简单时间非常短并且请求的基数很大那么我们这样做cpu的处理器大部分时间都在干嘛很容易有这么一个结论CPU竟然一直在不停的创建和销毁线程。显然这对于程序本身来说是不合理的。这里补充一点额外的知识就是Java对于线程的创建和销毁的代价是远远大于普通类的。这一点很容易理解线程需要实现很多底层的操作系统级别的交互。他会需要很多资源去配合。那么上述并发问题我们怎么解决呢我们的主角线程池登场了。那什么是线程池呢其实可以把线程池理解为一个并发框架主要就是为了解决类似上述的问题。我们来看看一些书籍或者论文博客直接给出的线程池的好处的结论这里说明一点大部分博客都没有给出理由本文章后续会结合jdk源码一点点理解这些所谓的好处是怎么实现的。如果你嫌麻烦请自己跳转至下方红色标题处直接看杰伦哈哈是不是很贴心1.降低资源消耗通过重复利用已创建的线程降低线程创建和销毁的代价。2.提高响应速度当任务到达时任务可以不需要等到线程创建就能立即执行。3.提高线程的可管理性线程是稀缺资源如果无限制的创建不仅会消耗系统资源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。如果你本来就对线程池有一些基础的理解那么你仔细想想上述的好处你真正理解是为什么吗其实上述的几条好处就是我们为什么要使用线程池。如果你之前根本不知道关于线程池的任何东西看到这里是不是一脸懵?内心是这样的这博客写的什么啊既没有真正的说出来线程池是什么也像别人一样直接照搬结论也没有说出来为什么真垃圾啊哈哈不要慌干货来了。因为通过我个人的学习我觉得线程池是没有必要使用一些所谓的定义文字去抽象的描述它的因为它本身就是为了解决某些并发的问题所以我们还是从底层的实现去理解。上述的这些描述只是让你对所谓的线程池有一个抽象笼统的认知。线程池的相关实现原理以及与其相关的知识剖析。(JDK1.8)java中有一个底层实现的线程池类ThreadPoolExecutor我们就根据这个类来学习jdk中线程池的实现。我们先来看一下所谓的线程池这样的框架其实现流程是怎么样的提醒你如果你看不懂很正常因为线程池的内容本身就是相互关联的而文章必须从某一点切入解释所以如果你现在看不懂整个文章看完你一定会懂的!线程池内部维护这很多成员变量现在你需要知道这几个核心数量最大数量阻塞队列。不理解无所谓你现在不需要理解你只需要知道有这三个变量1.任务提交到线程池首先判断线程池内部已经创建的线程池的数量是否小于核心数量如果小于就创建一个新的线程来执行提交的任务。2.如果1中已经存在的线程数大于等于核心数量则把提交的任务放进阻塞队列中等待正在执行任务的线程执行完任务后来执行阻塞队列中的任务。3.在放进阻塞队列的时候那自然也要判断阻塞队列是否已满如果没有满就放入进去如果已经满了这时候需要去判断最大数量如果线程池中存在的线程数小于最大数量则再创建新的线程。如果数量已经等于最大数量了那么此时提交的任务必须交由一种叫做饱和策略的逻辑去处理它。使用线程池无非就是创建使用执行任务销毁的过程。线程池的创建我们来看ThreadPoolExecutor的构造方法当程序中使用该类的时候我们来看一下JDK底层的实现进入到源码我们会发现其构造函数有很多个如下所示此时的你如果仔细的看了这几个构造函数你会发现所有的构造函数其实最后都是执行了第四个的逻辑。也就是代码中的this(.....)方法前三个构造函数都在其内部显式的调用了第四个构造方法。这四个构造方法其实就是基于形式参数的四个方法重载而最后都执行了第四个重载的构造方法那里面的第四个构造方法我们发现他的形式参数是最多的涉及到了七个。1.corePoolSize:核心线程数量这时候你是不是想到了执行流程中的这个参数哈哈2.maximumPoolSize线程池最大容量。3.keepAliveTime:线程活动保持时间线程池的工作线程所谓的工作线程就是执行任务的线程空闲后所谓的空闲就是把刚才其执行的任务执行完了并且此时阻塞队列没有任务需要执行保持存活的时间。这个参数很好理解就是线程池内的线程在没有任务需要执行后在存活多长时间才去销毁他如果设置为0那就是马上销毁。4.TimeUnit一个keepAliveTime的单位自行查看jdk,他是一个枚举类型内部是可选的时间单位。5.BlockingQueue阻塞队列就是设置线程池所采用的阻塞队列。后续会介绍。6.ThreadFactory线程工厂用于设置创建线程的工厂无需多理解。7.RejectedExecutionHandler饱和策略四种后续会介绍。了解了这七个参数我们返回构造函数理解构造函数的细节问题1.四个构造函数最后都是调用的最后一个七参数的构造函数。2.四个构造函数的差异仅仅存在于外部传入的参数的数量而正如jdk源码所看到的前五个参数是一定要进行传入的也就是无论外部显式使用的是哪一个构造方法都必须指定前五个参数。而线程工厂和饱和策略没有要求外部必须指定。3.既然6和7两个参数没有要求外部必须指定那他的默认是什么呢看上述源码默认传入的是Executors.defaultThreadFactory(), defaultHandler这两个参数。工厂就不多说了我们看看这个默认的handler4.我们看看最终使用的第四个构造方法的注意事项也就是上述代码中箭头所指的那两个if判断。其中第一个if要求了核心数量和最大容量之间的数量关系的限制否则抛出异常。第二个if强制判断了阻塞队列工厂饱和策略不能为空。现在我们来看看几种阻塞队列JDK7提供了7个阻塞队列。首先什么是阻塞队列BlockingQueue是一个支持两个附加操作的队列。这两个附加操作支持阻塞的插入和移除方法。①支持阻塞的插入方法意思是当队列满时队列会阻塞插入元素的线程直到队列不满。②支持阻塞的移除操作意思是在队列为空时获取元素的线程会等待队列变为非空。注意别矛盾线程池只是用到了阻塞队列存储结构并没有非要用到这两个阻塞方法线程池进行具体的插入移除操作所采用的具体逻辑是线程池自己的流程逻辑。可以返回文章上述中的线程池执行流程。1.ArrayBlockingQueue一个由数组结构组成的有界阻塞队列2.LinkedBlockingQueue一个由链表结构组成的有界阻塞队列3.SynchronousQueue一个不存储元素的阻塞队列4.LinkedBlockingDeque一个由链表结构组成的双向阻塞队列5.PriorityBlockingQueur一个支持优先级排序的无界阻塞队列6.DelayQueue一个使用优先级队列实现的误解阻塞队列7.LinkedTransferQueue一个由链表结构组成的无界阻塞队列由于阻塞队列并不是本文章想要描述的内容所以这里简单看一下即可1.ArrayBlockingQueue直接看源码基于数组的队列需要传参指定大小默认顺序为FIFO2.LinkedBlockingQueue基于链表的队列大小参数可以指定也可以不指定不指定默认无穷顺序FIFO(FixedThreadPool()使用了这个阻塞队列。)3.SynchronousQueue一个不存储元素的阻塞队列。(CachedThreadPool()使用了这个阻塞队列。)好的我们在来看看四种不同的饱和策略1.AbortPolicy直接抛出异常。2.CallerRunsPolicy只用调用者所在线程来运行任务3.DiscardOldestPolicy丢弃阻塞队列里最近的一个任务执行当前任务4.DiscardPolicy不处理丢弃掉好了基于此或许你还有很多疑问但是至少对于线程池的基本变量有了初步的认知了。向线程池提交任务:向线程池提交任务其实就是线程池如何去处理你所提交的任务哈哈终于到了线程池的底层本质了我先剧透告诉你哈哈提交任务的两个方法execute()和submit()我们先来回顾一些基础知识创建线程的几种方法1.继承线程类Thread重写run方法2.实现Runnable接口实现run方法3.实现Callable接口实现call方法。注意理解该方法的区别虽然看似三种方法其实真正意义上作为线程类的只有前两种方法自行体会吧每个人的理解都不同。我的理解是自定义线程的本质是利用了Thread类的构造方法而Thread的构造方法虽然有很多但都是基于Runnable接口的就连Thread类本身都是实现Runnable接口的。而任何Thread类的构造方法都没有涉及到Callable接口并且callable只有在线程池的submit下才有其实现意义所以可以简单理解为callable接口是线程池产生后衍生出来的一种东西。好了稍微理解一下上述辩述我们来看一下执行任务的两个方法1.execute只能执行Runnable接口的任务我们进入到源码去分析它重点来了这时候你慢慢就会理解线程池的内部逻辑了好了大概浏览发现他其实和一个addWorker方法有直接联系继续看addworker方法很长下面四张截图应该是一起的其实你只需要看箭头哈哈.好了是不是很蒙我简单帮你梳理一下execute()方法调用addWorker(),addWorker()方法中又出现了Worker类Worker类是什么呢先简单描述一下下面会附上代码Worker其实是线程池的本质线程池接收任务提交后真正去执行的就是这个Worker类也就是说Worker类是线程内部所维护的线程。是由这个Worker类的执行逻辑去执行任务的。先理解到此后续有更深的理解。来看一下Worker类的定义以及构造方法创建Worker对象实例的时候显然传入了一个Runnable接口而Worker有成员变量firstTask任务和thread。而这个thread是干嘛的呢其实很简单就是实现了一步功能由于Worker类是实现了runable接口而实现runable接口的线程类在作为线程使用的时候还得基于Thread类的构造方法所以这一步this.thread getThreadFactory().newThread(this);这句语句把Worker又装饰成了Thread。好了简单理解了Worker后我们此时再回到上面的addWorker介绍的最后一段代码也就是着重标记三个箭头指向的那个地方t.start()好了真正的boss要上场了我们来看看strat()启动Woreker之后所执行的run()方法。好了其中又设计到了runWorker方法我们再进入到runWorker()方法好了到此线程池底层的实现原理终于有了眉目了。哦原来是在Worker线程中执行了任务的run()方法哈哈。哎等等我们是不是发现了一个重要的东西如果你逻辑很敏锐你发现我们提交的Runnable接口的任务在线程池底层原来是直接执行的run()方法而不是start()哇哦jdk源码关于线程池的设计真的是太精妙了这个调用run()方法的思路给了我们什么启示呢线程池的核心还记得文章开头提到的线程池的好处吗最重要的就是线程池重复利用已创建的线程来降低线程创建和销毁所造成的消耗。无论当时的你是怎么理解这条好处的现在的你应该深入理解了吧原来底层并不是让任务类直接作为线程执行而是把任务封装到Worker线程中开启Worker线程而Woker线程中直接执行任务的run()方法且Worker是线程池维护的线程从而达到重复利用worker的目的。这时候如果你没理解什么意思你就需要理解一个线程类的start()方法和run()方法的区别了。start方法是开启线程并分配给线程除cpu执行外的所有所需资源这个资源是很多的所以说线程的代价是很大的。而直接执行run()方法就简单多了就像正常类执行正常的成员方法一样这样就达到了降低资源消耗的目的。其实如果你只有几个线程的并发线程池的作用并不大因为他还是需要创建Worker线程但是如果你有数以万计的任务提交那通过反复使用worker线程的方法自然就体会到线程池这个思路的庞大意义了。说到这里你也会感到其实线程池提交任务是把线程的创建销毁Worker.start()和任务的执行(Runnable.run())分离开了达到了线程的重复利用。理解了上述问题你也应该知道了线程池的第二条好处就是响应任务提交直接执行这就不多说了因为Worker已经维护在线程池中了只要有空闲的Worker线程那你提交任务的时候就省去了创建线程的时间直接让Worker调用提交任务的run()方法多伟大的思路啊,佩服哈哈。至于第三条好处不多说线程池作为一个线程容器的身份自然就是去更好的组织管理线程了。回到问题关于任务执行还有一个submit方法我们来简单学习一下简单的介绍不多说上述代码的注释中也有说明我们来看看submit方法的源码简单分析一下submit有几个方法重载重载的逻辑是传入参数的接口类型我们看到底层执行的execute()方法那这个execute和ThreadPoolExecutor是一个方法吗哈哈是的首先submit()方法就不是ThreadPoolExecutor的。我们一点点分析原来ThreadPoolExecutor继承了一个AbstractExecutorService类但是此类是一个抽象类不能实例化通过jdk源码的查看我们发现ThreadPoolExecutor类中并没有submit方法也就是作为子类他没有重写submit所以他调用的是继承自父类AbstratExecutService的submit。看到这里你就应该明白了此时submit中的execute方法他的所属类应该是抽象类引用的实际类ThreadPoolExecutor。这时候你应该明白ThreadPoolExecutor中的execute方法其实是对上层抽象方法的实现哈哈逻辑很敏锐。那么我们稍后再看这个execute().因为此时你发现又出来个RunnableFuture类。但是细心的你一定发现了其中每个方法的第三行代码返回一个RunnableFuture无论传入的参数是哪个接口都是以这个方法进行了封装我们继续看这个东西的源码1哇哦原来这个新东西继承了Runnable和Future接口。Future接口是什么呢你现在只需要知道submit方法的返回值是一个Future接口的具体实现类就行。2好了上述submit方法中涉及到了newTaskFor()方法我们发现该方法又返回一个FutureTask类型的实例。我们继续看FutureTask的实例。3这个类实现了RunnableFuture的接口下面是其构造方法。45哇哦这次的信息量有点大不过没有关系稍安勿躁。我们发现传入的是Callable接口的时候并没有进行什么额外的操作那么传入的是runnable接口的时候呢看5中代码第二行this.callable Executors.callable(runnable, result);这是在干嘛呢直接看参数传入Runnable返回Callable哦是不是有印象采用了一种设计模式把Runnable接口封装成了Callable目的就是为了统一处理这两个接口。哇哦前辈的思路真的很厉害总结上面这一段的思路就是调用subimit方法其实是调用的父类的submit方法而该方法会首先把传入的接口包括runnable和callable接口封装成一个RunnableFuture,其实本质是FutureTask而封装的方法就是newTaskFor该方法又统一处理runnable和callable因为该方法底层又把runnable封装成callable处理了。然后执行ThreadPoolExecutor中的execut方法此时传入的参数是RunnableFuture当然了继承Runnable接口。此时你应该立刻就想到submit中本质上执行的execute方法传入的是FutureTask而其实现了Runnable接口所以submit底层的提交后Worker线程中执行的应该是这个FutureTask的run()方法来看一下果不其然在其run方法中有调用了Callable的call方法。看上述箭头明白两件事情就是关于这个方法的①在submit中调用newTaskFor方法如果传入callable接口那所调用的FutureTask的构造函数其实就是把这个callable接口的实例关联进了FutureTask的一个Callable类型的成员变量。所以在run方法中直接得到所关联的Callable的实例。②如果submit中调用newTaskFor传入的是runnable接口那在调用Futuretask的构造方法的时候就需要额外的操作。把Runnable必须包装成callable接口类型因为FutureTask的run方法就只是针对callable的没有任何关于runnable接口类型的处理。所以就有了如果你看到这里感觉有点乱那我把这些类的继承关系稍微梳理一下1.关于ThreadPoolExecutor:这里关于ThreadPoolExecutor多说一句就是基于Executor框架的工具类Executors可以创建三种类型的ThreadPoolExecutor。①FixedThreadPool可重用固定线程数的线程池。阻塞队列是LinkedBlockingQueue②SingleThreadExecutor使用单个Worker线程的线程池阻塞队列也是:LinkedBlockingQueue③CachedThreadPool根据需要创建新线程的线程池阻塞队列是SynchronousQueue2.关于Future的层次关系这里只是做一个简单的梳理其实线程池的框架Executor还涉及很多类和接口自行学习不在本文章的讨论中。好了我们看一下调用submit的具体使用代码个人的简单练习代码在文章最后有上传如果你是初学者可以复制到自己的编译器中学习一下很简单只是作为一个学习参照使用没有其他作用作者在此也只是利用练习代码叙述jdk的源码原理。好了看到这里相信你现在已经不是线程池方面的小白了因为我们到了最后一步了很简单关闭线程池关闭线程池两个方法如上面代码中箭头所指内容原理遍历线程池中的工作线程然后逐个调用线程的interrupt方法来中断线程所以无法响应中断的线程可能永远无法终止但是他们既然作为两个方法必然有其特定的区别1.shutDownNow方法首先将线程池的状态设置为STOP然后尝试停止所有的正在执行或暂停任务的线程并返回等待执行任务的列表2.shutDown只是将线程池的状态设置为SHUTDOWN状态然后中断所有没有正在执行任务的线程。所以你会有这样的结论一般我们采用shutdown方法因为他不会打断正在执行任务的线程。如果任务不一定要执行完我们强制中断则可以调用shutDownNow方法。看到这里相信再被问到关于线程池的相关问题的时候你的内心是这样的都给我让开LZ要装逼了。最后附上文章中所用的简单练习代码import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadPoolTest { public static void main(String args[]) { //这里只是展示几种阻塞队列jdk7提供了7种 BlockingQueueRunnable aBQueue new ArrayBlockingQueueRunnable(10); //基于数组结构的有界队列 BlockingQueueRunnable lBQueue new LinkedBlockingQueueRunnable(); //基于链表结构的队列 BlockingQueueRunnable sBQueue new SynchronousQueueRunnable(); //一个不存储元素的阻塞队列 BlockingQueueRunnable pBQueue new PriorityBlockingQueueRunnable(); //具有优先级的无限阻塞队列 //线程池的基本实现类最大构造函数有7个底层参数其他所有构造函数内部都是调用最大构造函数 ThreadPoolExecutor tpe new ThreadPoolExecutor(5, 6, 0, TimeUnit.DAYS, aBQueue); tpe.execute(new RunnableTest()); //execute只能执行Runnable无返回值 tpe.execute(new ThreadTest(线程1)); //没有问题Thread本身就实现了Runnable接口 //start方法的本质 //Thread内部维护着一个Runnable类型的成员变量target //然后Thread本身又实现了Runnable的接口内部实现run()方法 //该方法就是调用target的run()方法 //target在构造函数时就传入了一个Runnable接口的实现类 (new ThreadTest(线程1)).start(); //submit可以执行Runnable,此时方法也是有返回值的,也是返回Future接口 //底层实现其实submit把runnable接口封装成了callable tpe.submit(new RunnableTest()); //submit还可以执行Callable方法有返回值返回Future FutureString future tpe.submit(new CallableTest()); try { //Future只是个接口其真正实现类都是FutureTask //真正的关系是RunnableFuture接口继承了Future和Runnable //FutureTask实现了RunnableFuture接口 String aa future.get(); System.out.println(aa); } catch (Exception e) { e.printStackTrace(); } System.out.println(程序正常运行无异常); tpe.shutdown(); //pe.shutdownNow(); } } //封装程序中用到的几个线程 //继承线程的方法实现线程 class ThreadTest extends Thread { public ThreadTest(String name) { super(name); } //重写run方法 public void run() { System.out.println(Thread.currentThread().getName()); } } //实现Runnable接口的线程类无返回值 class RunnableTest implements Runnable { public void run() { for(int i 0; i 10; i) { System.out.println (Thread.currentThread() 程序子线程循环的次数 i); } } } //实现Callable接口的类有返回值 //注意这种方法只能配合线程池使用并不能够单独的启动线程 //因为Thread的所有构造方法都没有能够传入Callable接口的 //除非手动的再实现Runnable接口或者继承Thread类 class CallableTest implements CallableString { public String call() { return new String(Thread.currentThread().getName()); } }