再进入主题之前,我们先了解几个概念,对读源码有所帮助,对于线程池的运行状态,有4个级别,分别是RUNNING,SHUTING,STOP,TIDING,TERMINATED
解释如下:

The runState provides the main lifecycle control, taking on values:
*
* RUNNING: Accept new tasks and process queued tasks //能接受新的任务,并且可以运行已经在任务队列中的任务
* SHUTDOWN: Don't accept new tasks, but process queued tasks //不再接受新的任务,但是仍能运行在阻塞队列里等待的任务
* STOP: Don't accept new tasks, don't process queued tasks, //不再接受新的任务,也停止运行队列中的任务
* and interrupt in-progress tasks
* TIDYING: All tasks have terminated, workerCount is zero, //后面的就是停止后的东西了,暂时不需要理解。。
* the thread transitioning to state TIDYING
* will run the terminated() hook method
* TERMINATED: terminated() has completed

 将这几个任务对应着数字,所以可以进行大小比较

     private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;

下面我们正式了解线程池:

线程池的实现有很多,比如

    1. newFixedThreadPool()        //可以指定核心线程个数,并且创建的所有线程都是核心线程
2. newSingleThreadExecutor() //有且只有一个线程,且是核心线程
3. newCachedThreadPool() //没有规定最多可以有多少个线程,但没有一个是核心线程
4. newScheduledThreadPool() //可以指定核心线程的个数,而且可以创建非核心线程(不限数量)

这些线程的创建方法其实最终都引用了一个构造方法:

 public ThreadPoolExecutor(
int corePoolSize, //核心线程个数
int maximumPoolSize, //最大线程个数
long keepAliveTime, //线程空闲多长时间被回收
TimeUnit unit, //时间的单位
BlockingQueue<Runnable> workQueue, //任务队列
ThreadFactory threadFactory, //线程工厂方法(其实就是给线程设置一下属性,可以用它创建线程)
RejectedExecutionHandler handler) //回绝策略

  这其中的线程工厂方法和回绝策略可以自己创建,也可以使用默认的策略,回绝策略我会在待会儿说明

现在我们的关注点就在ThreadPoolExecutor对象上了,因为他负责管理所有线程池里的线程,以及维护阻塞队列
      那么他是怎么维护线程和任务的呢? 我们来看一下这个类的结构

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

它里面有一个Worker类型的hashset,以及一个任务类型的BlockingQueue,
所以我们可以猜到,这个Hashset的workers就是用来存储线程的,而BlockingQueue就是用来存储任务的
我们再仔细了解一下这个Worker:

 private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{ final Thread thread; Runnable firstTask; //创建worker时给的初始任务 volatile long completedTasks; Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask; //创建worker时给worker的初始任务
this.thread = getThreadFactory().newThread(this); //利用线程工厂创建一个新的线程
} public void run() {
runWorker(this);
}
}

这个Worker里面包含着一个Thread,并且Worker本身也是一个任务(继承了Runnable),为什么还要传给他一个初始任务呢?为什么要这么设计呢?
答案其实很简单,我们都知道一个Thread不能被重用的,但是跟据线程池的定义,一个线程却可以在完成一个任务之后又去做其他的任务,这就是源于这个设计,这个Thread一直运行的就是worker这个任务,而没有去运行其他的任务,而worker的run方法里面有一个runworker方法,线程就是通过这个runworker方法去读取初始任务或者任务列表中的任务来运行的

有了这些基础知识后,我们就开始从我们常用的线程流程开始分析,看看线程到底是怎么运作的吧!

我们都知道,运行线程池时要给他提交任务,比如

ExecutorService exec = Executors.newCachedThreadPool(); //创建线程池
exec.execute(Runnable) //提交自己的任务

我们跟踪一下execute方法,他的描述如下

    public void execute(Runnable command) {
if (command == null)
throw new NullPointerException(); //获取线程池状态码
int c = ctl.get(); //workerCountof(c) 能根据状态码运算出线程池核心线程的个数
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); //如果线程池没有停止接受,但是核心线程已经全部被回收(比如cachedThreadpool会发生这种情况)
else if (workerCountOf(recheck) == 0) //添加非核心线程,且这个线程没有初始任务(会让他在任务队列中去取)
addWorker(null, false); //这里隐含了一层意思:这些条件都不满足时,即有核心线程,且队列未满,则什么都不做,仅仅将任务添加到队列
}
//如果核心线程满了,并且任务队列也添加失败了(满了),那么尝试创建非核心线程
else if (!addWorker(command, false)) //如果都失败了,那么启用拒绝策略
reject(command);
}

  

跟据上面的方法,我们知道execute主要就是判断线程池情况,然后决定是直接新建线程来执行任务,或者添加任务到任务列表,其最主要的方法就是addWorker
所以我们再来看一下addWorker是怎么实现的

 private boolean addWorker(Runnable firstTask, boolean core) {
retry:
/***********增加线程池的线程计数*************************/
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); //获取当前运行状态RunStatus 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
}
}
/************创建新线程,并将它加入wokers的hashSet进行管理********************/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
33 final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get()); 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(); //运行线程,对应着worker的run方法,里面调用了runworker(this)方法
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

  由于线程运行后,run方法里调用的是runworker(this)方法,所以我们再来看一看这个方法

  1 final void runWorker(Worker w) {
2 Thread wt = Thread.currentThread();
3 Runnable task = w.firstTask;
4 w.firstTask = null; //运行一次初始任务后,就应该将初始任务放掉,否则会重复运行
5 w.unlock(); // allow interrupts
6 boolean completedAbruptly = true;
7 try {
8 while (task != null || (task = getTask()) != null) {
9 w.lock();
10 if ((runStateAtLeast(ctl.get(), STOP) ||
11 (Thread.interrupted() &&
12 runStateAtLeast(ctl.get(), STOP))) &&
13 !wt.isInterrupted())
14 wt.interrupt();
15 try {
16 beforeExecute(wt, task);
17 Throwable thrown = null;
18 try {
19 task.run(); //运行任务,不是用thread.start,而是直接调用任务的run方法
20 } catch (RuntimeException x) {
21 thrown = x; throw x;
22 } catch (Error x) {
23 thrown = x; throw x;
24 } catch (Throwable x) {
25 thrown = x; throw new Error(x);
26 } finally {
27 afterExecute(task, thrown);
28 }
29 } finally {
30 task = null;
31 w.completedTasks++;
32 w.unlock();
33 }
34 }
35 completedAbruptly = false; //没有任务了,这个线程空闲出来了
36 } finally {
37 processWorkerExit(w, completedAbruptly); //将线程从hashset里面移除
38 }
39 }

  

那么到现在我们已经大致了解了,线程池的运行流程,总结一下就是:

1.创建线程池后,首先会用submit或者execute提交任务
2.任务提交后,会根据线程池的状态利用AddWorker进行线程的创建,并将其添加到线程池中,判断条件如下:
  线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
  线程数量达到了corePoolsize,则将任务移入队列等待
  队列已满,新建线程(非核心线程)执行任务
  队列已满,总线程数又达到了maximumPoolSize,就会采用回绝策略进行处理
3.如果线程添加成功,此时会调用线程的start方法,而线程对应任务的run方法里面,调用的是runworker()方法
4.runworker()方法会在线程的初始任务或者任务队列里面取任务来运行,运行时直接调用被取出任务的run方法
5.运行完成后,如果不能取得新的任务,那么此时线程就是空闲线程,会执行清理策略(跟据空闲时间的长短来清理)

现在问题已经被我们解决了,但是还有一个问题,细心的同学们应该已经发现了:不是说好的线程池里的核心线程可以不被回收吗,但是按照源码的分析,
线程池里所有的线程,只要拿不到任务,过一段时间就会被回收,这是怎么回事呢? 其实原因在于我们忽略掉的getTask方法,我们来看看它的源码

  

 private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out? for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { //如果线程池已经停止接受新线程,且工作队列为空
decrementWorkerCount(); //释放线程
return null; //返回空(使线程拿不到任务)
} int wc = workerCountOf(c); //或操作有false才往后判断
//allowCoreThreadTimeOut表示核心线程也可以回收
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
19 //当允许回收core线程,timed为true
20 //当不允许回收核心线程时,如果当前线程数> 核心线程数 timed=true 否则为false if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
} try {
Runnable r = timed ? //跟据timed的值,选择使用阻塞方法获取队列中的值还是使用非阻塞方法获取
31 workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
32 workQueue.take(); //当核心线程调用这个方法时,会被阻塞,直到获取到任务,而不会返回null,然后被释放
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}

所以,跟据上面的源码,核心线程不被释放的原因也被我们解决了,现在相信你对线程池的了解已经比较深刻了吧

这是博主的第一篇博客,如果写的有问题,希望大家在留言区进行指正,如果转载,请注明出处,尊重作者的辛苦付出,谢谢!

浅谈java线程池实现的更多相关文章

  1. 浅谈Java 线程池原理及使用方式

    一.简介 什么是线程池? 池的概念大家也许都有所听闻,池就是相当于一个容器,里面有许许多多的东西你可以即拿即用.java中有线程池.连接池等等.线程池就是在系统启动或者实例化池时创建一些空闲的线程,等 ...

  2. 浅谈Java线程安全

    浅谈Java线程安全 - - 2019-04-25    17:37:28 线程安全 Java中的线程安全 按照线程安全的安全程序由强至弱来排序,我们可以将Java语言中各种操作共享的数据分为以下五类 ...

  3. 转载【浅谈ThreadPool 线程池】

    浅谈ThreadPool 线程池 http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html

  4. 浅谈ThreadPool 线程池(引用)

    出自:http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html 浅谈ThreadPool 线程池 相关概念: 线程池可以看做容纳线程的 ...

  5. 浅谈 Java线程状态转换及控制

    线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ...

  6. 浅谈ThreadPool 线程池

    本文来自:http://www.cnblogs.com/xugang/archive/2010/04/20/1716042.html 相关概念: 线程池可以看做容纳线程的容器: 一个应用程序最多只能有 ...

  7. 浅谈JAVA线程

    一.线程(Thread) 1.线程 线程:是指程序中的顺序流 多线程:一个程序中的多个顺序流同时执行 (1)线程的状态: 新生 就绪 运行 阻塞 终止 (2)学习多线程: 1)线程的创建 2)线程的状 ...

  8. Java线程池的那些事

    熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...

  9. 干货,阿里P8浅谈对java线程池的理解(面试必备)

    线程池的概念 线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成:ThreadPoolExecutor ...

随机推荐

  1. 手把手教你如何安装Pycharm——靠谱的Pycharm安装详细教程

    今天小编给大家分享如何在本机上下载和安装Pycharm,具体的教程如下: 1.首先去Pycharm官网,或者直接输入网址:http://www.jetbrains.com/pycharm/downlo ...

  2. Servlet知识点总结

    一, ServletAPI中有4个Java包: 1.javax.servlet:其中包含定义Servlet和Servlet容器之间契约的类和接口 2.javax.servlet.http:其中包含定义 ...

  3. Rabbit MQ 延迟插件rabbitmq_delayed_message_exchange的使用

    环境: windows server 2008 R2 rabbitmq 3.7.2 下载插件: http://www.rabbitmq.com/community-plugins.html 注意要下载 ...

  4. PAT1065: A+B and C (64bit)

    1065. A+B and C (64bit) (20) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 HOU, Qiming G ...

  5. 关于java集合类HashMap的理解

    一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap  ...

  6. Java 8 Optional类深度解析(转)

    经常会遇到这样的问题,调用一个方法得到了返回值却不能直接将返回值作为参数去调用别的方法.我们首先要判断这个返回值是否为null,只有在非空的前提下才能将其作为其他方法的参数. 新版本的Java,比如J ...

  7. 分布式任务系统gearman的python实战

    Gearman是一个用来把工作委派给其他机器.分布式的调用更适合做某项工作的机器.并发的做某项工作在多个调用间做负载均衡.或用来在调用其它语言的函数的系统.Gearman是一个分发任务的程序框架,可以 ...

  8. Java 精简Jre jar打包成exe

    #开始 最近几天都在忙一个事情,那就是尝试精简jre,我想不明白为什么甲骨文官方不出exe打包工具... 网络上精简jre的文章很多,但是原创的似乎没几个,绝大多数都是转发同一个博客, 这里借鉴了不少 ...

  9. 集成学习之Boosting —— AdaBoost原理

    集成学习大致可分为两大类:Bagging和Boosting.Bagging一般使用强学习器,其个体学习器之间不存在强依赖关系,容易并行.Boosting则使用弱分类器,其个体学习器之间存在强依赖关系, ...

  10. FPGA学习笔记(三)—— 数字逻辑设计基础(抽象的艺术)

    FPGA设计的是数字逻辑,在开始用HDL设计之前,需要先了解一下基本的数字逻辑设计-- 一门抽象的艺术. 现实世界是一个模拟的世界,有很多模拟量,比如温度,声音······都是模拟信号,通过对模拟信号 ...