Java的线程池是怎么实现复用的?

Java的线程池,维护了一个线程池,每一个新的任务都会提交到线程池,由线程池进行调度和资源释放,这样的好处:

1.通过线程池,可以限制线程创建的数量,当创建许多的任务时,任务需要在线程池中进行排队。

2.通过线程池,统一管理任务调度以及异常的策略处理。

Java的四种线程池

  Java提供了这些线程池,不同的线程池,使用的场景不同,这里重点不是介绍各种线程池的特点,而是Java的线程池是怎么设计的,如何实现重用。Java通过Executors提供四种线程池,分别为:

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
线程池简单例子
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*线程池Demo*/
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // <1> 创建了一个FixedThreadPool线程池,指定线程池的大小为10
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        // <2> 循环生成20个线程任务,提交到FixedThreadPool线程池中
        /*线程池,打印线程名字和i的值*/
        for (int i = 0;i < 20;i++){
            int finalI = i;
            // <3> 任务中主要打印了线程的name和i的值。
            executorService.execute(()-> System.out.println(Thread.currentThread().getName()+"->" + finalI));
        }
        executorService.shutdown();
    }
}

上面的例子主要做了这几件事:

  • 1.创建了一个FixedThreadPool线程池,指定线程池的大小为10
  • 2.循环生成20个线程任务,提交到FixedThreadPool线程池中
  • 3.任务中主要打印了线程的name和i的值。

运行例子的执行结果如下图:
在这里插入图片描述
  有这个结果可以看pool-1-tread-*这个就是线程池中线程的名字,因为我们例子中创建了20个任务,而线程池中执行任务的线程我们只定义了10个,所以如运行的结果所示,部分线程池中的线程复用了。这就是线程池的特性,可以实现线程的复用,不用为每一个线程任务单独创建线程。

线程池是怎么复用的了

  其实根据上面的运行结果我们可以进行一下推测,线程池是怎么复用的。根据上面的结果,可以发现,创建的20个线程任务,提交到了线程池,执行时永远都只会打印了线程池中的10个执行线程的名字。说明线程池中,有一组10个线程,不断的在运行着,当有线程任务提交到线程时,线程池空闲的线程就会将提交的任务放到当前执行。提交进来的线程任务,虽然是实现了runnable接口,但是在线程池中它们不会作为线程进行调用,而是被当做了简单一个Java对象,在执行任务中,调用的是线程任务的run方法而已;下面我们通过阅读源码来看看。

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
     
        int c = ctl.get();
        //<1> 如果运行的线程少于corepoolsize,请尝试以给定的命令作为第一个任务启动新线程。
        if (workerCountOf(c) < corePoolSize) {
            //<1.1> 对addworker的调用以原子方式检查runstate和workercount,因此可以通过返回false来防止在不应该添加线程时出现错误警报。
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //<2> 如果任务可以成功排队,那么我们仍然需要再次检查是否应该添加线程(因为自上次检查以来已有的线程已死亡),或者池是否在进入此方法后关闭。
        if (isRunning(c) && workQueue.offer(command)) {//将任务加入队列中
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                //<2.1>因此,我们重新检查状态,如果停止,则回滚排队
                reject(command);
            else if (workerCountOf(recheck) == 0)
                //<2.2> 如果当前的线程池没有线程,则启动新线程。
                addWorker(null, false);
        }
        //<3> 如果无法将任务排队,则尝试添加新线程。如果失败了,我们知道我们被关闭或饱和,所以拒绝任务。
        else if (!addWorker(command, false))
            reject(command);
}

ThreadPoolExecutor的一些默认的变量,用于标识该线程池的状态,在该类文件的前面:

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //记录位数,32-3=29位,整型是4个字节,32位,为下面的运行状态留了3位用于表示。
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //默认的线程池的最大线程数,根据这个计算则最大线程数为:2^29 - 1个线程。
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 运行状态,使用高位来标识,这也是前面COUNT_BITS减去3的原因。
    private static final int RUNNING    = -1 << COUNT_BITS;//101...标识RUNNIG
    private static final int SHUTDOWN   =  0 << COUNT_BITS;//000...标识SHUTDOWN
    private static final int STOP       =  1 << COUNT_BITS;//001...标识STOP
    private static final int TIDYING    =  2 << COUNT_BITS;//010...标识TIDYING
    private static final int TERMINATED =  3 << COUNT_BITS;//011...标识TERMINATED

workerCountOf和isRunning分别用于计算当前线程池的大小和运行状态:

 // c是当前的线程数,c & CAPACITY标识按位与运算,所以最终保留的是c对应的位数,即当前线程池中的工作线程数。
 private static int workerCountOf(int c)  { return c & CAPACITY; }
 
 // c是当前的线程数,c & ~CAPACITY表示c和CAPACITY取反值得按位与运算,~表示取反,然后再与c进行按位与运算,因此跟上面的 workerCountOf的运算时相反的,这里运算后的保留的时高三位,所以表示的时当前线程池对应的运行状态。
 private static int runStateOf(int c)     { return c & ~CAPACITY; }

addWorker是线程中添加工作线程的核心代码,添加工作线程的源码如下:

 private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //<1> 尝试增加ctl的值,如果满足条件则新增worker的个数
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // <1.1> 检查线程池的状态和workQueue和firstTask是否为null
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                //<1.2> 如果core为true,则工作线程池与corePoolSize值比,否则与最小线程数比较                     if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //<1.3> 尝试更新worker的总数,如果失败则调到retry:进行重试
                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 {
            //<2.1> 新建Worker实例 
            w = new Worker(firstTask);
            final Thread t = w.thread;
           
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                //<2.2> 加锁
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());
                    //<2.3> 判断线程池的状态
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //<2.3.1>  将worker加入线程池的工作线程中
                        workers.add(w);
                        int s = workers.size();
                        //<2.3.2>  更新线程池的当前最大线程数据中和工作线程添加的标识workerAdded
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
}

主要完成了几步:

1.尝试增加ctl的值,如果满足条件则 尝试更新worker线程的总数,如果失败则调到retry:进行重试

2.新建Worker实例,加锁,判断线程池状态正常则向workers中添加新的工作线程worker.

Worker类的实现:

  //Worker类的的构造函数
  Worker(Runnable firstTask) {
      setState(-1); // inhibit interrupts until runWorker
      this.firstTask = firstTask;//我们提交的任务作为了Worker的一个成员变量存在
      this.thread = getThreadFactory().newThread(this);//实例化一个线程对象。
  }

Worker类中,runWorker方法就是线程池中的工作线程最核心的代码,也就是是Worker实例执行我们提交的任务线程的代码的地方,

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;//记住,这个就是我们提交的线程任务对象。
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 如果Worker中需要处理的Task不为空,则继续往下执行。
            while (task != null || (task = getTask()) != null) {
                //<1> 工作线程加锁,确保只处理当前的任务
                w.lock();
                //<2> 如果池正在停止,请确保线程已中断;否则,请确保线程未中断。这需要在第二种情况下重新检查,以便在清除中断时处理shutdownnow race
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //<3> 这就是我们的任务的实际执行,只是对提交给线程池的Runnable,其实最终在工作线程中,对这个Runnable的任务只是当做了一个简单的对象,调用的run方法而不是start方法。
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
}

到此,整个线程池的核心过程就讲完了。

总结

  回到最开始的那个例子,Executors.newFixedThreadPool(10);创建的10个线程是被重用的;service.execute(()-> System.out.println(Thread.currentThread().getName()+"->" + finalI));向线程池中添加的任务,线程池是并不会调用你提交的Runable作为Thread进行调用start方法。

  线程重用的核心是,它把Thread#start()给屏蔽起来了(一定不要重复调用),然后线程池它自己的工作线程Worker封装了对于你提交的任务的调用,只是调用了你提交的任务的Runnable#run()方法,循环在跑,跑的过程中不断检查我们是否有新加入的子Runnable对象,如果工作线程不存在,则先创建工作线程否则就直接调一下工作线程的run()方法;其实就类似于一个大的run()把其它任务即run()#1,run()#2,run()#3,...给串联起来了,基本原理就这么简单,当然具体也没这个描述那么简单,但是他的实现大概就是上述描述的过程。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值