线程系列 2 - 并发编程之线程池 ThreadPool 的那些事

本文详细探讨了线程池的概念、线程池的各种参数及其作用,包括核心线程数、最大线程数、任务队列、拒绝策略等,并介绍了线程池的执行流程和监控方法。同时,文章对比了execute和submit方法的区别,列举了四种常见的线程池类型,并指导如何合理配置线程池以应对不同类型的任务负载。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、New Thread 弊端

 

  • 每次 new Thread 新建对象,性能差。
  • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM。
  • 缺少更多功能,如更多执行、定期执行、线程中断。

 

2、线程池的好处

 

  • 降低资源消耗:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。重复利用已创建的线程,可以降低线程创建和销毁造成的消耗。

  • 可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。

  • 提高线程的可管理性:线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务的数量等。通过线程池可以对线程资源进行统一的分配、监控和调优

  • 提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。

     

3、线程池 - ThreadPoolExecutor

在这里插入图片描述
在这里插入图片描述

 

3.1、 核心参数

 

  • corePoolSize:核心线程数量。
  • maximumPoolSize: 线程最大线程数。
  • workQueue: 阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
  • keepAliveTime: 线程没有任务执行时,最多保持多久时间终止。
  • unit: keepAliveTime 的时间单位。
  • threadFactory: 线程工厂,用来创建线程。
  • rejectHandler: 当拒绝处理任务时的策略。
     

3.1.1 ThreadFactory (线程工厂)

 
        Executors创建新的线程池时,使用Executors.defaultThreadFactory默认实例,全部位于同一个ThreadGroup(线程组)中,具有相同的NORM_PRIORITY(优先级为5),而且都是非守护进程状态。
 

package java.util.concurrent;

public interface ThreadFactory {
    /**
     * 实现该方法可以更改所创建的新线程的名称、线程组、优先级、守护进程状态等。
     * 如果newThread()的返回值为null,表示线程工厂未能成功创建线程,线程池可能无法执行任何任务。
     */
    Thread newThread(Runnable r);
}

 

3.1.2 workQueue (任务阻塞队列)

 
       在阻塞队列为空时会阻塞当前线程的元素获取操作。在一个线程从一个空的阻塞队列中获取元素时,线程会被阻塞,直到阻塞队列中有了元素;当队列中有元素后,被阻塞的线程会自动被唤醒(唤醒过程不需要用户程序干预 )。
Java线程池使用BlockingQueue实例暂时接收到的异步任务,BlockingQueue是JUC包的一个超级接口,比较常用的实现类有:

(1)ArrayBlockingQueue:是一个数组实现的有界阻塞队列,队列中的元素按FIFO排序。该阻塞队列在创建时必须设置大小。

(2)LinkedBlockingQueue:是一个基于链表实现的阻塞队列,按FIFO排序任务,可以设置容量(有界队列),不设置容量则默认使用Integer.Max_VALUE作为容量(无界队列)。该队列的吞吐量高于ArrayBlockingQueue。

如果不设置LinkedBlockingQueue的容量(无界队列),当接收的任务数量超出corePoolSize时,则新任务可以被无限制地缓存到该阻塞队列中,直到资源耗尽。newSingleThreadExecutor和newFixedThreadPool使用了LinkedBlockingQueue,并且都没有设置容量(无界队列)。

(3)PriorityBlockingQueue:是具有优先级的无界队列。

(4)DelayQueue:这是一个无界阻塞延迟队列,底层基于PriorityBlockingQueue实现,队列中每个元素都有过期时间,当从队列获取元素(元素出队)时,只有已经过期的元素才会出队,队列头部的元素是过期最快的元素。newScheduledThreadPool所创建的线程池使用此队列。

(5)SynchronousQueue:(同步队列)是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程的调用移除操作,否则插入操作一直处于阻塞状态,其吞吐量通常高于LinkedBlockingQueue。newCachedThreadPool所创建的线程池使用此队列。与前面的队列相比,这个队列比较特殊,它不会保存提交的任务,而是直接新建一个线程来执行新来的任务。

 

3.1.3 RejectHandler (线程池的拒绝策略)

 
任务被拒绝有两种情况:

(1)线程池已经被关闭。

(2)工作队列已满且maximumPoolSize已满。在线程池的任务缓存队列为有界队列(有容量限制的队列)的时候,如果队列满了,提交任务到线程池的时候就会被拒绝。

任务被拒绝时,线程池都会调用RejectedExecutionHandler实例的rejectedExecution方法。

RejectedExecutionHandler 是拒绝策略的接口,JUC为该接口提供了以下几种实现:

实现策略
AbortPolicy (默认)如线程池队列满了,新任务会被拒绝,抛出异常:RejectedExecutionException.该异常是非受检异常(运行时异常),很容易忘记捕获。
DiscardPolicy如线程池队列满了,新任务会直接被丢掉
DiscardOldestPolicy如队列满了,会将最早进入队列的任务抛弃,从队列中腾出空间,再尝试加入队列
CallerRunsPolicy如队列满了,新任务被添加到线程池时,提交任务线程会自己去执行该任务,不使用线程池中的线程去执行新任务。
自定义实现RejectedExecutionHandler接口的rejectedExecution方法.

 
自定义拒绝策略示例:


import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ThreadPoolDemo {

    // 自定义线程工厂
    public static class MyThreadFactory implements ThreadFactory {
        static AtomicInteger threadNo = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            String threadName = "MyThreadFactory-" + threadNo.get();
            log.info("创建线程,线程名称:" + threadName);
            threadNo.incrementAndGet();
            //设置线程名称和异步执行目标
            Thread thread = new Thread(r, threadName);
            //设置为守护线程
            thread.setDaemon(true);
            return thread;
        }
    }

    // 自定义拒绝策略
    public static class MyRejectedPolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            // 一般是对拒绝的任务落库,后续进行补偿等
            log.info("{} -> 执行拒绝策略,taskCount = {}", r, executor.getTaskCount());
            // 新建一个线程去处理
            new Thread(r, "拒绝策略中新建线程").start();

//            或者 sleep 一段时间后再次将任务加入队列
//            try {
//                TimeUnit.SECONDS.sleep(8);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            // 再次将任务加入队列
//            executor.execute(r);

        }
    }


    public static void main(String[] args) throws InterruptedException {
        int corePoolSize = 2; //核心线程数
        int maximumPoolSize = 4; //最大线程数
        long keepAliveTime = 10;
        TimeUnit unit = TimeUnit.SECONDS;
        //阻塞队列任务数
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        //线程工厂
        ThreadFactory threadFactory = new MyThreadFactory();
        //拒绝和异常处理策略
        RejectedExecutionHandler policy = new MyRejectedPolicy();
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue, threadFactory, policy);
        // 预启动所有核心线程
        pool.prestartAllCoreThreads();
        // 循环开 10个任务,线程池最大线程数 = 最大核心线程数(4)+阻塞队列数(2) = 6
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        for (Integer t : list) {
            pool.execute(() -> {
                try {
                    log.info("线程名称-> {} ---> 参数 -> {}执行任务中", Thread.currentThread().getName(), t);
                    // 线程睡眠一会
                    TimeUnit.SECONDS.sleep(5);
                    log.info("线程名称-> {} ---> 参数 -> {}任务执行完毕", Thread.currentThread().getName(), t);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        //等待10秒
        TimeUnit.SECONDS.sleep(15);
        log.info("关闭线程池:pool");
        pool.shutdown();
    }

}

打印结果:

15:42:22.494 [main] INFO com.demo.test.ThreadPoolDemo - 创建线程,线程名称:MyThreadFactory-1
15:42:22.497 [main] INFO com.demo.test.ThreadPoolDemo - 创建线程,线程名称:MyThreadFactory-2
15:42:22.498 [main] INFO com.demo.test.ThreadPoolDemo - 创建线程,线程名称:MyThreadFactory-3
15:42:22.498 [main] INFO com.demo.test.ThreadPoolDemo - 创建线程,线程名称:MyThreadFactory-4
15:42:22.499 [main] INFO com.demo.test.ThreadPoolDemo - com.demo.test.ThreadPoolDemo$$Lambda$21/0x00000008000c6c40@a38d7a3 -> 执行拒绝策略,taskCount = 6
15:42:22.498 [MyThreadFactory-2] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-2 ---> 参数 -> 2执行任务中
15:42:22.499 [MyThreadFactory-3] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-3 ---> 参数 -> 3执行任务中
15:42:22.498 [MyThreadFactory-1] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-1 ---> 参数 -> 1执行任务中
15:42:22.498 [MyThreadFactory-4] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-4 ---> 参数 -> 6执行任务中
15:42:22.500 [main] INFO com.demo.test.ThreadPoolDemo - com.demo.test.ThreadPoolDemo$$Lambda$21/0x00000008000c6c40@77f99a05 -> 执行拒绝策略,taskCount = 6
15:42:22.501 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 7执行任务中
15:42:22.501 [main] INFO com.demo.test.ThreadPoolDemo - com.demo.test.ThreadPoolDemo$$Lambda$21/0x00000008000c6c40@63440df3 -> 执行拒绝策略,taskCount = 6
15:42:22.501 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 8执行任务中
15:42:22.501 [main] INFO com.demo.test.ThreadPoolDemo - com.demo.test.ThreadPoolDemo$$Lambda$21/0x00000008000c6c40@3aeaafa6 -> 执行拒绝策略,taskCount = 6
15:42:22.501 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 9执行任务中
15:42:22.501 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 10执行任务中
15:42:27.500 [MyThreadFactory-2] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-2 ---> 参数 -> 2任务执行完毕
15:42:27.500 [MyThreadFactory-1] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-1 ---> 参数 -> 1任务执行完毕
15:42:27.500 [MyThreadFactory-4] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-4 ---> 参数 -> 6任务执行完毕
15:42:27.500 [MyThreadFactory-3] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-3 ---> 参数 -> 3任务执行完毕
15:42:27.500 [MyThreadFactory-2] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-2 ---> 参数 -> 5执行任务中
15:42:27.500 [MyThreadFactory-1] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-1 ---> 参数 -> 4执行任务中
15:42:27.516 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 9任务执行完毕
15:42:27.516 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 8任务执行完毕
15:42:27.516 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 10任务执行完毕
15:42:27.516 [拒绝策略中新建线程] INFO com.demo.test.ThreadPoolDemo - 线程名称-> 拒绝策略中新建线程 ---> 参数 -> 7任务执行完毕
15:42:32.508 [MyThreadFactory-1] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-1 ---> 参数 -> 4任务执行完毕
15:42:32.508 [MyThreadFactory-2] INFO com.demo.test.ThreadPoolDemo - 线程名称-> MyThreadFactory-2 ---> 参数 -> 5任务执行完毕
15:42:37.505 [main] INFO com.demo.test.ThreadPoolDemo - 关闭线程池:pool

 

3.2、线程的任务调度流程 (**)

在这里插入图片描述
线程池的任务调度流程(包含接收新任务和执行下一个任务)大致如下:

(1)如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程。

(2)如果线程池中总的任务数量大于核心线程池数量,新接收的任务将被加入阻塞队列中,一直到阻塞队列已满。在核心线程池数量已经用完、阻塞队列没有满的场景下,线程池不会为新任务创建一个新线程。

(3)当完成一个任务的执行时,执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空,其中所有的缓存任务被取光。

(4)在核心线程池数量已经用完、阻塞队列也已经满了的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。

(5)在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出maximumPoolSize。如果线程池的线程总数超过maximumPoolSize,线程池就会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略
 

注意:
 
(1)核心和最大线程数量、BlockingQueue队列等参数如果配置得不合理,可能会造成异步任务得不到预期的并发执行,造成严重的排队等待现象。
 
(2)线程池的调度器创建线程的一条重要的规则是:在corePoolSize已满之后,还需要等阻塞队列已满,才会去创建新的线程。
 

 

3.3、 核心方法

 

  • execute(): 提交任务,交给线程池执行。
  • submit(): 提交任务,能够返回执行结果 execute+Future.
  • shutdown(): 关闭线程池,等待任务都执行完。
  • shutdownNow(): 关闭线程池,不等待任务执行完。
     

3.3.1 execute 和 submit

 

package java.util.concurrent;

public interface Executor {

    /**
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

------------------------------------
package java.util.concurrent;

import java.util.Collection;
import java.util.List;    
public interface ExecutorService extends Executor {
    
    <T> Future<T> submit(Callable<T> task);

    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);
}  

 

区别executesubmit
参数RunnableCallable / Runnable
返回值Future
抛出异常

execute 和 submit 示例

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.*;

@Slf4j
public class ThreadExecuteDemo {


    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(4);
        pool.execute(() -> {
                log.info("执行 execute run()方法", Thread.currentThread().getName());
        });
        pool.execute(() -> {
            log.info("执行 execute run()方法 迎接异常", Thread.currentThread().getName());
            Integer.valueOf("hello error");
        });
        Future<String> myCallableFuture = pool.submit(new MyCallable());
        try {
           // 获取结果 这里容易阻塞
           String myCallableResult =  myCallableFuture.get();
           log.info("myCallableResult ---> {}", myCallableResult);
        } catch (InterruptedException e) {
            log.error("myCallableResult InterruptedException happened");
            e.printStackTrace();
        } catch (ExecutionException e) {
            log.error("myCallableResult ExecutionException happened");
            e.printStackTrace();
        }
        Future<String> myCallableExceptionFuture = pool.submit(new MyCallableThrowException());
        try {
            // 获取结果 , 这里容易阻塞 MyCallableThrowException 执行没有主线程快就会阻塞,后面再具体讲Future
            String myCallableExceptionFutureResult =  myCallableExceptionFuture.get();
            log.info("myCallableExceptionFutureResult ---> {}", myCallableExceptionFutureResult);
        } catch (InterruptedException e) {
            log.error("myCallableExceptionFutureResult InterruptedException happened");
            e.printStackTrace();
        } catch (ExecutionException e) {
            log.error("myCallableExceptionFutureResult ExecutionException happened");
            e.printStackTrace();
        }
        pool.shutdown();
    }

    static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            log.info("线程-> {} --> 执行call()方法", Thread.currentThread().getName());
            return "MyCallable you get call result!";
        }
    }

    static class MyCallableThrowException implements Callable<String> {
        @Override
        public String call() throws Exception {
            log.info("线程-> {} --> 执行call()方法", Thread.currentThread().getName());
            Integer.valueOf("hello call error");
            return "MyCallableThrowException you get call result!";
        }
    }

}

执行结果:

16:50:24.018 [pool-1-thread-1] INFO com.demo.test.ThreadExecuteDemo - 执行 execute run()方法
16:50:24.018 [pool-1-thread-3] INFO com.demo.test.ThreadExecuteDemo - 线程-> pool-1-thread-3 --> 执行call()方法
16:50:24.018 [pool-1-thread-2] INFO com.demo.test.ThreadExecuteDemo - 执行 execute run()方法 迎接异常
16:50:24.026 [main] INFO com.demo.test.ThreadExecuteDemo - myCallableResult ---> MyCallable you get call result!
16:50:24.028 [pool-1-thread-5] INFO com.demo.test.ThreadExecuteDemo - 线程-> pool-1-thread-5 --> 执行call()方法
16:50:24.028 [main] ERROR com.demo.test.ThreadExecuteDemo - myCallableExceptionFutureResult ExecutionException happened
Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: For input string: "hello error"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.base/java.lang.Integer.parseInt(Integer.java:652)
	at java.base/java.lang.Integer.valueOf(Integer.java:983)
	at com.demo.test.ThreadExecuteDemo.lambda$main$1(ThreadExecuteDemo.java:17)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "hello call error"
	at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
	at com.demo.test.ThreadExecuteDemo.main(ThreadExecuteDemo.java:34)
Caused by: java.lang.NumberFormatException: For input string: "hello call error"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.base/java.lang.Integer.parseInt(Integer.java:652)
	at java.base/java.lang.Integer.valueOf(Integer.java:983)
	at com.demo.test.ThreadExecuteDemo$MyCallableThrowException.call(ThreadExecuteDemo.java:58)
	at com.demo.test.ThreadExecuteDemo$MyCallableThrowException.call(ThreadExecuteDemo.java:54)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)

Process finished with exit code 0

3.4、 监控方法

 

  • getTaskCount(): 线程池已执行和未执行的任务总数。
  • getCompletedTaskCount(): 已完成的任务数量。
  • getPoolSize(): 线程池当前的线程数量。
  • getActiveCount(): 当前线程池中正在执行任务的线程数量。
     
package com.icao;

import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;

/**
 * @author
 * @title: NewCachedThreadPoolExample
 * @description: TODO
 * @date 2020/4/16 9:30
 */
@Slf4j
public class NewCachedThreadPoolExample {

    public static Integer clientCount = 5000;
    public static Integer threadCount = 200;
    public static List<Integer> list = new Vector<>();
    
    public static void main(String[] args) throws Exception {
        // 声明 newCachedThreadPool 线程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
        final Semaphore semaphore = new Semaphore(threadCount);
        final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
        for ( int i = 0; i<clientCount; i++) {
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try { 
                        semaphore.acquire();
                        add();
                        /** 线程监控 log - start**/
                       // 当前线程池中正在执行任务的线程数量
                        int activeCount =
                               ((ThreadPoolExecutor)newCachedThreadPool).getActiveCount();
                        log.info("ActiveCount={}",activeCount);
                        // 线程池已执行和未执行的任务总数
                        long taskCount = 
                               ((ThreadPoolExecutor)newCachedThreadPool).getTaskCount();
                        log.info("taskCount={}",taskCount);
                        /** 线程监控 log - end**/
                        semaphore.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("exception", e);
                    }
                    // //将计数值减1
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        newCachedThreadPool.shutdown();
        log.info("list.size() = {}",list.size());
        // 线程池当前的线程数量
        int poolSize = 
               ((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
        log.info("poolSize={}",poolSize);
        // 已完成的任务数量。
        long completedTaskCount =
                ((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
        log.info("completedTaskCount={}",completedTaskCount);
    }

    private static void add() {
      list.add(0) ;
    }
}

 

3.5、Executors 提供四种线程池

 

  • Executors.newCachedThreadPool
  • Executors.newFixedThreadPool
  • Executors.newScheduledThreadPool
  • Executors.newSingleThreadExecutor

 

3.5.1 newCachedThreadPool

 
Executors.newCachedThreadPool :
 
       创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
 

newCachedThreadPool 线程池特点是:
 
1、工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE),?这样可灵活的往线程池中添加线程。
 
2、如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
 
3、在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

代码模拟高并发:

package com.icao;

import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.*;

/**
 * @author 
 * @title: NewCachedThreadPoolExample
 * @description: TODO
 * @date 2020/4/16 9:30
 */
@Slf4j
public class NewCachedThreadPoolExample {

    public static Integer clientCount = 5000;

    public static Integer threadCount = 200;

    public static List<Integer> list = new Vector<>();

    public static void main(String[] args) throws Exception {
        long startMili=System.currentTimeMillis();// 当前时间对应的毫秒数
        log.info("/**开始 "+startMili);
        // 声明 newCachedThreadPool 线程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
        final Semaphore semaphore = new Semaphore(threadCount);
        // countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
        // 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
        final CountDownLatch countDownLatch = new CountDownLatch(clientCount);
        for ( int i = 0; i<clientCount; i++) {
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //   方法 acquire( int permits ) 参数作用,及动态添加 permits 许可数量  
                        //  acquire( int permits ) 中的参数可以这么理解, new Semaphore(6) 表示初始化了 6个通路, semaphore.acquire(2) 表示每次线程进入将会占用2个通路,semaphore.release(2) 运行时表示归还2个通路。没有通路,则线程就无法进入代码块。
                        //  而semaphore.acquire() +  semaphore.release()  在运行的时候,其实和 semaphore.acquire(1) + semaphore.release(1)  效果是一样的。  
                        semaphore.acquire();
                        add();
                        log.info("CurrentThreadId()={},list.size()={}",
                                Thread.currentThread().getId(),list.size());
                        semaphore.release();
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error("exception", e);
                    }
                    // //将计数值减1
                    countDownLatch.countDown();
                }
            });
        }
        // 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
        countDownLatch.await();

        newCachedThreadPool.shutdown();
        // 这里使用countDownLatch 是为了保证最后执行 log.info("list.size() = {}",list.size());
        log.info("list.size() = {}",list.size());
        //这里加入需要测试的代码
        long endMili=System.currentTimeMillis();//结束时间
        log.info("/**结束 s"+endMili);
        log.info("/**总耗时为:"+(endMili-startMili)+"毫秒");
        // 线程池当前的线程数量
        int poolSize =
               ((ThreadPoolExecutor)newCachedThreadPool).getPoolSize();
        log.info("poolSize={}",poolSize);
        // 已完成的任务数量。
        long completedTaskCount =
               ((ThreadPoolExecutor)newCachedThreadPool).getCompletedTaskCount();
        log.info("completedTaskCount={}",completedTaskCount);
    }

    private static void add() {
      list.add(0) ;
    }
}

 

3.5.2 newFixedThreadPool

 
Executors.newFixedThreadPool :
 
       创建固定大小(定长)的线程池,可控制线程最大并发数。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,超出的线程会在队列中等待。如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
 

3.5.3 newScheduledThreadPool

 
Executors.newScheduledThreadPool :
 
       创建一个定长线程池,支持定时及周期性任务执行。
 

3.5.4 newSingleThreadPool

 
Executors.newSingleThreadExecutor :
 
       创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它

 

4、确定线程池的线程数

 
虽然使用线程池的好处很多,但是如果其线程数配置得不合理,不仅可能达不到预期效果,反而可能降低应用的性能。确定线程池的线程数,首先需要针对不同类型的异步任务需求,创建不同类型的线程池,并进行针对性的参数配置。

按照任务类型对线程池进行分类

4.1、IO密集型任务

此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率底,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。

对于IO密集型任务,一般开CPU核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。

4.2、CPU密集型任务

此类任务主要是执行计算任务。由于响应时间很快,所以CPU一直在运行,此类任务CPU的利用率很高。

CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以要最高效地利用CPU,CPU密集型任务并行执行的数量应当等于CPU的核心数。比如4核的CPU,开4个线程并行地执行4个CPU密集型任务,此时的效率是最高的。

4.3、混合型任务

此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)。相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。

公式:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) * CPU核数.
例子:

比如在Web服务器处理HTTP请求时,假设平均线程CPU运行时间为100毫秒,而线程等待时间(比如包括DB操作、RPC操作、缓存操作等)为900毫秒,如果CPU核数为8,那么根据上面这个公式,估算如下:

(900毫秒 + 100毫秒) / 100毫秒 * 8 = 10 * 8 = 80

 

5、关闭线程的那些事

 

  • shutdown:是JUC提供的一个有序关闭线程池的方法,此方法会等待当前工作队列中的剩余任务全部执行完成之后,才会执行关闭,但是此方法被调用之后线程池的状态转为SHUTDOWN,线程池不会再接收新的任务。

  • shutdownNow:是JUC提供的一个立即关闭线程池的方法,此方法会打断正在执行的工作线程,并且会清空当前工作队列中的剩余任务,返回的是尚未执行的任务。

  • awaitTermination:等待线程池完成关闭。在调用线程池的shutdown()与shutdownNow()方法时,当前线程会立即返回,不会一直等待直到线程池完成关闭。如果需要等到线程池关闭完成,可以调用awaitTermination()方法。

(1)执行shutdown()方法,拒绝新任务的提交,并等待所有任务有序地执行完毕。
 
(2)执行awaitTermination(long timeout,TimeUnit unit)方法,指定超时时间,判断是否已经关闭所有任务,线程池关闭完成。
 
(3)如果awaitTermination()方法返回false,或者被中断,就调用shutDownNow()方法立即关闭线程池所有任务。
 
(4)补充执行 awaitTermination(long timeout,TimeUnit unit) 方法,判断线程池是否关闭完成。如果超时,就可以进入循环关闭,循环一定的次数(如1000次),不断关闭线程池,直到其关闭或者循环结束
 

 

5.1、优雅的关闭线程

 

代码示例:

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CloseThreadPoolDemo {

    /**
     * 优雅的关闭线程池,该方法可以作为工具类方法
     *
     * @param threadPool
     */
    public static void shutdownThreadPoolGracefully(ExecutorService threadPool) {
        // 若已经关闭则返回
        if (!(threadPool instanceof ExecutorService) || threadPool.isTerminated()) {
            return;
        }
        try {
            //拒绝接受新任务
            threadPool.shutdown();
        } catch (SecurityException e) {
            return;
        } catch (NullPointerException e) {
            return;
        }
        try {
            // 等待60秒,等待线程池中的任务完成执行
            if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
                // 调用 shutdownNow() 方法取消正在执行的任务
                threadPool.shutdownNow();
                // 再次等待60秒,如果还未结束,可以再次尝试,或者直接放弃
                if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
                    log.info("线程池任务未正常执行结束");
                }
            }
        } catch (InterruptedException ie) {
            // 捕获异常,重新调用 shutdownNow() 方法
            threadPool.shutdownNow();
        }
        // 仍然没有关闭,循环关闭1000次,每次等待10毫秒
        if (!threadPool.isTerminated()) {
            try {
                for (int i = 0; i < 1000; i++) {
                    if (threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
                        break;
                    }
                    threadPool.shutdownNow();
                }
            } catch (InterruptedException e) {
                log.error(e.getMessage());
            } catch (Throwable e) {
                log.error(e.getMessage());
            }
        }
    }


    static class MyThread extends Thread {

        public MyThread(String theadName) {
            super(theadName);
        }

        @Override
        public void run() {
            log.info("执行run() start");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("执行run() end");
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            pool.execute(new MyThread("MyThread" + i));
        }
        shutdownThreadPoolGracefully(pool);
        for (int i = 5; i < 10; i++) {
            pool.execute(new MyThread("MyThread" + i));
        }
    }

}

执行结果:

17:05:13.511 [pool-1-thread-2] INFO com.demo.test.CloseThreadPoolDemo - 执行run() start
17:05:13.511 [pool-1-thread-3] INFO com.demo.test.CloseThreadPoolDemo - 执行run() start
17:05:13.511 [pool-1-thread-4] INFO com.demo.test.CloseThreadPoolDemo - 执行run() start
17:05:13.511 [pool-1-thread-5] INFO com.demo.test.CloseThreadPoolDemo - 执行run() start
17:05:13.511 [pool-1-thread-1] INFO com.demo.test.CloseThreadPoolDemo - 执行run() start
17:05:18.522 [pool-1-thread-4] INFO com.demo.test.CloseThreadPoolDemo - 执行run() end
17:05:18.522 [pool-1-thread-3] INFO com.demo.test.CloseThreadPoolDemo - 执行run() end
17:05:18.522 [pool-1-thread-2] INFO com.demo.test.CloseThreadPoolDemo - 执行run() end
17:05:18.522 [pool-1-thread-1] INFO com.demo.test.CloseThreadPoolDemo - 执行run() end
17:05:18.522 [pool-1-thread-5] INFO com.demo.test.CloseThreadPoolDemo - 执行run() end
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task Thread[MyThread5,5,main] rejected from java.util.concurrent.ThreadPoolExecutor@26a7b76d[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 5]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2055)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:825)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1355)
	at com.demo.test.CloseThreadPoolDemo.main(CloseThreadPoolDemo.java:83)

Process finished with exit code 1

 
 
 
线程系列博文:
 
线程系列 1 - 线程基础
线程系列 3 - 关于 CompletableFuture
线程系列 4 - synchronized 和线程间的通信
线程系列 5 - CAS 和 JUC原子类
线程系列 6 - JUC相关的显示锁
线程系列 7 - JUC高并发容器类
 
 
 
 
 
.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值