Java线程池复用线程的秘密你知道吗

 更新时间:2022年03月07日 10:58:09   作者:Maybe_9527  
这篇文章主要为大家详细介绍了Java线程池复用线程的秘密,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望您能够多多关注

前言

我们都知道线程池可以帮我们管理线程,重复利用线程执行不同的任务。正常情况下,我们创建的线程执行完任务后就会自行销毁,那么线程池是如何做到复用线程的呢?

源码探究

我们从线程池ThreadPoolExecutor源码入手,一探究竟。为了突出重点,以下的方法源码过滤了部分无关代码,以求逻辑清晰。

execute方法

那就从线程池执行的execute方法入手吧!来看一下方法的源码

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();        
        int c = ctl.get();
        //1.小于核心线程数时,创建线程
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //2.达到核心线程数,不超过队列界限时,添加到队列
        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);
        }
        //3.队列已满,不超过最大线程数时,创建线程
        else if (!addWorker(command, false))
        //4.达到最大线程数时,执行拒绝策略
            reject(command);
    }

线程池执行的4个步骤相信大家已经有所了解,这里我们只看添加线程的方法addWorker()

addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	//1.创建Worker,传入任务
            w = new Worker(firstTask);
            //2.取出执行任务的线程
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {                    
                    int c = ctl.get();
                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        workerAdded = true;
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                	//3.执行线程
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

参数解释:

core:true表示添加的是核心线程,false表示添加的非核心线程

这里大家只需要关心这3行加注释的代码就可以了

就是Worker管理了创建的线程和这个线程执行的第一个任务,并且在addWorker方法中调用线程的start方法,开启线程执行了任务。下面我们看看Worker这个类

Worker类

实现了Runnable接口

Worker 是线程池ThreadPoolExecutor的内部类,实现了Runnable接口

private final class Worker    extends AbstractQueuedSynchronizer    implements Runnable

重要属性

他有两个核心关键的属性,即封装了线程池的线程和要执行的任务,达到了线程和任务解耦的目的。

final Thread thread;
Runnable firstTask;

构造方法

addWorker方法会执行创建一个worker

w = new Worker(firstTask);

看一下Worker的构造方法

Worker(Runnable firstTask) {
	this.firstTask = firstTask;    		
	this.thread = getThreadFactory().newThread(this);
}

可以看到 新创建的Worker本身也是一个Runnable,他的thread传的runnable任务就是worker本身

在addWorker方法,最终会取到worker的thread属性,然后启动这个thread

w = new Worker(firstTask);
final Thread t = w.thread;
... ...
t.start();

run方法

刚才介绍过,worker的thread的runnable参数传的就是worker本身,就是调的worker的run方法,现在我们来看最核心的worker的run方法

public void run() { 
	runWorker(this);
}

调的是ThreadPoolExecutor的runWorker方法

final void runWorker(Worker w) {
    Runnable task = w.firstTask;
    w.firstTask = null;
    try { 
        while (task != null || (task = getTask()) != null) 
        {           
            ...           
            task.run();
            ...

可以看到runWorker核心是一个while循环,执行了第一个task之后,就不停的从队列中取任务,直到没有任务了才会执行完,销毁线程

执行流程

execute方法调用addWorker方法,并且执行worker.thread.start()开启线程

​ ——》worker.thread 执行worker本身的run方法(worker实现了Runnable接口)

​ ——》执行ThreadPoolExecutor的runWorker方法,是个while循环,执行完worker本身的第一个任务之后,就不停从队列取任务,直到没有任务,执行完,退出循环,销毁

总结

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。

在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对 Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停的检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式将只使用固定的线程就将所有任务的 run 方法串联起来。

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!             

相关文章

  • Java内部类和异常类的概念以及使用

    Java内部类和异常类的概念以及使用

    这篇文章主要介绍了Java内部类和异常类的概念以及使用,文中有非常详细的代码以及注释,适合正在学习java基础的同学们使用,需要的朋友可以参考下
    2021-04-04
  • 一个Java中BigDecimal的问题记录

    一个Java中BigDecimal的问题记录

    这篇文章主要给大家介绍了关于Java中一个BigDecimal问题的相关资料,通过文中介绍的方法可以很方便的解决BigDecimal进行计算的时候不管怎么计算,最后得到的值都没有变化的问题,需要的朋友可以参考下
    2021-11-11
  • Java并发线程之线程池的知识总结

    Java并发线程之线程池的知识总结

    这篇文章主要介绍了Java并发线程之线程池的知识总结,帮助大家更好的理解和学习Java并发线程的相关内容,感兴趣的朋友可以了解下
    2021-01-01
  • Ireport的安装与使用教程

    Ireport的安装与使用教程

    这篇文章主要介绍了Ireport的安装与使用教程,需要的朋友可以参考下
    2021-10-10
  • springboot整合mqtt客户端示例分享

    springboot整合mqtt客户端示例分享

    这篇文章主要介绍了springboot整合mqtt客户端示例分享的相关资料,需要的朋友可以参考下
    2023-07-07
  • Java8 日期、时间操作代码

    Java8 日期、时间操作代码

    在Java8之前,日期时间API一直被开发者诟病,包括:java.util.Date是可变类型,SimpleDateFormat非线程安全等问题。故此,Java8引入了一套全新的日期时间处理API,新的API基于ISO标准日历系统
    2021-09-09
  • SVN报错:Error Updating changes:svn:E155037的解决方案

    SVN报错:Error Updating changes:svn:E155037的解决方案

    今天小编就为大家分享一篇关于SVN报错:Error Updating changes:svn:E155037的解决方案,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • java环境变量path和classpath的配置

    java环境变量path和classpath的配置

    这篇文章主要为大家详细介绍了java系统环境变量path和classpath的配置过程,感兴趣的小伙伴们可以参考一下
    2016-07-07
  • 简单易懂的Java Map数据添加指南

    简单易懂的Java Map数据添加指南

    Java提供了多种方法来往Map中添加数据,开发者可以根据具体需求选择合适的方法,需要的朋友可以参考下
    2023-11-11
  • springboot 实现bean手动注入操作

    springboot 实现bean手动注入操作

    这篇文章主要介绍了springboot 实现bean手动注入操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01

最新评论