1.线程池简介
2.线程池应用
3.线程池参数详解
4.如何正当设置线程池参数
1.线程池简介
咱们先来介绍一下,什么是线程池?
在以往的博客中,咱们介绍线程的时候,无论是Runnable,还是Calllable这些,最终都是应用new Thread().start(); 的形式进行创立,其实在实在的生产环境中,咱们是禁止应用这种写法的,因为这样有几个重大的弊病:
a.每次new Thread() 新建对象,会造成开销,导致服务器运行性能变差。
b.在无限度new Thread()的状况下,咱们一不小心就会创立过多的线程,相互之间竞争资源,可能导致上下文频繁切换,占用资源过多,死锁或者oom。
c. 不足更多功能,如定时执行、定期执行、线程中断。
于是,JAVA线程池就产生了!
JAVA线程池做的次要工作是管制运行的线程的数量,解决过程中将工作放入队列而后在线程创立后启动这些工作,如果线程数量超过了最大执行数量,超出数量的线程将排队等待,期待其它线程执行结束,再从队列中取出工作来执行。
它的次要特点:线程服用,管制最大并发数,治理线程。
第一:升高资源耗费,通过反复利用本人创立的线程升高线程创立和销毁造成的耗费。
第二:进步响应速度,当工作达到时,工作能够不须要等到线程创立就能立刻执行
第三:进步线程的可管理性,线程是稀缺资源,如果无限度地创立,不仅会耗费系统资源,还会升高零碎的稳定性,应用线程池能够对立地进行调配,调优和监控。
多说无用,咱们来看看如何应用JAVA线程池吧!
2.线程池应用
咱们先介绍一下JAVA自带的创立线程池的工具类!
Executors
它提供了三个办法来创立线程池:
ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池固定数线程 ExecutorService threadPoo2 = Executors.newCachedThreadPool();//一池多线程 ExecutorService threadPoo3 = Executors.newSingleThreadExecutor();//一池一线程
顾名思义:
newFixedThreadPool()办法就是创立一个固定线程数的线程池,
newCachedThreadPool()办法会依据你计算机和工作的状况,进行主动增减线程
newSingleThreadExecutor()创立一个单线程的线程池,每次只能执行一个工作
同时创立完线程池之后,应用线程池自带的submit或者excute办法,而后用lambda表达式传入函数体后,就能够执行工作了!
threadPool.submit(); threadPool.execute();
这时,咱们能够应用一个线程池尝试着应用一下
ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池固定数线程 //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程 try { for (int i = 1; i < 10000; i++) { threadPool.submit(() -> { System.out.println(Thread.currentThread().getName() + "正在办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }
咱们创立了一个线程池,固定数量为5,而后启动10000个线程,看看运行状况。
能够看出,线程编号始终没有超过咱们设置的初始值5。
这时,咱们能够换一个线程池应用一下,咱们应用cache线程池:
ExecutorService threadPoo1 = Executors.newCachedThreadPool();//一池多线程 //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程 try { for (int i = 1; i < 10000; i++) { threadPoo1.submit(() -> { System.out.println(Thread.currentThread().getName() + "正在办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoo1.shutdown(); }
能够看出,线程启动地越多,该线程池就会启动越多的线程来适应需要。
让咱们来看一下最初一个线程池:
ExecutorService threadPoo1 = Executors.newSingleThreadExecutor();//一池一线程 //模仿10000个用户来办理业务,每一个用户就是来自内部的申请线程 try { for (int i = 1; i < 10000; i++) { threadPoo1.submit(() -> { System.out.println(Thread.currentThread().getName() + "正在办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoo1.shutdown(); }
查看后果:
能够看出,永远只有一个线程在运行。
是不是十分神奇?!咱们能够看一下创立线程池的办法到底怎么执行地,竟然有这种成果!
首先看一下固定数线程的办法:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, //常驻外围线程数 nThreads,//最大线程数 0L,//多余的闲暇线程的存活工夫 TimeUnit.MILLISECONDS,//多余线程存活单位 new LinkedBlockingQueue<Runnable>()); //应用的阻塞队列 }
cache线程池的办法:
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
单线程池的办法:
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
从我给的源码正文中能够看出,
固定数线程池最大和常驻线程数都是传入的参数
而cache线程池的最大线程数是Integer.MAX_VALUE
单线程池底层应用了SynchronousQueue : 不存储元素的阻塞队列,即单个元素的队列。
多线程池的底层应用了LinkedBlockingQueue : 由链表组成的有界(然而默认值为Integer.MAX_VALUE阻塞队列)
阻塞队列能够看我之前写的这篇文章:JAVA并发编程——阻塞队列实践及其API介绍
所以就导致了为什么会有以上的运行后果!
所以在生产中,咱们到底如何应用呢??
答案是————一个都不应用,因为LinkedBlockingQueue它的最大长度是Integer.MAX_VALUE,那就相当于无界,可能会让服务器内存爆仓产生OOM,所以接下来,咱们学习一下自定义创立线程池的参数!
3.线程池参数详解
咱们先点到ThreadPoolExecutor这个类里看一下,它到底有几个参数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
咱们能够看到,有七个参数
corePoolSize 线程池中常驻外围线程数
maximumPoolSize 线程池可能包容的同时执行的最大线程数,此值必须大于等于1
keppAliveTime 多余的闲暇线程的存活工夫
unit 存活工夫单位
workQueue 工作队列,被提交然而尚未执行的工作
threadFactory 示意生成线程池中工作线程的线程工厂,用于创立线程,个别默认即可
handler 回绝策略,示意队列满了并且工作线程大于等于线程池的最大线程数,如何解决。
后面几个参数都好了解,重要的是这个办法能够自定义阻塞队列的类型,咱们就能够防止应用LinkedBlockingQueue了。
接下来,咱们看一下handler回绝策略:
当外围线程数满了,阻塞队列也满了的时候,再进来的线程,就会启动回绝策略,个别常见的回绝策略有这几种:
AbortPolicy(默认):间接抛出RejectExcutionException异样阻止线程运行。
CallerRunsPolicy:将多进去的工作退还给调用者,从而升高流量。
DiscardOldestPolicy:摈弃期待队列中期待最久的工作,而后把当前任务退出期待队列。
DiscardPolicy:间接抛弃工作。
接下来,咱们尝试一下本人新建线程池,并尝试着应用该线程池执行一下工作。
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//常驻外围线程数 5,//最大外围线程数 1L,//多余的闲暇线程的存活工夫 TimeUnit.SECONDS,//多余闲暇线程存活工夫单位 new ArrayBlockingQueue<Runnable>(10),//阻塞队列 Executors.defaultThreadFactory(),//生成线程池中工作线程的线程工厂 new ThreadPoolExecutor.AbortPolicy());//回绝策略 //模仿多个用户来办理业务,每一个用户就是来自内部的申请线程 try { for (int i = 1; i < 10000; i++) { threadPoolExecutor.submit(() -> { System.out.println(Thread.currentThread().getName() + "正在办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPoolExecutor.shutdown(); } }
在应用以后回绝策略下,很显著能够看出,执行线程超出线程池的数量了,会抛异样,咱们来看一下运行后果:
咱们能够换一个回绝策略试一下:比如说DiscardPolicy(摈弃工作)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,//常驻外围线程数 5,//最大外围线程数 1L,//多余的闲暇线程的存活工夫 TimeUnit.SECONDS,//多余闲暇线程存活工夫单位 new ArrayBlockingQueue<Runnable>(10),//阻塞队列 Executors.defaultThreadFactory(),//生成线程池中工作线程的线程工厂 new ThreadPoolExecutor.DiscardPolicy());//回绝策略
它就运行实现了,且没有抛出任何异样。
4.如何正当设置线程池参数
如何配置线程池的参数,其实咱们最须要配置的参数只有一个:
corePoolSize 线程池中常驻外围线程数
当咱们的线程池应用场景是在CPU密集型(过程绝大部份工作依附cpu的计算能力实现),那么为了避免上下文的切换造成的开销,咱们个别设置最大线程数等于CPU核数(核数获取办法:Runtime.getRuntime().availableProcessors();)
当咱们的线程池应用场景是在IO密集型(绝大部分工作就是在读入,输入数据),那么cpu不是十分十分忙碌,大部分在期待的状况下,最大线程能够是CPU核数的两倍。
5.总结
这次咱们介绍了线程池的原理以及参数详解,最重要的是,创立线程池必须要手动应用构造方法创立,不能应用默认的办法类进行创立!
最初,咱们用两张图来概括一下线程池的执行流程:
好了,明天的分享就到这里!