本文参考:
https://www.cnblogs.com/exe19/p/5359885.html 写的很好,但是jdk版本老,与8的代码不符。
https://www.jianshu.com/p/50fffbf21b39 可以简单看一下
https://www.cnblogs.com/trust-freedom/p/6681948.html jdk版本与8代码相符,流程较为详细
Ch1.基础 必会
线程池的核心类与层级关系:
图中没有画出来但是也很常用的类:Executors。是线程池的静态工厂,提供了快捷创建线程池的静态方法。
ThreadPoolExecutor的核心成员变量:
- corePoolSize和maximumPoolSize:分别是核心线程池的容量和整个线程池的最大容量。核心线程与非核心线程的区别是,如果没有调用allowCoreThreadTimeOut(true),那么核心线程不会因为一直没有被分配任务而被销毁,而非核心线程当处于空闲状态(即没有执行任何任务)一定时间后(keepAliveTime),会被销毁。
- keepAliveTime和unit:表示线程没有任务执行时最多保持多久时间会终止。如果调用了allowCoreThreadTimeOut(true),keepAliveTime将会作用于所有线程上,否则只作用于非核心线程上。 unit是keepAliveTime的时间单位,在TimeUnit枚举类型中有DAYS,HOURS,MINUTES,SECONDS,MILLISECONDS,MICROSECONDS,NANOSECONDS等7中静态属性对应7个取值。二者共同决定了线程的空闲终止时间。
- workQueue和threadFactory:workQueue的类型为BlockingQueue<Runnable>即线程的阻塞队列,队列中的线程是等待被执行的线程。当所有的核心线程都在执行任务时,新添加的任务会被添加到workQueue中等待处。如果队列满了,则新建非核心线程执行任务。 threadFactory线程工厂,用于创建线程。
- handler,用于处理拒绝任务时的策略,有几种情况:ThreadPoolExecutor.AbortPolicy(丢弃任务并抛出RejectdExecutionExeption异常);DiscardPolicy(丢弃任务但是不抛出异常);DiscardOldestPolicy(丢弃队列最前面的任务,然后重新尝试执行任务);CallerRunsPolicy(由调用线程处理该任务)
- private final AtomicInteger ctl : 老版本的jdk使用volatile的变量workerCount与runState,分别表示线程数量和线程池的运行状态。jdk8中使用一个变量ctl来表示这两个值。其高3位用于维护线程池的运行状态,低29位维护线程池中的线程数量。这样就需要维护两个volatile来保证线程之间的可见性了。
- mainLock = new ReentrantLock() : 线程池的主要状态锁,对线程池的状态(如线程池大小、runState/ctl)进行改变要获取这个锁。注意这个锁是个可重入锁。
- HashSet<Worker> workers:其中Worker是内部类,实现了Runnable接口。该set包含了线程池中的所有worker线程。worker其实就是一个Runnable,其也是需要构造成一个Thread对象,然后调用Thread start方法运行的。只不过在worker的run方法中是定一个了一个
runWorker
的方法。这个方法的主要内容从 for 循环的不停的从task队列中获取对应的runnable的task,然后同步调用这个task的run()方法。其实就是在某个线程中,不停的拿队列中的任务进行执行。
线程池的执行流程
当试图通过execute方法将一个Runnable任务添加到线程池中时,按照如下顺序来处理:
- 如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务
- 如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行)
- 如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务
- 如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理
服务策略与阻塞队列
线程池的服务、排队策略,与其成员变量workQueue直接相关,而BlockingQueue的不同实现方式可以产生不同的效果,从而导致采用不同类型的阻塞队列可以产生不同的排队策略。
- LinkedBlockingQueue(基于链表的无界队列,FIFO),理论上该队列可以对无限多的任务进行排队,将导致在所有corePoolSize线程都工作的情况下将新任务加入到队列中,因此maximumPoolSize的值也就无效了。 如Executors.newFixedThreadPool()是基于链表的无界队列。
- SynchronousQueue(不排队,直接提交),将任务直接交给线程而不保持它们。试图将任务加入阻塞队列的操作会失败,因此需要直接构造一个新的线程来处理新添加的任务并将其加入线程池中,如Executors.newCachedThreadPool()就是这个策略。
- ArrayBlockingQueue(基于数组结构的有界队列,FIFO),并指定队列的最大长度:使用有界队列可以防止资源耗尽,但也会造成超过队列大小和maximumPoolSize后,提交的任务会被拒绝的问题,比较难调整和控制。
常见线程池实现
Executors类中有很多用于快捷创建线程池的静态方法,常见的有
- Executors.newFixedThreadPool():创建一个指定工作线程数的线程池,其中参数 corePoolSize 和 maximumPoolSize 相等&#x