1.创建线程池的四种方式
package com.dudu.lizhen.thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 线程池的使用方法 * Created by lizhen on 2018/2/3. */ public class ThreadPoolTest { public static void main(String[] args) { /** * 1.可缓存的线程池,可以重复利用 */ ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int temp = i; executorService.execute(new Runnable() { @Override public void run() { System.out.println("threadName:" + Thread.currentThread().getName() + ",i:" + temp); } }); } /** * 2.创建一定长度的线程池 */ ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; executorService.execute(new Runnable() { @Override public void run() { System.out.println("threadName:" + Thread.currentThread().getName() + ",i=" + temp); } }); } /** * 3.可定时线程池,5秒之后创建三个线程完毕 */ ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3); for (int i = 0; i < 10; i++) { final int temp = i; scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println("threadName:" + Thread.currentThread().getName() + ",i=" + temp); } }, 5, TimeUnit.SECONDS); } /** * 4.单线程池 */ ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int temp = i; executorService.execute(new Runnable() { @Override public void run() { try { //我们延迟0.05秒看看线程池shutdown方法什么时候执行 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("threadName:" + Thread.currentThread().getName() + ",i=" + temp); } }); } //线程池关闭 executorService.shutdown(); } }
2.线程池的原理剖析
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }返回的时候其实new ThreadPoolExecutor(),我们来看一下它的参数是什么意思
3.线程池的配置
线程并非越多越好,一个任务(Runable)执行时间越短,需要的线程数量就应该越少,应为线程多了有可能切换上下文的时间就已经执行完了。
4.方法说明
ThreadPoolExecutor顾名思义,是一个线程池管理工具类,该类主要提供了任务管理,线程的调度和相关的hook方法来控制线程池的状态。当我们调用ExecutorService创建线程池的时候,其实就是创建了一个ThreadPoolExecutor
任务管理主要方法如下:
上述方法中,execute()和submit()方法在有空闲线程存在的情况下会立即调用该线程执行任务,区别在于execute()方法是忽略任务执行结果的,而submit()方法则可以获取结果(在抽象的父类AbstractExecutorService已经有了具体的实现),可以看到最后返回的是一个Future模式的线程,可以具有返回值和定义异常。
除此之外,ThreadPoolExecutor还提供了shutdown()和shutdownNow()方法用于关闭线程池,区别在于shutdown()方法在调用之后会将任务队列中的任务都执行完毕之后再关闭线程池,而shutdownNow()方法则会直接关闭线程池,并且将任务队列中的任务导出到一个列表中返回。
除上述用于执行任务的方法外,ThreadPoolExecutor还提供了如下几个hook(钩子)方法:
在ThreadPoolExecutor中这几个方法默认都是空方法,beforeExecute()会在每次任务执行之前调用,afterExecute()会在每次任务结束之后调用,terminated()方法则会在线程池被终止时调用。使用这几个方法的方式就是声明一个子类继承ThreadPoolExecutor,并且在子类中重写需要定制的钩子方法,最后在创建线程池时使用该子类实例即可。
2.任务调度
a.相关参数
对于ThreadPoolExecutor的实例化,其主要有如下几个重要的参数:
创建一个线程池
返回一个线程池管理工具类
调用自身的构造方法
corePoolSize: 线程池核心线程的数量;
maximumPoolSize: 线程池可创建的最大线程数量;
keepAliveTime: 当线程数量超过了corePoolSize指定的线程数,并且空闲线程空闲的时间达到当前参数指定的时间时该线程就会被销毁,如果调用过allowCoreThreadTimeOut(boolean value)方法允许核心线程过期,那么该策略针对核心线程也是生效的;
unit: 指定了keepAliveTime的单位,可以为毫秒,秒,分,小时等;
workQueue: 存储未执行的任务的队列;
threadFactory: 创建线程的工厂,如果未指定则使用默认的线程工厂;
handler: 指定了当任务队列已满,并且没有可用线程执行任务时对新添加的任务的处理策略;
b.调度策略
当初始化一个线程池之后,池中是没有任何用户执行任务的活跃线程的,当新的任务到来时,根据配置的参数其主要的执行任务如下:
若线程池中线程数小于corePoolSize指定的线程数时,每来一个任务,都会创建一个新的线程执行该任务,无论线程池中是否已有空闲的线程;
若当前执行的任务达到了corePoolSize指定的线程数时,也即所有的核心线程都在执行任务时,此时来的新任务会保存在workQueue指定的任务队列中;
当所有的核心线程都在执行任务,并且任务队列中存满了任务,此时若新来了任务,那么线程池将会创建新线程执行任务;
若所有的线程(maximumPoolSize指定的线程数)都在执行任务,并且任务队列也存满了任务时,对于新添加的任务,其都会使用handler所指定的方式对其进行处理。
c.调度策略注意点
在第二步中,当前核心线程都在执行任务,并且任务队列已满时,会创建新的线程执行任务,这里需要注意的是,创建新线程的时候当前总共需要执行的任务数是(corePoolSize + workQueueSize),并不是只有corePoolSize个任务;
在第三步中,这里workQueue主要有三种类型:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
第一个是有界阻塞队列,第二个是无界阻塞队列,当然也可以为其指定界限大小,第三个是同步队列,对于ArrayBlockingQueue,其是需要指定队列大小的,当队列存满了任务线程池就会创建新的线程执行任务,对于LinkedBlockingQueue,如果其指定界限,那么和ArrayBlockingQueue区别不大,如果其不指定界限,那么其理论上是可以存储无限量的任务的,实际上能够存储Integer.MAX_VALUE个任务(还是相当于可以存储无限量的任务),此时由于LinkedBlockingQueue是永远无法存满任务的,因而maxPoolSize的设定将没有意义,一般其会设定为和corePoolSize相同的值。
对于SynchronousQueue,其内部是没有任何结构存储任务的,当一个任务添加到该队列时,当前线程和后续添加任务的线程都会被阻塞,直至有一个线程从该队列中取出任务,当前线程才会被释放,因而如果线程池使用了该队列,那么一般corePoolSize都会设计得比较小,maxPoolSize会设计得比较大,因为该队列比较适合大量并且执行时间较短的任务的执行;