java并发编程(十七)----(线程池)java线程池架构和原理

来源:互联网 发布:淘宝怎么发链接充话费 编辑:程序博客网 时间:2024/06/07 03:35

前面我们简单介绍了线程池的使用,但是对于其如何运行我们还不清楚,Executors为我们提供了简单的线程工厂类,但是我们知道ThreadPoolExecutor是线程池的具体实现类。我们先从他开始分析。

1. ThreadPoolExecutor初探

ThreadPoolExecutor一共有3个构造方法,我们来看一下其中看起来比较复杂的这个:

public ThreadPoolExecutor(int corePoolSize,                              int maximumPoolSize,                              long keepAliveTime,                              TimeUnit unit,                              BlockingQueue<Runnable> workQueue,                              ThreadFactory threadFactory,                              RejectedExecutionHandler handler) {        if (corePoolSize < 0 ||            maximumPoolSize <= 0 ||            maximumPoolSize < corePoolSize ||            keepAliveTime < 0)            throw new IllegalArgumentException();        if (workQueue == null || threadFactory == null || handler == null)            throw new NullPointerException();        this.corePoolSize = corePoolSize;        this.maximumPoolSize = maximumPoolSize;        this.workQueue = workQueue;        this.keepAliveTime = unit.toNanos(keepAliveTime);        this.threadFactory = threadFactory;        this.handler = handler;    }

看起来参数是挺多的,我们不妨耐心看看参数都是什么意思:

  1. corePoolSize:核心池的大小,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  2. maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;

  3. keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。

  4. unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    TimeUnit.DAYS;               //天TimeUnit.HOURS;             //小时TimeUnit.MINUTES;           //分钟TimeUnit.SECONDS;           //秒TimeUnit.MILLISECONDS;      //毫秒TimeUnit.MICROSECONDS;      //微妙TimeUnit.NANOSECONDS;       //纳秒
  5. workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

    ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;
  6. threadFactory:是构造Thread的方法,你可以自己去包装和传递,主要实现newThread方法即可;

  7. handler:表示当拒绝处理任务时的策略,也就是参数maximumPoolSize达到后丢弃处理的方法,java提供了4种丢弃处理的方法,当然你也可以自己根据实际情况去重写,主要是要实现接口:RejectedExecutionHandler中的方法: public void rejectedExecution(Runnabler, ThreadPoolExecutor e) java默认的是使用:AbortPolicy,他的作用是当出现这中情况的时候会抛出一个异常;有以下四种取值:

    ①ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

    ②ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

    ③ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

    ④ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

上面说了ThreadPoolExecutor的构造方法,我们继续看他的类的关系:

public class ThreadPoolExecutor extends AbstractExecutorService {}

由源码我们看出ThreadPoolExecutor继承了AbstractExecutorService类,我们知道AbstractExecutorService是一个抽象类,它实现了ExecutorService接口。AbstractExecutorService存在的目的是为ExecutorService中的函数接口提供了默认实现。

public abstract class AbstractExecutorService implements ExecutorService {}

由上我们知道AbstractExecutorService又实现了ExecutorService接口,而ExecutorService是Executor实现类的最直接接口。

public interface ExecutorService extends Executor {}

由此我们似乎可以明白他们之间的关系:

  1. Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable);
  2. ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
  3. 抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
  4. ThreadPoolExecutor继承了类AbstractExecutorService,成为线程池的具体实现类。

2. 线程池的实现

上面我们从ThreadPoolExecutor的构造方法出发提到了线程池的状态,执行,初始化,排队策略等等,下面我们就从这些方面入手,看看线程池的原理。

线程池初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务(execute或者submit)之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

  • prestartCoreThread():初始化一个核心线程
  • prestartAllCoreThreads():初始化所有核心线程

下面是这两个方法的实现:

public boolean prestartCoreThread() {        return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true);}
-----------------------------------------------------
public int prestartAllCoreThreads() {        int n = 0;        while (addWorker(null, true))            ++n;        return n;}
-----------------------------------------------------

我们注意到上面两个方法都调用了addWorker方法,我们看一下实现:

private boolean addWorker(Runnable firstTask, boolean core) {       retry:       for (;;) {           int c = ctl.get();           int rs = runStateOf(c);           // Check if queue empty only if necessary.           if (rs >= SHUTDOWN &&               ! (rs == SHUTDOWN &&                  firstTask == null &&                  ! workQueue.isEmpty()))               return false;           for (;;) {               int wc = workerCountOf(c);               if (wc >= CAPACITY ||                   wc >= (core ? corePoolSize : maximumPoolSize))                   return false;               if (compareAndIncrementWorkerCount(c))                   break retry;               c = ctl.get();  // Re-read ctl               if (runStateOf(c) != rs)                   continue retry;               // else CAS failed due to workerCount change; retry inner loop           }       }       boolean workerStarted = false;       boolean workerAdded = false;       Worker w = null;       try {           final ReentrantLock mainLock = this.mainLock;           w = new Worker(firstTask);           final Thread t = w.thread;           if (t != null) {               mainLock.lock();               try {                   // Recheck while holding lock.                   // Back out on ThreadFactory failure or if                   // shut down before lock acquired.                   int c = ctl.get();                   int rs = runStateOf(c);                   if (rs < SHUTDOWN ||                       (rs == SHUTDOWN && firstTask == null)) {                       if (t.isAlive()) // precheck that t is startable                           throw new IllegalThreadStateException();                       workers.add(w);                       int s = workers.size();                       if (s > largestPoolSize)                           largestPoolSize = s;                       workerAdded = true;                   }               } finally {                   mainLock.unlock();               }               if (workerAdded) {                   t.start();                   workerStarted = true;               }           }       } finally {           if (! workerStarted)               addWorkerFailed(w);       }       return workerStarted;   }

这个方法还是挺好理解:上面的retry是对当前线程池状态进行检查,如果当前线程池未初始化或者未分配则返回false;

往下是初始化firstTask,我们看到在56行把初始化的firstTask加入workers集合,该集合定义为:

 private final HashSet<Worker> workers = new HashSet<Worker>();

集合中包含当前所有的工作线程。
看完addWorker的实现,那么上面的prestartCoreThread和prestartAllCoreThreads我们就很好理解,前一个是向当前工作线程池中加入一个工作线程,后一个是循环N次。

线程池的执行

通常你得到线程池后,会调用其中的:submit方法或execute方法去操作;其实你会发现,submit方法最终会调用execute方法来进行操作,只是他提供了一个Future来托管返回值的处理而已,当你调用需要有返回值的信息时,你用它来处理是比较好的;这个Future会包装对Callable信息,并定义一个Sync对象(),当你发生读取返回值的操作的时候,会通过Sync对象进入锁,直到有返回值的数据通知。

我们先看一下submit方法的源码:

public Future<?> submit(Runnable task) {       if (task == null) throw new NullPointerException();       RunnableFuture<Void> ftask = newTaskFor(task, null);       execute(ftask);       return ftask;   }

我们看到在源码的第4行实际上是调用了execute()方法来处理包装的RunnableFuture。下面是execute方法的源码:

public void execute(Runnable command) {    if (command == null)        throw new NullPointerException();    int c = ctl.get();    if (workerCountOf(c) < corePoolSize) {        if (addWorker(command, true))            return;        c = ctl.get();    }    if (isRunning(c) && workQueue.offer(command)) {        int recheck = ctl.get();        if (! isRunning(recheck) && remove(command))            reject(command);        else if (workerCountOf(recheck) == 0)            addWorker(null, false);    }    else if (!addWorker(command, false))        reject(command);}

第一个if为非空判断;

第二个if中的workerCountOf()方法拿到ctl中存储的当前线程总数,如果小于corePoolSize,那么就会走到addWorker()方法中,如果成功创建了Worker的话,那么返回true,直接return,否则重新通过cas拿一次c;

第三个if中判断当前的线程池是否处于RUNNING状态,如果是,并且workQueue.offer加入队列成功话,那么就重新拿出来一次ctl,再判断如果加入队列之后,线程池如果不是处于RUNNING的状态,并且从队列中remove成功的话,那么就会执行reject操作;判断当前线程数是否为0,如果为0的话,那么就调用addWorker(null,false),否则如果非Running状态或者加入队列失败的话,那么就会调用addWorker(command,false)如果返回false,说明没有添加成功,就会执行reject操作。

任务缓存队列

我们还记得ThreadPoolExecutor的构造函数中有一个参数workQueue,它用来存放等待执行的任务。
workQueue的类型为BlockingQueue,通常可以取下面三种类型:

1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
线程池容量的动态调整

ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize()

setCorePoolSize:设置核心池大小

setMaximumPoolSize:设置线程池最大能创建的线程数目大小

当上述参数从小变大时,ThreadPoolExecutor进行线程赋值,还可能立即创建新的线程来执行任务。

下面我们看一个小例子:

public class ThreadPoolExecutorTest {    private static int produceTaskSleepTime = 2;    private static int produceTaskMaxNumber = 10;    public static void main(String[] args) {        // 构造一个线程池        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),                new ThreadPoolExecutor.DiscardOldestPolicy());        for (int i = 1; i <= produceTaskMaxNumber; i++) {            try {                String task = "task-- " + i;                System.out.println("创建任务并提交到线程池中:" + task);                threadPool.execute(new ThreadPoolTask(task));                System.out.println("线程池中线程数目:"+threadPool.getPoolSize()+",队列中等待执行的任务数目:"+                       threadPool.getQueue().size()+",已执行完毕的任务数目:"+threadPool.getCompletedTaskCount());                Thread.sleep(produceTaskSleepTime);            } catch (Exception e) {                e.printStackTrace();            }        }    }}class ThreadPoolTask implements Runnable, Serializable {    private Object attachData;    ThreadPoolTask(Object tasks) {        this.attachData = tasks;    }    public void run() {        System.out.println("开始执行任务:" + attachData);        attachData = null;    }    public Object getTask() {        return this.attachData;    }}

结果为:

创建任务并提交到线程池中:task-- 1开始执行任务:task-- 1线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完毕的任务数目:0创建任务并提交到线程池中:task-- 2线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完毕的任务数目:1开始执行任务:task-- 2创建任务并提交到线程池中:task-- 3线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:2开始执行任务:task-- 3创建任务并提交到线程池中:task-- 4线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:3开始执行任务:task-- 4创建任务并提交到线程池中:task-- 5线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:4开始执行任务:task-- 5创建任务并提交到线程池中:task-- 6线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:5开始执行任务:task-- 6创建任务并提交到线程池中:task-- 7线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:6开始执行任务:task-- 7创建任务并提交到线程池中:task-- 8线程池中线程数目:2,队列中等待执行的任务数目:1,已执行完毕的任务数目:7开始执行任务:task-- 8创建任务并提交到线程池中:task-- 9开始执行任务:task-- 9线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完毕的任务数目:8创建任务并提交到线程池中:task-- 10开始执行任务:task-- 10线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完毕的任务数目:9

由结果我们可以看到当线程池中线程的数目大于2时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 亚马逊被移除销售权该怎么办 玉米煮水不够熟吃了怎么办 华为云手机找回关了找回手机怎么办 我的淘宝号被别人盗用开店怎么办 淘宝买东西发货了不想要了怎么办 两张单号一样罚款已被处理怎么办 58同城登录电话换了怎么办 在超市买的东西坏了怎么办 安卓系统无法安装第三方软件怎么办 金立手机报毒不能安装软件怎么办 js中下拉菜单被图片挡住怎么办 更换主机后用户名跟密码忘了怎么办 换手机号了京东账号登录不上怎么办 口令卡绑定时访问页面找不回怎么办 手机淘宝购物车不小心删除了怎么办 上淘宝网图片文字不清晰怎么办 淘宝店铺低消费人群占比多怎么办 微信只能在应用商城里面打开怎么办 淘宝买了特价商品店家不发货怎么办 红米5a装不下卡怎么办 红米3s流量太慢怎么办 红米3s触屏失灵怎么办 红米note的4g信号差怎么办 电信4g网速慢怎么办红米手机 红米3电信4g信号不好怎么办 红米note3无法连接4g怎么办 红米2a手机开不了机怎么办 红米2a不支持微信运动怎么办 红米2a开不了机怎么办 红米2a突然开不了机怎么办 苹果手机刷机刷到一半没电了怎么办 红米5手机死屏了怎么办 小米2a手机开不了机怎么办 小米2a长时间没用开不了机怎么办 红米手机玩游戏太卡怎么办 红米2a无限重启怎么办 红米3s像素好差怎么办 红米4x后摄像头进水了怎么办 红米手机卡死了又不能拆电池怎么办 红米1s格式化输入法没了怎么办 红米5a锁屏密码怎么办