java并发编程(十七)----(线程池)java线程池架构和原理
前面我们简单介绍了线程池的使用,但是对于其如何运行我们还不清楚,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;
    }
看起来参数是挺多的,我们不妨耐心看看参数都是什么意思:
corePoolSize:核心池的大小,默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
threadFactory:是构造Thread的方法,你可以自己去包装和传递,主要实现newThread方法即可;
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 {
}
由此我们似乎可以明白他们之间的关系:
- Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable);
 - ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
 - 抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;
 - 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时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。
java并发编程(十七)----(线程池)java线程池架构和原理的更多相关文章
- Java并发编程(您不知道的线程池操作)
		
Java并发编程(您不知道的线程池操作) 这几篇博客,一直在谈线程,设想一下这个场景,如果并发的线程很多,然而每个线程如果执行的时间很多的话,这样的话,就会大量的降低系统的效率.这时候就可以采用线程池 ...
 - java并发编程笔记(七)——线程池
		
java并发编程笔记(七)--线程池 new Thread弊端 每次new Thread新建对象,性能差 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM 缺 ...
 - Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理
		
Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...
 - 【Java并发编程】之二:线程中断
		
[Java并发编程]之二:线程中断 使用interrupt()中断线程  当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一 ...
 - java并发编程笔记(五)——线程安全策略
		
java并发编程笔记(五)--线程安全策略 不可变得对象 不可变对象需要满足的条件 对象创建以后其状态就不能修改 对象所有的域都是final类型 对象是正确创建的(在对象创建期间,this引用没有逸出 ...
 - java并发编程笔记(三)——线程安全性
		
java并发编程笔记(三)--线程安全性 线程安全性:  当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现 ...
 - Java并发编程(十一)-- Java中的锁详解
		
上一章我们已经简要的介绍了Java中的一些锁,本章我们就详细的来说说这些锁. synchronized锁 synchronized锁是什么? synchronized是Java的一个关键字,它能够将代 ...
 - [Java并发编程(五)] Java volatile  的实现原理
		
[Java并发编程(五)] Java volatile 的实现原理 简介 在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 sync ...
 - [Java并发编程(四)] Java volatile  的理论实践
		
[Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...
 - [Java并发编程(三)] Java volatile 关键字介绍
		
[Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...
 
随机推荐
- XTOJ 1267:Highway(树的直径)***
			
http://202.197.224.59/OnlineJudge2/index.php/Problem/read/id/1267 题意:给出一棵树,每条树边有权值,现在要修建n-1条边,边的权值为边 ...
 - Java是如何实现平台无关性的
			
相信对于很多Java开发来说,在刚刚接触Java语言的时候,就听说过Java是一门跨平台的语言,Java是平台无关性的,这也是Java语言可以迅速崛起并风光无限的一个重要原因.那么,到底什么是平台无关 ...
 - C#开发中常用的加密算法总结
			
相信很多人在开发过程中经常会遇到需要对一些重要的信息进行加密处理,今天给大家分享我个人总结的一些加密算法: 常见的加密方式分为可逆和不可逆两种方式 可逆:RSA,AES,DES等 不可逆:常见的MD5 ...
 - Java面试题汇总---基础版(附答案)
			
基于我个人对面试的认知和招聘经验,在此我总结一下Java开发者的基础知识掌握要求,及应聘者面试的需要准备的内容. 首先,Java基础是每个面试官都会问到的,可能只是针对工作经验的多少,对问题追踪深度有 ...
 - UVA10763 交换学生 Foreign Exchange 题解
			
题目链接: https://www.luogu.org/problemnew/show/UVA10763 题目分析: 本题我首先想到的做法是把每一个数都map一下,然后互相判断,例如a,b两人准备交换 ...
 - 记一次java.lang.NoClassDefFoundError异常
			
前阵子做了个评论过滤敏感词的功能,本地测试没有任何问题,然后就部署到线上服务器,通知相关人员线上测试.大约过了十来天,那货和我说接口出问题了,当时一脸懵逼,用了十来天突然出问题了???好吧,出问题了咱 ...
 - Excel催化剂开源第40波-Excel插入图片做到极致的效果
			
不知道是开发人员的自我要求不高还是用户的使用宽容度足够大,在众多Excel插入图片的版本中,都没有考虑到许多的可大幅度提升用户体验的细节处理. Excel催化剂虽然开发水平有限,但也在有限的能力下,尽 ...
 - 了解一下zookeeper,搭建单机版和集群版的环境玩玩,需要手稿的,留下邮箱
			
第一章:Zookeeper介绍 Zookeeper,动物管理员,是用来管理hadoop(大象).Hive(蜜蜂).Pig(小猪)的管理员. Apache Hbase和Apache Solr的分布式集群 ...
 - Flume框架的学习使用
			
Flume框架的学习使用 Flume简介 Flume提供一个分布式的,可靠的,对大数据量的日志进行高效收集.聚集.移动的服务. Flume基于流失架构,容错性强,也很灵活简单 Flume,kafka用 ...
 - C#2.0新增功能01 分布类与分部方法
			
连载目录 [已更新最新开发文章,点击查看详细] 分部类型 拆分一个类.一个结构.一个接口或一个方法的定义到两个或更多的文件中, 每个源文件包含类型或方法定义的一部分,编译应用程序时将把所有部分组 ...