线程池篇
线程池原理分析: 总的来说就是: 一幅图——
几种常见的对比
Runnable和callable
callable1.5引入的,runnable1.1引入,
Runnable接口不会返回结果和抛出异常。
若不需要返回结果或者抛出异常,使用Runnable接口更加简洁
Executors可以实现Runnable对象和Callable对象的相互转化、
(Executors.callable(Runnable tas或 Executors.callable(RunnabletaskObjectresule))
execute()和submit()
execute()执行不需要返回值的任务,无法判断任务被线程池执行成功与否 submit()提交需要返回值的任务,返回一个futrue对象,可以通过future的get()获得返回值。submit()方法调用newTaskFor()返回一个FutrueTask对象。
线程池的几种状态
RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED
当调用shutdown方法时,从Running到ShutDown,(关闭状态)再到TIDYING,最后到TERMINATED销毁状态,线程池不再接受新任务了,但是队列里的任务得执行完毕。 当调用shutdownNow()时候,线程池状态从RUNNING到STOP,再到TIDYING,最后到TERMINATED销毁状态。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
shutdown()和shutdownNow()
shutdown()时,从Running到ShutDown,不再接受新任务 shutdownNow()时候,线程池状态从RUNNING到STOP,终止当前正在运行的任务,队列中的任务也不处理了
isTerminated()和isShutdown()
isShutDown 当调用 shutdown() 方法后返回为 true。isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true。
几种常见的线程池详解
首先还是FixedThreadPool
(核心和最大为指定值,确定同时并发的数量) 为什么不推荐这个方式来创建线程呢 使用的是无界队列 LinkedBlockingQueue,这是无界队列,队列容量为 Intger.MAX_VALUE(2147483647) 1.当线程池中的线程数达到 corePoolSize 后,新任务将在无界队列中等待,因此线程池中的线程数不会超过corePoolSize; 2.由于使用无界队列时maximumPoolSize 将是一个无效参数,因为不可能存在任务队列满的情况。所以,通过创建 FixedThreadPool的源码可以看出创建的 FixedThreadPool 的 corePoolSize 和 maximumPoolSize 被设置为同一个值。 3.由于 1 和 2,使用无界队列时 keepAliveTime 将是一个无效参数;(当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交, 核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;) 4.运行中的 FixedThreadPool(未执行 shutdown()或 shutdownNow())不会拒绝任务,在任务比较多的时候会导致OOM(内存溢出)。
SingleThreadExecutor
(核心和最大都是1,允许请求的队列长度是Integer.MAX_VALUE) 也是使用无界队列座位了线程池的工作队列会给线程池带来影响,类似于FixedThreadPool
CachedthreadPool
(核心为0,最大为max,只要有请求进来,就一直等待,等到运行为止) CachedThreadPool 的corePoolSize 被设置为空(0),maximumPoolSize被设置为 Integer.MAX.VALUE,即它是无界的, 如果主线程提交任务的速度高于 maximumPool 中线程处理任务的速度时,CachedThreadPool 会不断创建新的线程。极端情况下,这样会导致耗尽cpu和内存资源。CachedThreadPool允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM
ScheduledThreadPool
延后运行任务,或者定期执行任务,在实际项目中基本不会被用到。允许创建的线程数量为Integer.MAX_VALUE,会导致OOM ScheduledThreadPoolExecutor 的执行主要分为两大部分:
1.当调用 ScheduledThreadPoolExecutor 的 scheduleAtFixedRate() 方法或者scheduleWithFixedDelay()方法时会 ScheduledThreadPoolExecutor 的 DelayQueue 添加一个实现了 RunnableScheduledFuture 接口的 ScheduledFutureTask 。 2.线程池中的线程从 DelayQueue 中获取 ScheduledFutureTask,然后执行任务。 ScheduledThreadPoolExecutor 为了实现周期性的执行任务,对 ThreadPoolExecutor做了如下修改:使用 DelayQueue 作为任务队列; ● 获取任务的方式不同 ● 执行周期任务后,增加了额外的处理 ScheduledThreadPoolExecutor执行周期
1.线程 1 从 DelayQueue 中获取已到期的 ScheduledFutureTask(DelayQueue.take())。到期任务是指 ScheduledFutureTask的 time 大于等于当前系统的时间;
2.线程 1 执行这个 ScheduledFutureTask;
3.线程 1 修改 ScheduledFutureTask 的 time 变量为下次将要被执行的时间;4.线程 1 把这个修改 time 之后的 ScheduledFutureTask 放回 DelayQueue 中(DelayQueue.add())。
线程池大小的确定
过大或者过小都会出现问题——肯定是合适的最好 太小:同一时间有大量请求的话,导致大量请求、任务在任务队列中排队等待执行。 任务队列满了请求无法处理的情况,cpu未得到充分的利用。 太大:大量线程争取CPU资源,导致大量的上下文切换,从而增加线程的执行时间,影响整体效率。
IO密集型:
CPU密集型(N+1):消耗的是CPU资源,线程数设置为(CPU核心数+1),比CPU核心数多出来一个线程是为了避免线程偶发的缺页中断,或者其他原因导致的影响
I/O密集型:系统会用大部分的时间处理I/O交互,线程在处理I/O的时间段不会占用CPU处理,这样就酱CPU交给其他线程处理,具体计算方法是2N