天增的博客
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
  • Java并发工具包
  • 并发基础
    • 线程基础
      • Thread的状态
      • 进程与线程
      • 正确停止线程的方式
      • Thread的实现方式
      • waitnotifynotifyAll
      • 生产者消费者模型
    • 线程安全
      • 线程不安全
      • 线程安全
      • 需要注意线程安全问题的情况
  • 并发工具
    • 线程协作
      • Semaphore信号量
      • CountDownLatch详解
      • 使用CompletableFuture解决旅游平台问题
      • 使用CyclicBarrier解决团建问题
    • Future
      • Future主要功能
      • FutureTask源码分析
    • ThreadLocal
      • ThreadLocal内存泄漏
      • ThreadLocal使用场景
    • 原子类
      • 原子类的作用概览
      • 原子类的性能分析
    • 阻塞队列
      • 常见的阻塞队列
      • 阻塞队列的常用方法
      • 什么是阻塞队列
    • 并发容器
      • HashMap
      • CopyOnWriteArrayList
      • ConcurrentHashMap详解
    • 线程池
      • 为什么多线程会带来性能问题
      • 线程池的优势
      • 创建线程池的参数
        • 如何设置线程数
      • 线程池线程复用原理
        • 实现方式
        • 总结
      • ForkJoin框架
    • 各种锁
      • 锁的种类和特点
        • 公平锁非公平锁
        • 自旋锁非自旋锁
        • 共享锁独占锁
        • 乐观锁和悲观锁
      • JVM锁优化
      • synchronized和Lock的对比
      • lock的常用方法
  • 底层原理
    • CAS原理
    • AQS框架
    • 伪共享
    • java内存模型
      • Java内存模型介绍
      • happens-before规则
  • topic
  • Java并发工具包
  • 并发工具
  • 线程池
  • 线程池线程复用原理
2022-04-21
目录

线程池线程复用原理

# 线程池线程复用原理

线程池最大的优势就在于可以复用线程,以减少创建和销毁时带来的消耗。线程池运行一堆固定数量的任务,需要的线程数远小于任务的数量,精髓就在于线程复用,让同一个线程去执行不同的任务。

线程池创建线程的时机

依旧是这张图,我们能从中可以看到,有三个关键的地方

  1. 当前线程数小于核心线程数创建线程
  2. 核心线程数已满,就往任务队列里面塞
  3. 任务队列和核心线程都满了,就创建非核心线程用于分摊压力
  4. 当前三个都无法塞入的时候,拒绝执行

具体执行的代码在 ThreadPoolExecutor的execute中。

# 实现方式

来看看是怎么实现的。(最核心的代码)

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
  			// ctl属性的高3位,提供了线程池的运行状态,包含线程池主要生命周期。
  			// 剩余位记录线程池线程个数
        int c = ctl.get();
  		  // 如果工作线程的数量小于核心线程数 (步骤1)
        if (workerCountOf(c) < corePoolSize) {
          	// 如果核心线程没有满,创建核心线程执行任务,如果返回false说明在创建核心线程的时候线程数已经满了
            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)
              	// 如果检查下来运行的线程数量为0,就调用addWorker创建新的线程
                addWorker(null, false);
        }
  			// 线程池关闭,或者队列已经满了,就去判断最大线程数是否满了,步骤3
        else if (!addWorker(command, false))
          	// 最大线程数满了,执行拒绝策略
            reject(command);
    }

线程复用的秘密就是在这个addWorker里面。

private boolean addWorker(Runnable firstTask, boolean core) {
	/**
	 * 跳过上面一大段检查队列的直接看启动
	 */
   			boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
          	..... 
            if (t != null) {
                // 加锁
                if (workerAdded) {
                  	// 启动,
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
}

通过内置的Worker对象,把自己的firstTask作为任务封装进去,重写了run方法,所以在调用start方法的时候会调用的worker的run方法

     public void run() {
            runWorker(this);
        }
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
          	// 加个死循环,不断的从队列中获取任务,并执行----这里的task才是我们要执行的业务代码
            while (task != null || (task = getTask()) != null) {
                w.lock();
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    try {
                        task.run();
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

# 总结

复用的本质,就是将我们的Runable给封装起来,封装成一个个task,塞入到队列中。

然后使用worker线程,不断的轮训这个任务队列,直接执行,采用了代理模式,增强了原本runable的执行逻辑。

最近更新
01
以 root 身份启动 transmission-daemon
12-13
02
Debian系统安装qbittorrent-nox
12-09
03
LXC Debain12安装zerotier并实现局域网自动nat转发
07-29
更多文章>
Theme by Vdoing | Copyright © 2015-2024 天增 | 苏ICP备16037388号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式