对于线程池ThreadPool的学习总结
线程池:就是一个管理线程的池子。
优点:
- 它帮我们管理线程,避免增加创建线程和销毁线程的资源损耗。因为线程其实也是一个对象,创建一个对象,需要经过类加载过程,销毁一个对象,需要走GC垃圾回收流程,都是需要资源开销的。
- 提高响应速度。 如果任务到达了,相对于从线程池拿线程,重新去创建一条线程执行,速度肯定慢很多。
- 重复利用。 线程用完,再放回池子,可以达到重复利用的效果,节省资源。
一、线程池的创建
线程池可以通过ThreadPoolExecutor来创建
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) //corePoolSize: 线程池核心线程数最大值 //maximumPoolSize: 线程池最大线程数大小 //keepAliveTime: 线程池中非核心线程空闲的存活时间大小 //unit: 线程空闲存活时间单位 //workQueue: 存放任务的阻塞队列 //threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。 //handler: 线城池的饱和策略事件,主要有四种类型。
二、线程池处理流程
1,首先线程池判断基本线程池是否已满(< corePoolSize ?)?没满,创建一个工作线程来执行任务。满了,则进入下个流程。
2,其次线程池判断阻塞队列是否已满?没满,则将新提交的任务存储在阻塞队列里。满了,则进入下个流程。
3,最后线程池判断整个线程池是否已满(< maximumPoolSize ?)?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。
三、线程池的阻塞队列
线程池阻塞队列:1、有界阻塞队列 2、无界阻塞队列 3、同步移交阻塞队列
public class QueueTest { @Test public void arrayBlockingQueue() throws InterruptedException { /** * 基于数组的有界阻塞队列,队列容量为10 */ ArrayBlockingQueue queue = new ArrayBlockingQueue<Integer>(10); // 循环向队列添加元素 for (int i = 0; i < 20; i++) { queue.put(i); System.out.println("向队列中添加值:" + i); } } @Test public void linkedBlockingQueue() throws InterruptedException { /** * 基于链表的有界/无界阻塞队列,队列容量为10 */ LinkedBlockingQueue queue = new LinkedBlockingQueue<Integer>(); // 循环向队列添加元素 for (int i = 0; i < 20; i++) { queue.put(i); System.out.println("向队列中添加值:" + i); } } @Test public void test() throws InterruptedException { /** * 同步移交阻塞队列 */ SynchronousQueue queue = new SynchronousQueue<Integer>(); // 插入值 new Thread(() -> { try { queue.put(1); System.out.println("插入成功"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 删除值 new Thread(() -> { try { queue.take(); System.out.println("删除成功"); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); Thread.sleep(1000L * 60); } }
四、线程池饱和策略
- Caller-runs调用者运行策略,即该线程的执行有调用的这个线程来执行,调用的线程会暂停原来的任务,转而去执行该任务,该任务执行完成后继续执行原来的任务
- AbortPolicy终止策略(系统默认), 该策略保证在线程池满的情况下任何试图提交任务到该线程池的线程的线程均会抛出,RejectedExecutionException,该异常导致调用线程的终止。但这种异常时可以捕获的(非检查型异常)。
- DiscardPolicy抛弃策略,该策略保证任何试图向满的线程池提交任务时,该任务的提交会立即返回,任务不会被提交,并且不会抛出任何形式的异常。
- DiscardOldestPolicy抛弃旧任务策略,这种模式下,将会抛弃下一个将要执行的任务,然后把刚提交的任务添加到任务队列,等待执行。
五、线程池的常用线程池
- newCachedThreadPool (创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。)
public static ExecutorService newCachedThreadPool(){ return new ThreadPoolExecutor( 0, // corePoolSoze == 0 Integer.MAX_VALUE, // maximumPoolSize 非常大 60L, // 空闲判定是60 秒 TimeUnit.SECONDS, // 神奇的无存储空间阻塞队列,每个 put 必须要等待一个 take new SynchronousQueue<Runnable>() ); }
-
newFixedThreadPool (创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。)
public static ExecutorService newFixedThreadPool(int nThreads){ return new ThreadPoolExecutor( nThreads, // corePoolSize nThreads, // maximumPoolSize == corePoolSize 0L, // 空闲时间限制是 0 TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>() // 无界阻塞队列 ); }
-
newSingleThreadPool (创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。)
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService ( new ThreadPoolExecutor ( 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory ) ); }
-
newScheduleThreadPool (创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
六、向线程池提交任务
提交任务有两种方式:1、有返回结果-sunmit 2、无返回结果-execute
public class RunTest { @Test public void submitTest() throws ExecutionException, InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newCachedThreadPool(); /** * 利用submit方法提交任务,接收任务的返回结果 */ Future<Integer> future = threadPool.submit(() -> { Thread.sleep(1000L * 10); return 2 * 5; }); /** * 阻塞方法,直到任务有返回值后,才向下执行 */ Integer num = future.get(); System.out.println("执行结果:" + num); } @Test public void executeTest() throws InterruptedException { // 创建线程池 ExecutorService threadPool = Executors.newCachedThreadPool(); /** * 利用execute方法提交任务,没有返回结果 */ threadPool.execute(() -> { try { Thread.sleep(1000L * 10); } catch (InterruptedException e) { e.printStackTrace(); } Integer num = 2 * 5; System.out.println("执行结果:" + num); }); Thread.sleep(1000L * 1000); } }
关于线程池五种状态:
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED.
1、RUNNING
(1). 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2). 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
2、 SHUTDOWN
(1). 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2). 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3、STOP
(1). 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2). 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1). 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。
当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;
可以通过重载terminated()函数来实现。
(2). 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5、 TERMINATED
(1). 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2). 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
最后实践Springboot中线程池的使用
@Configuration @EnableAsync public class ExecutorConfig { @Bean("exportServiceExecutor") public Executor exportServiceExecutor(){ //创建线程池 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程数量:当前机器核心数 executor.setCorePoolSize ( Runtime.getRuntime().availableProcessors()); //最大线程数 executor.setMaxPoolSize( Runtime.getRuntime().availableProcessors() * 2); //队列大小 executor.setQueueCapacity(Integer.MAX_VALUE); //线程池中线程名前缀 executor.setThreadNamePrefix("export-"); //线程池阻塞策略-拒绝策略:直接拒绝 executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy()); return executor; } }
@Async("exportServiceExecutor") //指定线程池 public void executorTest(){ System.out.println("测试线程"); }