Java ThreadPool


一、线程池


1. 线程池执行流程

  • 线程池执行任务的主要流程:
  1. 线程池是为了避免线程频繁的创建和销毁带来的性能消耗,而建立的一种池化技术。
  2. 它是把已创建的线程放入"池"中,当有任务来临时就可以重用已有的线程,无需等待创建的过程。
  3. 这样就可以有效提高程序的响应速度。
  4. 但如果要说线程池的话一定离不开 ThreadPoolExecutor 线程池执行器。
    在这里插入图片描述

2. 阿里巴巴《Java 开发手册》

  • 在阿里巴巴的《Java 开发手册》中规定线程池:
  1. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
  2. 这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。

3. Executors 创建线程池对象的弊端

  • Executors 返回的线程池对象的弊端如下:
  1. FixedThreadPoolSingleThreadPool
  1. 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
  1. CachedThreadPoolScheduledThreadPool
  1. 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM
  1. Executors 的源码会发现。
  1. Executors.newFixedThreadPool()Executors.newSingleThreadExecutor()Executors.newCachedThreadPool() 等方法的底层都是通过 ThreadPoolExecutor 实现的。

二、ThreadPoolExecutor 线程池执行器


1. 构造方法

  • ThreadPoolExecutor 的核心参数,指的是它在构建时需要传递的参数。
/**
 * @param corePoolSize 保留在池中的线​​程数,即使如果它们空闲,除非设置了{@code allowCoreThreadTimeOut}
 * @param maximumPoolSize 池中允许的最大线程数
 * @param keepAliveTime 当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。
 * @param unit {@code keepAliveTime}参数的时间单位
 * @param workQueue 在执行任务之前用于保留任务的队列。 该队列将仅保存由{@code execute}方法提交的{@code Runnable}任务。
 * @param threadFactory 执行程序创建新线程时要使用的工厂
 * @param handler 因为达到了线程界限和队列容量而在执行被阻止时使用的处理程序
 */
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    // maximumPoolSize 必须大于 0,且必须大于 corePoolSize
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
  • 核心参数
  1. int corePoolSize:核心线程数。
  1. 表示线程池的常驻核心线程数。
  2. 如果设置为 0,则表示在没有任何任务时,销毁线程池。
  3. 如果大于 0,即使没有任务时,也会保证线程池的线程数量等于此值。
  4. 但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程。
  5. 如果设置的比较大,则会浪费系统资源,所以开发者需要根据自己的实际业务来调整此值。
  1. int maximumPoolSize:最大线程数。
  1. 表示线程池在任务最多时,最大可以创建的线程数。
  2. 官方规定此值必须大于 0,也必须大于等于 corePoolSize
  3. 此值只有在任务比较多,且不能存放在任务队列时,才会用到。
  1. long keepAliveTime:空闲线程存活时间。
  1. 表示线程的存活时间,当线程池空闲时并且超过了此时间,多余的线程就会销毁。
  2. 直到线程池中的线程数量销毁到等于 corePoolSize 为止。
  3. 如果 maximumPoolSize 等于 corePoolSize,那么线程池在空闲的时候也不会销毁任何线程。
  1. TimeUnit unit:存活时间单位。
  1. 表示存活时间的单位,它是配合 keepAliveTime 参数共同使用的。
  1. BlockingQueue<Runnable> workQueue:任务队列。
  1. 表示线程池执行的任务队列。
  2. 当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行。
  1. ThreadFactory threadFactory:线程工厂。
  1. 表示线程的创建工厂,此参数一般用的比较少。
  2. 我们通常在创建线程池时不指定此参数,它会使用默认的线程创建工厂的方法来创建线程。
  1. RejectedExecutionHandler handler:拒绝策略
  1. 表示指定线程池的拒绝策略。
  2. 当线程池的任务已经在缓存队列 workQueue 中存储满了之后,并且不能创建新的线程来执行此任务时,就会用到此拒绝策略,它属于一种限流保护的机制。

2. BlockingQueue 任务队列


2.1 SynchronousQueue 直接提交队列
  • SynchronousQueue 是一个特殊的 BlockingQueue
  1. 它没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒。
  2. 反之每一个删除操作也都要等待对应的插入操作。
@Test
public void test() {
    /**
     * 1	corePoolSize	int	核心线程池大小
     * 2	maximumPoolSize	int	最大线程池大小
     * 3	keepAliveTime	long	线程最大空闲时间
     * 4	unit	TimeUnit	时间单位
     * 5	workQueue	BlockingQueue<Runnable>	线程等待队列
     * 6	threadFactory	ThreadFactory	线程创建工厂
     * 7	handler	RejectedExecutionHandler	拒绝策略
     */
    // 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 2
            , 1000
            , TimeUnit.MILLISECONDS
            , new SynchronousQueue<Runnable>()// 直接提交队列
            , Executors.defaultThreadFactory()
            , new ThreadPoolExecutor.AbortPolicy());// 拒绝策略

    for (int i = 0; i < 3; i++) {
        threadPoolExecutor.execute(new ThreadTask("任务-" + i));// 任务-3 会执行拒绝策略
    }
    /**
     * 问题: 当任务队列为SynchronousQueue, 拒绝策略为AbortPolic策略, 创建的线程数大于maximumPoolSize时, 直接执行了拒绝策略抛出异常。
     * 原因: 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行;
     * 	如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程;
     * 	如果达到maximumPoolSize设置的最大值,则根据你设置的 handler 执行拒绝策略;
     * 	因此这种方式你提交的任务不会被缓存起来,而是会被马上执行;
     * 	在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略。
     */
}
  • 执行结果:任务-3 会执行拒绝策略,略抛出异常。

2.2 ArrayBlockingQueue 有界任务队列【推荐】
  • 使用 ArrayBlockingQueue 有界任务队列。
  1. 若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到 corePoolSize 时,则会将新的任务加入到等待队列中。
  2. 若等待队列已满,即超过 ArrayBlockingQueue 初始化的容量,则继续创建线程,直到线程数量达到 maximumPoolSize 设置的最大线程数量。
  3. 若大于 maximumPoolSize,则执行拒绝策略。
  4. 在这种情况下,线程数量的上限与有界任务队列的状态有直接关系。
  1. 如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在 corePoolSize 以下。
  2. 反之当任务队列已满时,则会以 maximumPoolSize 为最大线程数上限。
public void test2() {
    // 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 2
            , 1000
            , TimeUnit.MILLISECONDS
            , new ArrayBlockingQueue<Runnable>(8)// 有界任务队列存满8个,会创建第二个线程
            , Executors.defaultThreadFactory()
            , new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 10; i++) {
        threadPoolExecutor.execute(new ThreadTask("任务-" + i));
    }
}
  • 执行结果:队列存满8个,会创建第二个线程。
Task: 任务-0, Thread: pool-1-thread-1, priority: 0
Task: 任务-9, Thread: pool-1-thread-2, priority: 0// 创建第二个线程
Task: 任务-2, Thread: pool-1-thread-1, priority: 0
Task: 任务-1, Thread: pool-1-thread-2, priority: 0
Task: 任务-4, Thread: pool-1-thread-2, priority: 0
Task: 任务-3, Thread: pool-1-thread-1, priority: 0
Task: 任务-5, Thread: pool-1-thread-2, priority: 0
Task: 任务-6, Thread: pool-1-thread-1, priority: 0
Task: 任务-7, Thread: pool-1-thread-2, priority: 0
Task: 任务-8, Thread: pool-1-thread-1, priority: 0

2.3 LinkedBlockingQueue 无界的任务队列
  • 使用无界任务队列。
  1. 线程池的任务队列,可以无限制的添加新的任务。
  2. 而线程池创建的最大线程数量,就是你 corePoolSize 设置的数量。
  1. 也就是说在这种情况下 maximumPoolSize 这个参数是无效的。
  2. 哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到 corePoolSize 后,就不会再增加了。
  3. 若后续有新的任务加入,则直接进入队列等待。
  1. 当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制。
  2. 不然会出现队列中的任务由于无法及时处理,导致一直增长,直到最后资源耗尽的问题。
public void test3() {
	// 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 2// 毫无意义
            , 1000
            , TimeUnit.MILLISECONDS
            , new LinkedBlockingQueue<Runnable>()// 无界的任务队列
            , Executors.defaultThreadFactory()
            , new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 10; i++) {
        threadPoolExecutor.execute(new ThreadTask("任务-" + i));
    }
}
  • 执行结果:就一个线程。
Task: 任务-0, Thread: pool-1-thread-1, priority: 0
Task: 任务-1, Thread: pool-1-thread-1, priority: 0
Task: 任务-2, Thread: pool-1-thread-1, priority: 0
Task: 任务-3, Thread: pool-1-thread-1, priority: 0
Task: 任务-4, Thread: pool-1-thread-1, priority: 0
Task: 任务-5, Thread: pool-1-thread-1, priority: 0
Task: 任务-6, Thread: pool-1-thread-1, priority: 0
Task: 任务-7, Thread: pool-1-thread-1, priority: 0
Task: 任务-8, Thread: pool-1-thread-1, priority: 0
Task: 任务-9, Thread: pool-1-thread-1, priority: 0

2.4 PriorityBlockingQueue 优先任务队列
public void test4() {
    // 优先任务队列
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 2
            , 1000
            , TimeUnit.MILLISECONDS
            , new PriorityBlockingQueue<Runnable>()// 优先任务队列
            , Executors.defaultThreadFactory()
            , new ThreadPoolExecutor.AbortPolicy());

    for (int i = 0; i < 10; i++) {
        threadPoolExecutor.execute(new ThreadTask("任务-" + i, i));
    }
}
  • 执行结果:就一个线程,按照优先级顺序执行。
  1. 可以看到除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列。
  2. 按优先级进行了重新排列执行,且线程池的线程数一直为 corePoolSize,也就是只有一个。
  3. 通过运行的代码可以看出 PriorityBlockingQueue 它其实是一个特殊的无界队列。
  1. 它其中无论添加了多少个任务,线程池创建的线程数也不会超过 corePoolSize 的数量。
  1. 只不过其他队列一般是按照先进先出的规则处理任务,而 PriorityBlockingQueue 队列可以自定义规则根据任务的优先级顺序先后执行。
Task: 任务-0, Thread: pool-1-thread-1, priority: 0
Task: 任务-9, Thread: pool-1-thread-1, priority: 9
Task: 任务-8, Thread: pool-1-thread-1, priority: 8
Task: 任务-7, Thread: pool-1-thread-1, priority: 7
Task: 任务-6, Thread: pool-1-thread-1, priority: 6
Task: 任务-5, Thread: pool-1-thread-1, priority: 5
Task: 任务-4, Thread: pool-1-thread-1, priority: 4
Task: 任务-3, Thread: pool-1-thread-1, priority: 3
Task: 任务-2, Thread: pool-1-thread-1, priority: 2
Task: 任务-1, Thread: pool-1-thread-1, priority: 1

3. ThreadFactory 线程工厂

  • 我们也可以自定义一个线程工厂,通过实现 ThreadFactory 接口来完成。
  • 这样就可以自定义线程的名称或线程执行的优先级了。
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {  	                      
    this(corePoolSize, 
    	maximumPoolSize, 
    	keepAliveTime, 
    	unit, 
    	workQueue,
      	Executors.defaultThreadFactory(),// 为默认的线程创建工厂   
      	defaultHandler);
}
public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}
/**
 * 默认的线程创建工厂,需要实现 ThreadFactory 接口
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    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;
    }
}

4. RejectedExecutionHandler 拒绝策略

  • 当线程池中的任务队列已经被存满。
  1. 再有任务添加时,会先判断当前线程池中的线程数,是否大于等于线程池的最大值。
  1. 如果是,则会触发线程池的 拒绝策略
  1. 一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建 有界任务队列
  1. 但有种模式下,如果出现任务队列已满,且线程池创建的线程数达到你设置的最大线程数时。
  2. 这时就需要你指定 ThreadPoolExecutorRejectedExecutionHandler 参数。
  3. 即合理的拒绝策略,来处理 线程池超载 的情况。

4.1 AbortPolicy 终止策略
  • 线程池会抛出异常并终止执行,它是默认的拒绝策略
  • 例如:演示一个 AbortPolicy 的拒绝策略,代码如下:
  1. 问题:第 6 个任务来的时候,线程池则执行了 AbortPolicy 拒绝策略,抛出了异常。
  2. 原因:因为队列最多存储 2 个任务,最大可以创建 3 个线程来执行任务(2+3=5)
  3. 所以当第 6 个任务来的时候,此线程池就忙不过来了。
/**
 * 线程池的拒绝策略: AbortPolicy 终止策略
 */
public void test() {
    // 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 3
            , 10
            , TimeUnit.SECONDS
            , new LinkedBlockingQueue<>(2)
            , new ThreadPoolExecutor.AbortPolicy());// 添加 AbortPolicy 终止策略

    for (int i = 0; i < 6; i++) {
        threadPoolExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}

4.2 CallerRunsPolicy 把任务交给当前线程来执行
  • 如果线程池的线程数量达到上限,该策略会把任务队列中的任务,放在调用者线程当中运行

4.3 DiscardPolicy 忽略此任务(最新的任务)
  • 该策略会默默丢弃无法处理的任务,不予任何处理。
  • 当然使用此策略,业务场景中需允许任务的丢失。

4.4 DiscardOldestPolicy 忽略最早的任务(最先加入队列的任务)
  • 该策略会丢弃任务队列中最老的一个任务。
  • 也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交。

5. execute() 执行

/**
 * 在将来的某个时间执行给定的任务。任务可以在新线程或现有池线程中执行。
 *
 * 如果由于执行程序已关闭或已达到其容量而无法提交执行任务,则该任务由当前的{@code RejectedExecutionHandler}处理。
 *
 * @param command 要执行的任务
 */
public void execute(Runnable command) {
	if (command == null)
        throw new NullPointerException();
	/*
     * 进行3个步骤:
     *
     * 1. 如果正在运行的线程少于 corePoolSize 线程,请尝试使用给定命令作为其第一个任务来启动一个新线程。对 addWorker 的调用从原子上检查 runState 和 workerCount,从而通过返回 false 来防止在不应该添加线程的情况下发出错误警报。
     *
     * 2. 如果一个任务可以成功地排队,那么我们仍然需要再次检查是否应该添加线程(因为现有线程自上次检查后就死掉了),或者自进入该方法以来该池已关闭。因此,我们重新检查状态,并在必要时回滚排队,如果停止,或者如果没有线程,则启动一个新线程。
     *
     * 3. 如果我们无法将任务排队,那么我们尝试添加一个新的线程。如果失败,我们知道我们已关闭或处于饱和状态,因此拒绝该任务。
     */
    int c = ctl.get();
    // 当前工作的线程数小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    	// 创建新的线程执行此任务
        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);
       	// 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生)     
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);// 新建线程执行任务
    }
    // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败
    else if (!addWorker(command, false))
        reject(command);// 执行拒绝策略
}
  • addWorker(Runnable firstTask, boolean core) 方法的参数说明如下:
  1. firstTask:线程应首先运行的任务,如果没有则可以设置为 null
  2. core:判断是否可以创建线程的阀值(最大值)
  1. 如果等于 true 则表示使用 corePoolSize 作为阀值。
  2. false 则表示使用 maximumPoolSize 作为阀值。

二、知识扩展


1. execute()submit() 区别

  • execute()submit() 都是用来执行线程池任务的,它们最主要的区别是:
  1. submit() 方法可以接收线程池执行的返回值。
  2. execute() 方法不能接收返回值。
// 线程池
private static ThreadPoolExecutor threadPoolExecutor;

/**
 * execute() VS submit()
 */
@Test
public void test() {
	// 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(2
            , 10
            , 10L
            , TimeUnit.SECONDS
            , new LinkedBlockingQueue<>(20));
            
    // 1. execute使用
    threadPoolExecutor.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println("run execute()");
        }
    });
    
    // 2. submit使用
    Future<String> submit = threadPoolExecutor.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            System.out.println("run submit()");
            return "SUCCESS";
        }
    });
    
    try {
        // 获取线程返回值
        System.out.println(submit.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
  • 从结果可以看出 submit() 方法可以配合 Futrue 来接收线程执行的返回值。
  1. 它们的另一个区别是 execute() 方法属于 Executor 接口的方法。
  2. submit() 方法则是属于 ExecutorService 接口的方法。
  • 以上程序执行结果如下:
run execute()
run submit()
SUCCESS

2. 自定义线程工厂

  • 线程池中的线程就是通过 ThreadPoolExecutor 中的 ThreadFactory,线程工厂创建的。
  • 那么通过自定义 ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等。
public void test() {
    // 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 2
            , 1000
            , TimeUnit.MILLISECONDS
            , new ArrayBlockingQueue<Runnable>(5)// 有界任务队列
            , new ThreadFactory() {// 自定义线程工厂

                @Override
                public Thread newThread(Runnable r) {
                    System.out.println("线程" + r.hashCode() + "创建");
                    // 线程命名
                    Thread thread = new Thread(r, "threadPoolExecutor-" + r.hashCode());
                    return thread;
                }
            }
            , new ThreadPoolExecutor.CallerRunsPolicy());

    for (int i = 0; i < 10; i++) {
        threadPoolExecutor.execute(new ThreadTask("任务-" + i));
    }
}
  • 以上代码执行的结果如下:
线程1451043227创建
线程783286238创建
Task: 任务-0, Thread: threadPoolExecutor-1451043227, priority: 0
Task: 任务-7, Thread: main, priority: 0
Task: 任务-6, Thread: threadPoolExecutor-783286238, priority: 0
Task: 任务-9, Thread: main, priority: 0
Task: 任务-2, Thread: threadPoolExecutor-783286238, priority: 0
Task: 任务-1, Thread: threadPoolExecutor-1451043227, priority: 0
Task: 任务-3, Thread: threadPoolExecutor-783286238, priority: 0
Task: 任务-4, Thread: threadPoolExecutor-1451043227, priority: 0
Task: 任务-5, Thread: threadPoolExecutor-783286238, priority: 0
Task: 任务-8, Thread: threadPoolExecutor-1451043227, priority: 0

3. 自定义拒绝策略

  • 自定义拒绝策略只需要新建一个 RejectedExecutionHandler 对象。
  • 然后重写它的 rejectedExecution() 方法即可。
  • 如下代码所示:
/**
 * 自定义拒绝策略
 */
public void test2() {
	// 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(1
            , 3
            , 10
            , TimeUnit.SECONDS
            , new LinkedBlockingQueue<>(2)
            , new RejectedExecutionHandler() {// 添加自定义拒绝策略

                @Override
                public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                    System.out.println("执行自定义拒绝策略");
                }
            });
            
    for (int i = 0; i < 6; i++) {
        threadPoolExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }
}
  • 可以看出线程池执行了自定义的拒绝策略。
  • 我们可以在 rejectedExecution 中添加自己业务处理的代码。
  • 以上代码执行的结果如下:
执行自定义拒绝策略
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3

4. ThreadPoolExecutor 扩展

  • ThreadPoolExecutor 的扩展
  1. 主要是通过重写它的 beforeExecute()afterExecute() 方法实现的。
  2. 我们可以在扩展方法中添加日志或者实现数据统计,比如统计线程的执行时间。
  • 如下代码所示:
// 保存线程执行开始时间
private final ThreadLocal<Long> localTime = new ThreadLocal<>();
/**
 * ThreadPoolExecutor 扩展
 */
public void test() {
    // 创建线程池
    threadPoolExecutor = new ThreadPoolExecutor(2
            , 4
            , 10
            , TimeUnit.SECONDS
            , new LinkedBlockingQueue<>()) {// 实现自定义接口

                /**
                 * 开始执行之前
                 * @param t 线程
                 * @param r 任务
                 */
                @Override
                protected void beforeExecute(Thread t, Runnable r) {
                    super.beforeExecute(t, r);
                    Long sTime = System.nanoTime();// 开始时间 (单位:纳秒)
                    localTime.set(sTime);
                    
                    System.out.println(String.format("%s | beforeExecute | time: %s",
                            t.getName(), sTime));
                }

                /**
                 * 执行完成之后
                 * @param r 任务
                 * @param t 抛出的异常
                 */
                @Override
                protected void afterExecute(Runnable r, Throwable t) {
                    super.afterExecute(r, t);
                    Long eTime = System.nanoTime();// 结束时间 (单位:纳秒)
                    Long totalTime = eTime - localTime.get();// 执行总时间
                    
                    System.out.println(String.format("%s | afterExecute | time: %s | 耗时: %s",
                            Thread.currentThread().getName(), eTime, (totalTime / 1000000.0)));
                }

                @Override
                protected void terminated() {
                    super.terminated();
                    System.out.println("线程池退出");
                }
            };
            
    for (int i = 0; i < 3; i++) {
        threadPoolExecutor.execute(() -> {
            System.out.println(Thread.currentThread().getName());
        });
    }

	/**
     * 可以看到通过对beforeExecute()、afterExecute()和terminated()的实现,
     * 我们对线程池中线程的运行状态进行了监控,在其执行前后输出了相关打印信息。
     * 另外使用 shutdown() 方法可以比较安全的关闭线程池, 当线程池调用该方法后,线程池中不再接受后续添加的任务。
     * 但是,此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出
     */
	threadPoolExecutor.shutdown();// 以比较安全的关闭线程池
}
  • 以上程序的执行结果如下所示:
pool-1-thread-1 | beforeExecute | time: 4013652833313
pool-1-thread-2 | beforeExecute | time: 4013666761362
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2 | afterExecute | time: 4013705529345 | 耗时: 38.767983
pool-1-thread-1 | afterExecute | time: 4013705585177 | 耗时: 52.751864
pool-1-thread-2 | beforeExecute | time: 4013712116298
pool-1-thread-2
pool-1-thread-2 | afterExecute | time: 4013712470585 | 耗时: 0.354287

5. 线程池线程数量

  • 线程池线程数量的设置,没有一个明确的指标。
  • 根据实际情况,只要不是设置的偏大和偏小都问题不大,结合下面这个公式即可。
/**
 * Nthreads = CPU数量
 * Ucpu = 目标CPU的使用率,0<=Ucpu<=1
 * W/C = 任务等待时间与任务计算时间的比率
 */
Nthreads = Ncpu * Ucpu * (1 + W / C)

三、小结

  • 线程池的使用必须要通过 ThreadPoolExecutor 的方式来创建。
  1. 这样可以更加明确线程池的运行规则,规避资源耗尽的风险。
  2. 同时,也介绍了 ThreadPoolExecutor 的七大核心参数,包括 核心线程数最大线程数 之间的区别。
  3. 当线程池的任务队列没有可用 空闲线程,且线程池的线程数量已经达到了 最大线程数 时,则会执行 拒绝策略
  4. Java 自动的拒绝策略有 4 种,用户也可以通过重写 rejectedExecution() 来自定义拒绝策略。
  5. 我们还可以通过重写 beforeExecute()afterExecute() 来实现 ThreadPoolExecutor 的扩展功能。

1. ThreadPoolExecutor 的执行方法有几种?它们有什么区别?

2. 什么是线程的拒绝策略?

3. 拒绝策略的分类有哪些?

4. 如何自定义拒绝策略?

5. ThreadPoolExecutor 能不能实现扩展?如何实现扩展?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骑士梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值