摘要:关于线程池中的疑问对于线程池,相信大家都或多或少使用过。所以,阻塞队列的作用是控制线程池中线程的生命周期。也就是说,在的线程池,只有消费者使用了阻塞的方法,生产者并没有。
关于线程池中BlockingQueue的疑问
对于Java线程池,相信大家都或多或少使用过。关于其用法和原理介绍,网上已经有很多非常精彩的文章,珠玉在前,我就不献丑了。不了解的,可以参考这篇文章。今天我想讲的,是关于我对Java线程次的两个疑问,当然高手可以略过了。
为什么使用阻塞队列?1.为什么线程池要使用BlockingQueue,而不是ArrayList或别的什么列表?
2.既然使用了BlockingQueue,为什么还要设置拒绝策略,队列满的时候不是阻塞吗?
要回答这个问答,首先来看看不用线程池的时候怎么执行异步任务
new Thread(() -> { // do something }).start();
也就是说,每次需要执行异步任务的时候,新建一个线程去执行,执行完就回收了。这会导致什么问题呢,首先,是对资源的浪费,线程的创建需要陷入内核,需要分配栈空间,需要执行调度,等等,只使用一次就回收太浪费资源。其次,当异步任务比较多的时候,这种方式要创建大量的线程,这对于内存资源也是一个很大的开销。我们知道,在jvm启动的时候可以设置线程栈大小的参数-Xss,默认的大小是1M,如果同时启动1000个线程,就要占用1G的内存,可想而知,这对内存是一个多大的开销。而且,线程数太多,对于内核的调度压力也是相当大的,而且,因为频繁的上下文切换而使程序的局部性丧失,也是一种消耗。线程池的作用,就是线程的复用,那么,怎么复用呢,来看一段代码:
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
在ThreadPoolExecutor中,线程封装在Worker中,Worker实现了Runnable,同时在run()方法中调用上面的runWorker()方法,只要runWorker()方法没有执行完,这个线程就不会被回收。而runWorker()方法要执行下去,就要保证while (task != null || (task = getTask()) != null)的条件为真,第一次判断时task为firstTask,即执行的第一个任务,那么要点就成了getTask()必须不能为空,来看看getTask()的实现:
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
核心逻辑是:
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
这里的workQueue就是阻塞队列,timed表示是否会超时释放,keepAliveTime是非核心线程允许的空闲时间;如果不超时,则调用BlockingQueue.take(),如果取不到值,就会一直阻塞直到程序提交了一个任务。所以,阻塞队列的作用是控制线程池中线程的生命周期。
那么,如果不用阻塞队列,有没有别的方式可以实现线程池的功能?答案是,有,但是没必要。比如我们可以使用wait/notify来控制线程的执行和阻塞,但这里使用生产者/消费者模式来实现是一种更优雅的方式。
为什么需要拒绝策略既然使用了阻塞队列,那添加任务的时候如果队列满了不就阻塞了吗,拒绝策略是干嘛用的?答案是添加任务调用的并不是阻塞的put()方法,而是非阻塞的offer()方法,看一下ThreadPoolExecutor的execute()方法就知道了
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn"t, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
至于为什么这么实现,应该是不希望阻塞用户进程吧。
也就是说,在Java的线程池,只有消费者使用了阻塞的方法,生产者并没有。
SynchronousQueue不过也有例外,调用ExecutorService executorService = Executors.newCachedThreadPool();
时,BlockingQueue的实现类是SynchronousQueue,顾名思义,这是一个同步队列,其内部没有容量,使用SynchronousQueue,消费者线程和生产者线程必须交替执行,也就是说,生产者和消费者都必须等待对方就绪。这样的话,不就阻塞用户进程了吗。确实会,但是这个时间非常短,因为使用这种方式,每次通过execute()提交任务的时候,要么复用现有空闲的线程,要么新建一个线程,也就是说线程数理论上没有上界,所以可以当作不会阻塞
https://stackoverflow.com/questions/7556465/why-threadpoolexecutor-has-blockingqueue-as-its-argument?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
http://www.geek-programmer.com/java-blocking-queues-explained/
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69078.html
摘要:当活动线程核心线程非核心线程达到这个数值后,后续任务将会根据来进行拒绝策略处理。线程池工作原则当线程池中线程数量小于则创建线程,并处理请求。当线程池中的数量等于最大线程数时默默丢弃不能执行的新加任务,不报任何异常。 spring-cache使用记录 spring-cache的使用记录,坑点记录以及采用的解决方案 深入分析 java 线程池的实现原理 在这篇文章中,作者有条不紊的将 ja...
摘要:任务性质不同的任务可以用不同规模的线程池分开处理。线程池在运行过程中已完成的任务数量。如等于线程池的最大大小,则表示线程池曾经满了。线程池的线程数量。获取活动的线程数。通过扩展线程池进行监控。框架包括线程池,,,,,,等。 Java线程池 [toc] 什么是线程池 线程池就是有N个子线程共同在运行的线程组合。 举个容易理解的例子:有个线程组合(即线程池,咱可以比喻为一个公司),里面有3...
摘要:一个线程池包含很多准备运行的空闲线程,每当执行完毕后,线程不会死亡而是回到线程池准备为下一个请求提供服务。另一个使用线程池的理由是减少并发线程数。创建大量线程会大大降低性能甚至拖垮虚拟机。 【Future的概念 interface Future ,表示异步计算的结果,Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常...
摘要:去美团面试,问到了什么是线程池,如何使用,为什么要用以下做个总结。二线程池线程池的作用线程池作用就是限制系统中执行线程的数量。真正的线程池接口是。创建固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。 去美团面试,问到了什么是线程池,如何使用,为什么要用,以下做个总结。关于线程之前也写过一篇文章《高级面试题总结—线程池还能这么玩?》 1、什么是线程池: java.util...
摘要:去美团面试,问到了什么是线程池,如何使用,为什么要用以下做个总结。二线程池线程池的作用线程池作用就是限制系统中执行线程的数量。真正的线程池接口是。创建固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。 去美团面试,问到了什么是线程池,如何使用,为什么要用,以下做个总结。关于线程之前也写过一篇文章《高级面试题总结—线程池还能这么玩?》 1、什么是线程池: java.util...
阅读 2078·2023-04-26 00:41
阅读 1092·2021-09-24 10:34
阅读 3538·2021-09-23 11:21
阅读 3597·2021-09-22 15:06
阅读 1507·2019-08-30 15:55
阅读 858·2019-08-30 15:54
阅读 1795·2019-08-30 15:48
阅读 508·2019-08-29 13:58