本文分析下线程的工作流程

  • 线程池创建。
  • 执行任务过程。
  • 创建线程过程。
  • 线程池中的线程执行任务过程。
  • 回收线程过程。
  • 任务缓冲队列。

因为在工作中遇到了一些线程池的使用问题,百度了许久,都未找到答案,所以就自己去看了下源码。特此记录下。

使用线程池遇到的问题

  • 核心线程已用完,不扩建线程。(最大线程数 > 核心线程数)的情况。

之前的错误理解

  • 核心线程都在工作,再添加任务,无论任务队列是否满了,只要工作线程数小于最大线程数,都会创建线程。
  • 任务放置队列中,是工作线程数达到最大线程数。
  • 核心线程不会被回收。
  • 有独立线程在回收线程池中的空闲线程。


线程池工作流程和常用API及使用demo

  • 1.0 创建线程池
  • 2.0 执行任务过程
  • 3.0 创建线程过程
  • 4.0 线程池中的线程执行任务过程
  • 5.0 回收线程过程
  • 6.0 任务缓冲队列
  • 7.0 线程池中的线程执行任务总体工作流程
  • 8.0 线程池其他常用API
  • 9.0 线程池演示Demo


1.0 创建线程池

创建线程池的构造参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}
ThreadPoolExecutor executor = new ThreadPoolExecutor(
                1,                          // 核心线程数,长期保持活跃数量, 不会被回收
                10,                     // 最大线程数, 包括(核心线程数)
                0L,                       // 空闲线程最大允许多长时间被回收
                TimeUnit.MILLISECONDS,                 // keepAliveTime 的时间单位
                new LinkedBlockingQueue<>(2),           // 线程池执行任务队列
                Executors.defaultThreadFactory(),      // 创建线程的工厂对象
                new ThreadPoolExecutor.AbortPolicy()   // 执行任务失败处理器
        );

1.1 threadFactory

ThreadFactory接口,其中只有一个 newThread()方法,用于创建线程

默认值:Executors.defaultThreadFactory()

代码如下:

  • 设置保护线程为false。
  • 设置线程名:pool-poolNumber-thread-threadNumber
  • 设置优先级
DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }

1.3 handler

RejectedExecutionHandler接口,其中只有一个 rejectedExecution()方法,用于失败执行。

默认值:new ThreadPoolExecutor.AbortPolicy()

代码如下:

失败抛出异常:RejectedExecutionException

public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

还可参考其他选项:

  • ThreadPoolExecutor.CallerRunsPolicy
  • ThreadPoolExecutor.DiscardPolicy
  • ThreadPoolExecutor.DiscardOldestPolicy

2.0 执行任务过程

线程池执行任务:executor.execute(Runnable)

execute 流程

Java 线程池中的工厂实现重命名 java线程池工作流程_Java 线程池中的工厂实现重命名

3.0 创建线程过程

创建线程方法:addWorker(Runnable firstTask, boolean core),core参数表示:是否在小于核心线程数的情况下创建线程。

Java 线程池中的工厂实现重命名 java线程池工作流程_Java 线程池中的工厂实现重命名_02

4.0 线程池中的线程执行任务过程

线程轮询在队列中获取任务。

5.0 回收线程过程

从任务队列中获取一个任务超时,正常结束。

6.0 任务缓冲队列

核心方法:

  • boolean offer(E)
  • E poll(timeout, unit) 获取并移除队列头节点。如果队列为空,则等待${timeout}时间,如果还是没有数据,则返回null。
  • E take()

7.0 线程池中的线程执行任务总体工作流程

结合以上知识:参考如下图

Java 线程池中的工厂实现重命名 java线程池工作流程_java_03


大致流程描述:

  • 执行任务,核心线程数未达到corePoolSize该值,就创建线程。否则塞入任务队列。
  • 任务队列满了,就创建线程,但是总线程数不能超过该值maximumPoolSize。
  • 如果任务队列满了,线程数达到maximumPoolSize值,则执行失败策略。
  • 工作线程则不停的轮询去队列中poll任务,如果poll为空,则工作线程执行结束(回收线程)。
  • 如果工作线程数<=核心线程数corePoolSize,则使用take从队列中获取任务(核心线程一直await)。

8.0 线程池其他常用API

  • prestartCoreThread:预先启动一个核心线程。
  • prestartAllCoreThreads:预先启动全部核心线程。
  • allowCoreThreadTimeOut:设置核心线程是否可以被回收。
  • getActiveCount:获取当前正在执行任务的线程的大致数量。
  • getLargestPoolSize:获取线程池有史以来最大的线程数。
  • getPoolSize:获取当前线程池数量。
  • getCompletedTaskCount:获取已完成执行的任务的大致总数。
  • getQueue:获取任务队列。
  • remove:从任务队列中移除一个任务。
  • shutdown:将队列中的任务执行完毕后,停止线程池执行器。但是不接受新的任务。
  • shutdownNow:尝试停止所有正在执行的任务,暂停正在等待的任务的处理,并返回正在等待执行的任务列表。从该方法返回后,这些任务将从任务队列中排出(删除)。
  • execute:执行一个任务。
  • submit:执行一个任务,可接受Callable类型。

9.0 线程池演示Demo

演示代码如下:

核心线程数2个,最大线程数10个,队列容量20个。
执行 22 次任务。任务里面休眠(1000 * 1000)毫秒。
执行结果:2个核心线程创建,执行两次任务,进入休眠。剩余20次任务都塞入队列。
并不会去创建线程

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo {

    public void test() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,          // 核心线程数
                10,     // 最大线程数
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(20)   // 任务队列容量
        );
        // 执行 22 次任务
        for (int i = 0; i < 22; i++) {
            executor.execute(new DemoRunnable(i));
        }
    }

    public static class DemoRunnable implements Runnable {
        final int value;
        static long time = 1000L * 1000L;   // 休眠长一点
        public DemoRunnable(int value) {
            this.value = value;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " - value = " + value);
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new ThreadPoolDemo().test();
    }

}

执行效果截图:

Java 线程池中的工厂实现重命名 java线程池工作流程_java_04

将执行任务次设置为23次,队列满了,再执行任务,则会创建线程,进行执行当前任务。

Java 线程池中的工厂实现重命名 java线程池工作流程_创建线程_05