任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。在Java中,Runnable对象代表一个任务,Thread对象负责创建一个线程执行这个任务。

前提:1. 程序需要处理大量任务

   2. 任务的执行时间相对创建线程和销毁的时间较短

方法1:

while (ture) {
  Socket connection = socket.accept();
  handleTask(connection); //单线程处理所用任务

方法2:

while (true) {
  final Socket connection = socket.accept();
  new Thread(() -> {handleTask(connection);}).start(); 每个任务一个线程处理
}

两种方法的缺点:1. 串行执行的问题在于响应慢,在多核处理器上吞吐量小。

        2. 无限创建线程导致资源消耗大,可创建线程数有限制,恶意提交任务会导致程序负载过高。

Java的第3种解决方案是线程池,它是兼顾资源和并发的处理方案。

线程池的关键是任务的执行策略,复用线程

执行策略定义了:

  ~ 任务在哪个线程上运行

  ~ 任务按照什么顺序执行(FIFO, LIFO, 优先级)

  ~ 有多少个任务可以并发执行

  ~ 队列中有多少个任务等待执行

  ~ 系统由于过载而需要拒绝一个任务时,选择哪个任务,如何通过程序有任务被拒绝

  ~ 任务执行前后,应该进行哪些动作。

线程池的优势:

  1. 重用线程,减少了创建和销毁线程的开销,在Window和Linux,Java使用一个线程对应一个轻量级进程的实现。

  2. 某些情况下,任务到达时,如果有空闲线程,可以立即执行任务,而不需要等待创建新线程,提高响应速度。

  3. 线程池的大小可以调节,以便处理器保持忙碌状态,提高效率。

  4. 通过将任务的提交和执行分离,可以根据硬件资源选择最佳的执行策略。

线程池的实现:

  在java.util.concurrent包中,ThreadPoolExecutor是一个线程池实现。

图中的三个接口:

Executor:一个运行新任务的简单接口;public interface Executor {

/**
* 在未来的某个时间执行任务command. 这个任务可能在一个新的线程中执行 ,
* 或者在线程池中的线程执行,或者一个调用线程中执行(即在main中运行)。   
* 取决于线程池的具体实现
* @param 需要执行的任务
*/
void execute(Runnable command);
}

ExecutorService:扩展Executor接口

public interface ExecutorService extends Executor {

    void shutdown(); // 停止接受任务。
List<Runnable> shutdownNow(); // 尝试停止所有活动的任务
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
     throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

ScheduledExecutorService:扩展了ExecutorService。支持Future和定期执行任务。

ThreadPoolExceutor源码

1. 关键field

 // 线程池状态初始化为处于运行状态,线程数为0.
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 储存运行状态和线程数(刚创建的线程处理RUNNING,0个活动线程。
private static final int COUNT_BITS = Integer.SIZE - 3; // Integer.SIZE = 32 COUNT_BITS = 29
// 线程池的线程数
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 0x000 1111 1111 1111 1111 1111 1111 1111 // 运行状态储存在高位中
private static final int RUNNING = -1 << COUNT_BITS; // 0x111 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS; // 0x000 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS; // 0x001 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS; // 0x0100 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS; // 0x011 0000 0000 0000 0000 0000 0000 0000
 private static int runStateOf(int c)     { return c & ~CAPACITY; } // 获取运行状态
private static int workerCountOf(int c) { return c & CAPACITY; } // 获取线程数
private static int ctlOf(int rs, int wc) { return rs | wc; } // 获取运行状态和线程数

线程池的五种状态:

RUNNING: 运行状态,接受新提交的任务,并且可以处理队列中的任务。

SHUTDOWN: 关闭状态,不再接受新提交的任务,可以处理队列中的任务。在线程池处于

       RUNNING状态时,调用shutdown()方法会使线程池进入到此状态。

STOP:停止状态,不再接受新提交的任务,也不处理队列中的任务,并且中断运行中的任务。

       在RUNNING或SHUTDOWN状态时,调用shutdownNow()方法使线程池进入到该状态。

TIDYING: 所有任务都己终止,workerCount为0, 转换到TIDYING状态的线程池将运行terminate()方法。

TERMINATE: 终止状态,terminated()方法调用后进入此状态。

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

workQueue用于保存任务

workers用于保存工作线程

threadFactory用于生产线程。

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.acc = System.getSecurityManager() == null ?
null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

corePoolSize: 线程池核心线程数量,作用:

  * 当新任务在execute()提交时,根据corePoolSize去判断任务如何执行:

    -> 如果线程池中线程小于corePoolSize,创建新的线程处理此任务,即使线程池中有空闲线程

    -> 如果线程池中线程数大于等于corePoolSize且workQueue未满时,任务添加到workQueue中。

    -> 如果线程池中线程数大于等于corePoolSize且workQueue己满时,创建新线程处理任务。

    -> 如果线程池中线程数大于等于maximunPoolSize,且workQueue已满时,通过指定的handler策略处理任务(有四种已定义的策略)

maximumPoolSize:最大线程数量

workQueue:未处理的任务队列

keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,多余的线程不会立即销毁,而是会等待,

        直到等待的时间超过了 keepAliveTime

threadFactory:用于创建新线程,通过ThreadFactory可以

handler:当线程池的的线程数等于maximumPoolSize且workQueue己满时,使用handler处理新提交的线程

execute方法,实现执行任务的逻辑。

public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 分3步处理任务:
* 1.线程数小于corePoolSize时,创建新线程执行此任务。
* addWorker 方法自动检查runState和workerCount
* addWorker因为不能添加线程时,返回false
*
* 2. 如果可以添加到队列, 我们仍要再次检查线程池的状态
      * (因为线程池中可能没有线程 或者在进入此方法时,线程池被关闭了。
* 如果线程池不处于RUNNING,清除刚才添加的任务
      * 如果处于RUNNING且workerCount=0,创建新线程。
      *
* 3.如果不能将任务添加到队列, 就尝试创建一个新的线程。如果创建失败,拒绝任务
*/
int c = ctl.get(); // 获取线程池的运行状态和线程数
if (workerCountOf(c) < corePoolSize) { // 如果线程数小于corePoolSize时
       // 第二个参数表示,限制添加线程的数量由谁决定
       // true 由corePoolSize
       // false 由maximumPoolSize
if (addWorker(command, true))
return; // 添加成功
c = ctl.get(); // 添加线程失败时,重新获取运行状态和线程数
}
     // 线程池处于RUNNING状态且任务成功添加到workQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get(); // 重新获取运行状态和线程数,
        // 重新判断运行状态,如果不是处于运行状态,移除上面调用workQueue.offer(command)时,添加的任务。
// 并且拒绝此任务
        if (! isRunning(recheck) && remove(command))
reject(command);
        // 如果处于运行状态,且线程数为0时
else if (workerCountOf(recheck) == 0)
addWorker(null, false); // 在线程中添加线程去执行此任务。
}
     // 如果线程池不是运行状态或者添加任务失败,且创建线程的失败时,
else if (!addWorker(command, false))
reject(command); // 拒绝此任务
}

 addWrok方法,实现增加线程的逻辑

  检查是否可以根据当前线程池状态和给定边界(核心或最大线程数)添加新工作线程。 如果是,则相应地调整工作线程的计数,并且如果可能,创建并启动新工作程序,将firstTask作为其第一个任务运行。 如果线程池已停止或可以关闭,则此方法返回false。 如果线程工厂在询问时无法创建线程,它也会返回false。 如果线程创建失败,或者由于线程工厂返回null,或者由于异常(通常是Thread.start()中的OutOfMemoryError)会回滚

   private boolean addWorker(Runnable firstTask, boolean core) {     
     retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); //获取运行状态     // 运行状态为RUNNING时,可以执行任务,跳出if
       // 处于SHUTDOWN状态,fisrtTask为null,workQueue不为空时,跳出if
if (rs >= SHUTDOWN && // 不处于RUNNING,继续判断,否则跳出if
! (rs == SHUTDOWN && // 如果处于SHUTDOWN状态,继续判断,否则返回false
firstTask == null && // fisrtTask为空,继续判断,否则返回false
! workQueue.isEmpty())) // workQueue为空时,返回false,否则跳出if
return false; for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY || // 工作线程数大于CAPACITY时,返回false
wc >= (core ? corePoolSize : maximumPoolSize)) // 小于CAPACITY,大于core或max时,返回false
return false;
if (compareAndIncrementWorkerCount(c)) // 如果添加线程成功,跳出第一个for循环
break retry;
c = ctl.get(); // Re-read ctl 添加失败,重新读取状态
if (runStateOf(c) != rs) // 不是RUNNING状态, 重新跑到第一个for循环。
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
} boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask); // 用firstTask创建一个新的Worker
final Thread t = w.thread; // Worker利用ThreadFactory创建一个线程对象储存在其实例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());
            // 如果处于运行状态,或者 处于关闭状态但任务为null时
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // 判断线程是否处于已经调用start(),如果是,抛出异常
throw new IllegalThreadStateException();
workers.add(w); // 将工作线程添加到Hashset中
int s = workers.size();
if (s > largestPoolSize) // 记录线程池中出现过最大的线程数
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); // 启动线程, t.start()调用的是Worker的run方法,见worker的构造器。
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

线程池如何复用一个线程-- ThreadPoolExecutor的实现(未完)的更多相关文章

  1. 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法

    [源码下载] 重新想象 Windows 8 Store Apps (42) - 多线程之线程池: 延迟执行, 周期执行, 在线程池中找一个线程去执行指定的方法 作者:webabcd 介绍重新想象 Wi ...

  2. java线程池技术(二): 核心ThreadPoolExecutor介绍

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程池技术属于比较"古老"而又比较基础的技术了,本篇博客主要作用是个人技术梳理,没什么新玩意. 一.Java线程池技术的 ...

  3. 手写一个线程池,带你学习ThreadPoolExecutor线程池实现原理

    摘要:从手写线程池开始,逐步的分析这些代码在Java的线程池中是如何实现的. 本文分享自华为云社区<手写线程池,对照学习ThreadPoolExecutor线程池实现原理!>,作者:小傅哥 ...

  4. Java线程池实现原理与技术(ThreadPoolExecutor、Executors)

    本文将通过实现一个简易的线程池理解线程池的原理,以及介绍JDK中自带的线程池ThreadPoolExecutor和Executor框架. 1.无限制线程的缺陷 多线程的软件设计方法确实可以最大限度地发 ...

  5. 并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  6. 发一个可伸缩线程池大小的python线程池。已通过测试。

    发一个可伸缩线程池大小的线程池. 当任务不多时候,不开那么多线程,当任务多的时候开更多线程.当长时间没任务时候,将线程数量减小到一定数量. java的Threadpoolexcutor可以这样,py的 ...

  7. 003-多线程-JUC线程池-几种特殊的ThreadPoolExecutor【newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool】

    一.概述 在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池: 以下方法是Executors下的静态方法,Ex ...

  8. 线程池中状态与线程数的设计分析(ThreadPoolExecutor中ctl变量)

    目录 预备知识 源码分析 submit()源码分析 shutdownNow()源码分析 代码输出 设计目的与优点 预备知识 可以先看下我的另一篇文章对于Java中的位掩码BitMask的解释. 1.一 ...

  9. 【Java线程池】 java.util.concurrent.ThreadPoolExecutor 分析

    线程池概述 线程池,是指管理一组同构工作线程的资源池. 线程池在工作队列(Work Queue)中保存了所有等待执行的任务.工作者线程(Work Thread)会从工作队列中获取一个任务并执行,然后返 ...

随机推荐

  1. Gym 101908C - Pizza Cutter - [树状数组]

    题目链接:https://codeforces.com/gym/101908/problem/C 题意: 一块正方形披萨,有 $H$ 刀是横切的,$V$ 刀是竖切的,不存在大于等于三条直线交于一点.求 ...

  2. python 科学计算与可视化

    一.Numpy 库 NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的数学函数库. 引用: import ...

  3. kettle 通用的数据库迁移流程

    需求: 1.你是否遇到了需要将mysql数据库中的所有表与数据迁移到Oracle. 2.你是否还在使用kettle重复的画着:表输入-表输出.创建表,而烦恼. 下面为你实现了一套通用的数据库迁移流程. ...

  4. 描述逻辑(DL)基础知识

    Logic逻辑理论实际上是一个规范性(normative)的理论,而不是一个描述性的(descriptive)理论.即,它并不是用来描述人类究竟是采用何种的形式来推理的,而是来研究人类应该如何有效的进 ...

  5. 理解JS原型和原型链

    本文通过对<JavaScript高级程序设计>第六章的理解,加上自己的理解,重组了部分内容,形成下面的文字. 理解了原型这个概念,你的JS世界会清明很多. 为什么要为JS创造原型这个概念 ...

  6. Centos7安装并配置mysql5.6

    1.下载安装包:https://pan.baidu.com/s/18xAumOggjm9bu9Wty6kYjg 2.卸载系统自带的Mariadb 2.1查询已安装的mariadb [root@loca ...

  7. HDU 2544最短路 【dijkstra 链式前向星+优先队列优化】

    最开始学最短路的时候只会用map二维数组存图,那个时候还不知道这就是矩阵存图,也不懂得效率怎么样 经过几个月的历练再回头看最短路的题, 发现图可以用链式前向星来存, 链式前向星的效率是比较高的.对于查 ...

  8. SpringMVC中映射路径的用法之请求限制、命名空间

    SpringMVC中映射路径的请求限制 什么是SpringMVC请求限制? 在SpringMVC中,支持对请求的设置.如果不满足限制条件的话,就不让请求访问执行方法,这样可以大大提高执行方法 的安全性 ...

  9. MySQL使用root权限创建用户并授权

    MySql篇 1.下载并安装Mysql (1)下载地址 MySQL-8.0下载地址 (2)Mysql配置 1.home目录下命令行执行:vi    .bash_profile来配置MySql绝对路径 ...

  10. Python多线程下存在_strptime的问题

    由于Python的datetime和time中的_strptime方法不支持多线程,运行时会报错:AttributeError: _strptime code: # -*- coding:utf-8 ...