为什么要用线程池
基本的三个线程池的底层就是ThreadPoolExecutor类
ExecutorService threadPool = Executors.newFixedThreadPool(int);//固定线程数的线程池,,执行长期的任务,性能好
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单线程的线程池,,一个任务一个任务执行
ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N线程,,,执行很多短期异步的任务
Executors是工具类,类似Arrays、Collections
底层ThreadPoolExecutor的重要参数:7大参数(面试一般问前5个)
- int corePoolSize, 线程池中的常驻核心线程数
- int maximumPoolSize, 线程池能够容纳同时执行的最大线程数,此值必须大于等于1(当核心线程数满了并且阻塞队列的线程数也满了,那么就会进行扩容,最大值是maximumPoolSize)
- long keepAliveTime, 多余的空闲线程的存活时间,当前线程池数量超过corePoolSize时,
当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩corePoolSize个线程为止。 - TimeUnit unit, keepAliveTime的单位
- BlockingQueue workQueue, 任务队列,被提交但尚未被执行的任务。(候客区)
- ThreadFactory threadFactory, 表示生成线程池中的工作线程的线程工厂,用于创建线程一般用默认的即可
- RejectedExecutionHandler handler 拒绝策略,表示当前队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略
当空闲线程空闲的时间超过keepAliveTime,就会被销毁,但是最少会存在corePoolsize数个线程,如下
线程池的底层工作原理(七大参数如何互动?)
文字讲解
- 在创建线程池后,等待提交过来的任务请求。
- 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
2.2 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
2.2 如果正在运行的线程数量大于corePoolSize,那么将这个任务加入阻塞队列
2.3 如果这时候阻塞队列满了且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻执行这个任务。
2.4 如果阻塞队列满了且正在运行的线程数大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略(RejectedExecutionHandler)来执行。 - 当一个线程完成任务时,他会从阻塞队列中取出下一个任务执行。
- 当一个线程无事可做超过keepAliveTime时,线程池会判断:
当运行的线程数大于corePoolSize,那么这个线程就被叫停。
所以线程池的所有任务完成后它的线程数最终会收缩到corePoolSize的数量。
package JUC;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** 线程池*/
public class MyThreadPoolDemo {public static void main(String []args){//System.out.println(Runtime.getRuntime().availableProcessors());//查看cup核数//线程池的底层就是ThreadPoolExecutor类//Executors是工具类//newScheduledThreadPool带时间的//java获得线程的方法有4种,继承Thread类、实现Runable(没有返回值,不会抛异常,重新run()方法)、实现Callable(有返回值,会抛异常,重新call()方法)、线程池(ThreadPoolExecutor)//池化不用newExecutorService threadPool = Executors.newFixedThreadPool(5);//固定线程数的线程池,,执行长期的任务,性能好//ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单线程的线程池,,一个任务一个任务执行//ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N线程,,,执行很多短期异步的任务//模拟10用户来办理业务,每个用户就是一个外部的请求线程,线程池里的线程是工作人员try{for (int i = 0; i < 10 ; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t办理业务");});//TimeUnit.MILLISECONDS.sleep(200);//在Cache线程池里,如果每次暂停0.2秒那么一个线程就可以处理过了,所以每次都是同一个线程,去掉延时就不是一个线程了}}catch (Exception e){e.printStackTrace();}finally {threadPool.shutdown();}}
}
线程池的拒绝策略RejectedExecutionHandler(四种)
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛出任务,也不会抛出异常,而是将某些任务回退到调用调用者,从而降低新的任务流量。()
- DiscardOldestPolicy:抛出队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
那么实际的高并发下使用哪个线程池?(面试题)
以上三种高并发下都不会使用:(阿里java开发手册)
1)第一个图片可以看到newFixedThreadPool和SingleThreadPool的阻塞队列用的是LinkedBlockingQueue,Linked底层是链表,Integer.MAX_VALUE接近21亿,请求可以一直加到阻塞队列里,那么太多就会OOM。
2)CachedThreadPool和ScheduledThreadPool的第二个参数也就是最大线程数是Integer.MAX_VALUE,可能创建大量线程,导致OOM
接下来继续测试下每个拒绝策略:(自定义线程池后)
这是自定义的线程池:可以看到核心线程数为2,最大线程数为5,阻塞队列为3,那么允许请求的最大线程数为5+3=8个
// 自定义线程池ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
1.AbortPolicy():直接抛出RejectedExecutionException异常阻止系统正常运行
public class MyThreadPoolDemo {public static void main(String []args){// 自定义线程池ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());try{for (int i = 1; i <= 10 ; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t办理业务");});//TimeUnit.MILLISECONDS.sleep(200);//在Cache线程池里,如果每次暂停0.2秒那么一个线程就可以处理过了,所以每次都是同一个线程,去掉延时就不是一个线程了}}catch (Exception e){e.printStackTrace();}finally {threadPool.shutdown();}}
结果:可以看到会抛异常。
2.CallerRunsPolicy():“调用者运行”一种调节机制,该策略既不会抛出任务,也不会抛出异常,而是将某些任务回退到调用调用者,从而降低新的任务流量。()
public class MyThreadPoolDemo {public static void main(String []args){// 自定义线程池ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());try{for (int i = 1; i <= 9 ; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t办理业务");});//TimeUnit.MILLISECONDS.sleep(200);//在Cache线程池里,如果每次暂停0.2秒那么一个线程就可以处理过了,所以每次都是同一个线程,去掉延时就不是一个线程了}}catch (Exception e){e.printStackTrace();}finally {threadPool.shutdown();}}
结果:可以看到多出来的会回退给main线程,(这里可能出现1个main或者没有main,因为线程执行的太快,阻塞队列的已经被执行到了,所以可能有偏差)
3.DiscardOldestPolicy() :抛出队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
public static void main(String []args){// 自定义线程池ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());try{for (int i = 1; i <= 10 ; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t办理业务");});//TimeUnit.MILLISECONDS.sleep(200);//在Cache线程池里,如果每次暂停0.2秒那么一个线程就可以处理过了,所以每次都是同一个线程,去掉延时就不是一个线程了}}catch (Exception e){e.printStackTrace();}finally {threadPool.shutdown();}}
4.DiscardPolicy() :直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
public static void main(String []args){// 自定义线程池ExecutorService threadPool = new ThreadPoolExecutor(2,5,1L,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());try{for (int i = 1; i <= 10 ; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"\t办理业务");});//TimeUnit.MILLISECONDS.sleep(200);//在Cache线程池里,如果每次暂停0.2秒那么一个线程就可以处理过了,所以每次都是同一个线程,去掉延时就不是一个线程了}}catch (Exception e){e.printStackTrace();}finally {threadPool.shutdown();}}
那么实际生产中,如何配置自定义线程池的参数呢?
1.CUP密集型:
2.IO密集型
2.1.
2.2