多线程——Java线程池原理深入

      上次我们简单了解了一下什么是线程池以及Java中几种类型的线程池,今天我们来深入剖析一下线程池的原理。

1、构造

      1. 线程池管理器(ThreadPoolManager):用于创建并管理线程池

      2. 工作线程(WorkThread): 线程池中线程

      3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。

      4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。

注:线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务

2、线程状态

这里写图片描述

       新建状态(New):新创建了一个线程对象。
      就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
      运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
      阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
      (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
      (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
      (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
      死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3、执行流程

这里写图片描述

      a.一个任务提交,如果线程池大小没达到corePoolSize,则每次都启动一个worker也就是一个线程来立即执行
      b.如果来不及执行,则把多余的线程放到workQueue,等待已启动的worker来循环执行
      c.如果队列workQueue都放满了还没有执行,则在maximumPoolSize下面启动新的worker来循环执行workQueue
      d.如果启动到maximumPoolSize还有任务进来,线程池已达到满负载,此时就执行任务拒绝RejectedExecutionHandler

// 流程就是:没达到corePoolSize,创建worker执行,达到corePoolSize加入workQueue
//           workQueue满了且在maximumPoolSize下,创建新worker,达到maximumPoolSize,执行reject
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();

    // 1:poolSize达到corePoolSize,执行3把任务加入workQueue
    // 2:poolSize没达到,执行addIfUnderCorePoolSize()在corePoolSize内创建新worker立即执行任务
    //    如果达到corePoolSize,则同上执行3
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        // 3:workQueue满了,执行5
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0) {
                // 4:如果线程池关闭,执行拒绝策略
                //   如果poolSize==0,新启动一个线程执行队列内任务
                ensureQueuedTaskHandled(command);
            }
            // 5:在maximumPoolSize内创建新worker立即执行任务
            //   如果达到maximumPoolSize,执行6拒绝策略
        } else if (!addIfUnderMaximumPoolSize(command))
            // 6:拒绝策略
            reject(command); // is shutdown or saturated
    }
}

4、线程销毁

      keepAliveTime:代表的就是线程空闲后多久后销毁,线程的销毁是通过worker的getTask()来实现的。
      一般来说,Worker会循环获取getTask(),如果getTask()返回null则工作线程worker终结,那我们再看看什么时候getTask()返回null

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            if (state == SHUTDOWN)  // Help drain queue
                r = workQueue.poll();
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
                // 在poolSize大于corePoolSize或允许核心线程超时时
                // 阻塞超时获取有可能获取到null,此时worker线程销毁
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
            else
                r = workQueue.take();
            if (r != null)
                return r;
            // 这里是是否运行worker线程销毁的判断
            if (workerCanExit()) {
                if (runState >= SHUTDOWN)
                    // STOP或TERMINATED状态,终止空闲worker
                    interruptIdleWorkers();
                return null; // 这里返回null,代表工作线程worker销毁
            }
            // 其他:retry,继续循环
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

5、线程池关闭

      平缓关闭 shutdown:这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程,同时不再接受新的任务,而仍在工作的线程不受影响,所以队列中的任务人会被执行
      立即关闭 shutdownNow:此方法将runState置为STOP,和shutdown方法的区别是,这个方法会终止所有的线程(取消所有正在执行和未执行的任务),所以队列中的任务也不会被执行了。

总结:

      通过这次学习,我们不仅知道了线程池是如何管理线程,而且还了解了线程多种状态之间的转换,这样更加便于我们对线程池的理解。之后,我们还将继续学习关于多线程的知识,让我们每天都有成长。

猜你喜欢

转载自blog.csdn.net/u010168160/article/details/76473993