有序的线程池(java线程池流程)
线程池Executor框架
Executor采用工厂模式提供了各种类型的线程池,是实际使用中我们就直接从Executor中获取我们想要的线程池,拿来直接使用即可 。下面简单的介绍下Executor提供的五大类线程池 。
newFixedThreadPool()方法:该方法返回一个固定数量的线程池;
newSingleThreadExecutor()方法:该方法返回只有一个线程的线程池;
newCachedThreadPool()方法:该方法返回一个可根据实际情况调整线程数量的线程池;
newScheduledThreadPool()方法:该方法返回一个ScheduleExecutorService对象,可以指定线程数量;
newSingleThreadScheduledExecutor()方法:该方法返回一个ScheduleExecutorService对象,线程数量为1;
【有序的线程池(java线程池流程)】 上面线程池分两大类,一类是无计划的任务,提交就会执行,这类业务应用很广泛;另一类是有计划的任务,提交后会按照设定的规则去执行,这种应用的场景相对少一些,不过对有些业务是必须的,比如:我们系统晚上需要清空用户的状态、优惠券到期了自动提醒等等,用到的就是这类计划任务,常见的有spring task 。下面分别举例演示两种线程池的使用 。
1、newFixedThreadPool()固定大小的线程池
package concurrent.threadpool;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class FixedThreadDemo {public static void main(String args[]) {// 创建任务对象MyTask task = new MyTask();// 创建固定数量线程池ExecutorService es = Executors.newFixedThreadPool(5);for(int i=0; i<10; i++) {// 向线程池里提交任务es.submit(task);}}public static class MyTask implements Runnable {@Overridepublic void run() {System.out.println(System.currentTimeMillis() + ": Thread ID: " + Thread.currentThread().getId());try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}} } 2、newScheduledThreadPool()计划任务
package concurrent.threadpool;import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;public class ScheduleThreadDemo {public static void main(String args[]) {// 创建任务对象MyTask task = new MyTask();// 创建任务调度计划对象ScheduledExecutorService ses = Executors.newScheduledThreadPool(100);// 设置任务与执行计划ses.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);}public static class MyTask implements Runnable {@Overridepublic void run() {System.out.println(System.currentTimeMillis()/1000 + ": Thread ID: " + Thread.currentThread().getId());try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}} } 大家可以看的出来这两段代码都非常简单,都是创建任务对象、获取Executors提供的线程池对象、将任务对象绑定到线程池对象上 。通过Executors提供的不同策略的对象,就能快速实现我们对线程的控制 。
线程池的内部实现
Executor为我们提供了功能各异的线程池,其实其内部均是由ThreadPoolExecutor实现的,我们详细了解下ThreadPoolExecutor实现原理不但对我们使用理解Executor提供的线程池大有帮助,也让我们能根据实际情况自定义特定的线程池 。
首先介绍下ThreadPoolExecutor类最核心的构造方法,
public ThreadPoolExecutor(int corePoolSize, // 指定线程池中线程的数量(总线程量可大于等于这个值)int maximumPoolSize, // 指定线程池中最大线程数量(总线程量不可能超越这个数值)Long keepAliveTime, // 超过corePoolSize数量的空闲线程,存活的时间TimeUtil unit, // keepAliveTime的单位BlockingQueue<Runnable> workQueue, // 任务队列,被提交但为执行的任务ThreadFactory threadFactory, // 线程工厂,用于创建线程RejectedExecutionHandler handler // 当workQueue队列满的时候的拒绝策略) 看到corePoolSize和maximumPoolSize的含义,应该很容易通过设置参数的不同,得到Executors提供的线程池对象 。该方法一共七个参数,前四个很简单,我们都会使用,第六个一般使用的是JDK默认提供的,剩下的就只有workQueue和handler了 。workQueue:存放的是提交的任务,例如:es.submit(task);在样例中提交了10次task,但线程只有5个,于是就有5个提交但没开始执行的任务存到了workQueue里啦 。既然是一个存放任务的队列,我们知道实现队列的方式有多种,比如:ArrayBlockQueue、LinkedBlockQueue、PriorityBlockQueue等等,选择不同的队列就会带来不同的问题 。ArrayBlockQueue,存在一个任务过多超出队列长度;LinkedBlockQueue,接受过多的任务可能会占用太多内存,造成内存崩溃等等 。这里介绍下,newFixedThreadPool和newSingleFixedThreadPool使用的都是LinkedBlockQueue,newCacheThreadExecutor使用的SynchronousQueue队列 。关于队列的选择是要根据实际情况来确定,这也是自定义线程池的核心 。
handler:拒绝策略,实际上是一种补救措施,就是当超出了workQueue临界值了,我们怎么让我们的系统不至于崩溃 。JDK内置的处理方法有AbortPolicy,抛出异常阻止程序(除非是安全性要求极高,否则在大并发情况下使用这种做法不是很明智);DiscardPolicy,丢弃无法处理的任务(如果允许丢弃,这是不错方案);DiscardOledesPolicy:也是丢弃任务,只不过丢弃的是队列最前的一个任务 。由于上面策略都是实现RejectExecutionHandler接口,我们也可以实现该接口自定义拒绝策略 。
自定义线程创建
package concurrent.threadpool;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class ThreadPoolDemo {public static void main(String args[]) {// 创建任务对象MyTask task = new MyTask();// 获取自定义线程池ExecutorService es = getMyThreadPool();for(int i=0; i<20; i++) {// 向线程池提交任务es.submit(task);}}// 自定义线程池,我们创建一个线程数固定的线程池public static ExecutorService getMyThreadPool() {ExecutorService es = new ThreadPoolExecutor(// 设置线程池大小5, 5, 0L, TimeUnit.MILLISECONDS,// 设置缓存队列new LinkedBlockingQueue<Runnable>(5),// 设置线程工厂Executors.defaultThreadFactory(),// 设置拒绝策略new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println(r.toString() + " is discard! "); // 输出日志后直接丢弃任务}});return es;}public static class MyTask implements Runnable {@Overridepublic void run() {System.out.println(System.currentTimeMillis() + ": Thread ID: " + Thread.currentThread().getId());try {Thread.sleep(1000);}catch (InterruptedException e) {e.printStackTrace();}}} } 从上面程序的运行结果我们可以看到,10任务被执行(因为线程池有5个线程,缓存队列也能缓存5个),10任务被丢弃,符合我们预期 。这个样例十分简单,只是通过这个样例展示怎么去自定义一个线程池,具体的线程池定义,我们要根据实际情况,设置传入的参数即可 。
Fork/Join框架
上面我们详细介绍了线程池的原理,还是那句话,学底层原理是拿来做设计,并不是让直接去使用 。Fork/Join在线程池的基础上,做了更近一步的封装,对线程的开启、分发做了优化,使系统更稳定 。另外补充下,Fork/Join还涉及到关于多线程的一个重要思想:“分而治之”,通俗的将就是将复杂的问题拆分成多个简单问题,分开处理 。下面通过一段样例了解下Fork/Join 。
package concurrent.threadpool;import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; import java.util.concurrent.RecursiveTask;public class ForkJoinDemo extends RecursiveTask<Long> {private static final int THRESHOLD = 10000; // 门阀值,当大于这个值才进行拆分处理private long start; // 数列的起始值private long end; // 数列的结束值public ForkJoinDemo(long start, long end) {this.start = start;this.end = end;}// 对数列进行求和@Overrideprotected Long compute() {// 定义求和对象long sum = 0;if((end - start) < THRESHOLD) {// 当求和数列的数量小于门阀值,则直接计算不需要分拆for(long i=start; i<=end; i++) {sum += i;}}else {// 当求和数列的数量大于门阀值,则拆分成100个小任务ArrayList<ForkJoinDemo> subTasks = new ArrayList<ForkJoinDemo>();long step = (start + end) / 100; // 计算每个小任务的数列的数量long firstOne = start; // 动态记录小任务数列的起始值// 将任务拆分,并提交给框架for(int i=0; i<100; i++) {long lastOne = firstOne + step; // 动态记录小任务数列的结束值if(lastOne > end) {// 当队列结束值大于end,需要将lastOne设置为endlastOne = end;}ForkJoinDemo subTask = new ForkJoinDemo(firstOne, lastOne);firstOne = firstOne + step + 1;// 将子任务添加到数组中subTasks.add(subTask);// 执行子任务,这里是将子任务交个Fork/Join框架,什么时候开始执行由框架自己决定subTask.fork();}// 将子任务的计算结果汇总for(ForkJoinDemo st : subTasks) {sum += st.join();}}return sum;}public static void main(String args[]) {// 创建任务对象ForkJoinDemo task = new ForkJoinDemo(0, 500000L);// 创建Fork/Join线程池ForkJoinPool forkJoinPool = new ForkJoinPool();// 将任务提交给线程池ForkJoinTask<Long> result = forkJoinPool.submit(task);try {// 取出运算结果long sum = result.get();System.out.println("sum: " + sum);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}} } 原文转载于:https://blog.csdn.net/CoderTnT/article/details/78971506
- 亲爱的自己(亲爱的她们)
- 孕妇|孕妇生下罕见“双头婴”,众人都来围观,专家的话却令人担忧
- 当男人恋爱时解析(女主喜欢男二的韩剧)
- 绒毛促性腺激素|孕妇可以吃草莓吗?吃草莓的好处要知道,并非补铁补血那么简单
- 畸形|怀孕后不能玩手机虽然可能会导致胎宝畸形,但没你想的那么可怕
- 霸道校草的拽丫头免费观看(霸道校草的拽丫头结局)
- 孕妈|临近预产期,每周一次的胎心监护做还是不做?认识到这两点很重要
- 妈妈|孕期有6种情况表明可能怀的是男宝,中三条以上的,迎接小王子吧
- 穿山甲|新一届的孕妈迷信行为大赏,第一个就是重灾区
- 占地方|养娃后钱都去哪了这些“鸡肋”的母婴用品,希望你没买过!
