JDK中JUC包多线程内容的讲解,涉及「线程池、阻塞队列、协作器、ThreadLocal、Atomic、Future、锁、CAS、AQS 等」; Object 下wait、notify ;JVM层面 synchronized、volatile 等。 最新文章公众号持续更新中… 欢迎骚扰,分享技术,探讨生活,
线程池,顾名思义是一个放着线程的池子,池子中的线程主要是用来执行任务的。当用户提交任务时,线程池会创建线程去执行任务,若任务超过了核心线程数的时候,会在一个任务队列里进行排队等待(详细往下在讲)。任务,通常是一些大批量的或者耗时的工作单元,会把应用程序的工作分解到多个任务中去执行。一般需要使用多线程执行任务的时候,这些任务最好都是相互独立的,这样有一定的任务边界供程序把控。 多线程,当使用多线程的时候,任务处理过程就可以从主线程中分离出来,任务可以并行处理,同时处理多个请求。任务处理代码必须是线程安全的。
七个参数,四大特性,五个种类、三大工作队列、四大拒绝策略
1.七个参数
一共有7个:corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler
这边我们区分两个概念:
当前活动的线程总数< corePoolSize
,新建的线程即为核心线程。当前活动的线程总数> corePoolSize
, 且阻塞队列已满,这时新建一个线程来执行新提交的任务即为非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程不工作(空闲状态),除非ThreadPoolExecutor 的 allowCoreThreadTimeOut
这个属性为 true
,那么核心线程如果空闲状态下,超过一定时间后就被销毁。
线程总数 = 核心线程数 + 非核心线程数
keepAliveTime即为空闲线程允许的最大的存活时间。如果一个非核心线程空闲状态的时长超过keepAliveTime了,就会被销毁掉。注意:如果设置allowCoreThreadTimeOut = true
,就变成核心线程超时销毁了。
TimeUnit 是一个枚举类型,列举如下:
2.四大特性
1.当前线程数量未达到 corePoolSize
,则新建一个线程(核心线程)
执行任务
2.当前线程数量达到了 corePoolSize
,则将任务移入阻塞队列等待
,让空闲线程处理;
3.当阻塞队列已满
,新建线程(非核心线程)
执行任务
4.当阻塞队列已满,总线程数又达到了 maximumPoolSize
,就会按照拒绝策略处理无法执行的任务,比如RejectedExecutionHandler抛出异常。
3.五个种类
创建固定数目线程的线程池 Executors.newFixedThreadPool(200);
创建一个无限线程的线程池,无需等待队列,任务提交即执行 Executors.newCachedThreadPool()
创建有且仅有一个线程的线程池Executors.newSingleThreadExecutor();
创建一个定时周期执行的线程池Executors.newScheduledThreadPool();
1.FixedTheradPool和SingleThreadedPool因为是固定core和max,所以其阻塞任务队列是LinkedBlockingQueue没有边界的所以任务过多处理不完会导致OOM
2.CachedThreadPool和S cheduledThreadPool因为max是Integer.MAX_VALUE,阻塞队列是固定长度的ArrayBlockingQueue所以在队列满之后一直创建新线程可能会导致OOM
PS: 阿里禁止使用Executors线程池工具创建线程池,要使用ThreadPoolExecutor根据构造函数应用场景硬件等自定义线程池运行规则
4.三大工作队列
1.无界队列 LinkedBlockingQueue
2.有界队列 ArrayBlockingQueue
3.直接交界 SynchronousQueue
5.四大拒绝策略
ThreadPoolExecutor的拒绝策略可以通过调用setRejectedExecutionHandler
来修改。JDK提供了几种不同的RejectedExecutionHandler实现,每种实现都包含有不同的饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。 拒绝策略如下:
RejectedExecutionHandler rejected = null;
//默认策略,阻塞队列满,则丢任务、抛出异常
rejected = new ThreadPoolExecutor.AbortPolicy();
//阻塞队列满,则丢任务,不抛异常
rejected = new ThreadPoolExecutor.DiscardPolicy();
//删除队列中最旧的任务(最早进入队列的任务),尝试重新提交新的任务
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();
//队列满,不丢任务,不抛异常,若添加到线程池失败,那么主线程会自己去执行该任务
rejected = new ThreadPoolExecutor.CallerRunsPolicy();
(1)AbortPolicy、DiscardPolicy和DiscardOldestPolicy AbortPolicy
是默认的饱和策略
,就是中止任务,该策略将抛出RejectedExecutionException。调用者可以捕获这个异常然后去编写代码处理异常。 当新提交的任务无法保存到队列
中等待执行时,DiscardPolicy
会悄悄的抛弃该任务
。DiscardOldestPolicy
则会抛弃最旧的
(下一个将被执行的任务),然后尝试重新提交新的任务。如果工作队列是那个优先级队列时,搭配DiscardOldestPolicy饱和策略会导致优先级最高的那个任务被抛弃,所以两者不要组合使用。
(2)CallerRunsPolicy CallerRunsPolicy是“调用者运行”策略,实现了一种调节机制 。它不会抛弃任务
,也不会抛出异常
。 而是将任务回退到调用者
。它不会在线程池中
执行任务,而是在一个调用了execute的线程中
执行该任务。在线程满后,新任务将交由调用线程池execute方法的主线程执行,而由于主线程在忙碌,所以不会执行accept方法,从而实现了一种平缓的性能降低。 当工作队列被填满后,没有预定义的饱和策略来阻塞execute(除了抛弃就是中止还有去让调用者去执行)。然而可以通过Semaphore来限制任务的到达率。
RUNNING:运行状态,指可以接受任务并执行队列里的任务。
SHUTDOWN:调用了 shutdown() 方法,不再接受新任务,但队列里的任务会执行完毕。
STOP:指调用了 shutdownNow() 方法,不再接受新任务,所有任务都变成STOP状态,不管是否正在执行。该操作会抛弃阻塞队列里的所有任务并中断所有正在执行任务。
TIDYING:所有任务都执行完毕,程序调用 shutdown()/shutdownNow() 方法都会将线程更新为此状态,若调用shutdown(),则等执行任务全部结束,队列即为空,变成TIDYING状态;调用shutdownNow()方法后,队列任务清空且正在执行的任务中断后,更新为TIDYING状态。
TERMINATED:终止状态,当线程执行 terminated()
后会更新为这个状态。
核心源码分析(有没有像上边执行任务的四个规则)
runwork简单梳理【runwork 线程复用原理: getTask从阻塞队列拿任务 task.run执行while循环 work不会停止一直在取任务,并且执行任务会加锁;前beforeExecuter 后afterExecute 钩子函数控制线程池中线程执行情况,并且能够对线程执行前/后进行处理】
Execute源码详细分析推荐 https://juejin.cn/post/6844904070906380301
ExecutorService继承Executor有几个Executor没有的几个方法
比如:shutdown 、shutdownNow、 isshutdown、 isTerminated、 awaitTermination 、 subimit 、 invokall 等初步管理线程池方法
//关闭线程池,但阻塞队列中的任务继续执行完之后才关闭
void shutdown();
//关闭线程池,阻塞队列中的任务直接丢弃
List<Runnable> shutdownNow();
//是否处于SHUTDOWN状态
boolean isShutdown();
//是否处于TERMINATED状态
boolean isTerminated();
//等待线程池进入TERMINATED状态
boolean awaitTermination( long timeout, TimeUnit unit)
throws InterruptedException;
//提交任务,不过任务有返回值
<T> Future<T> submit(Callable<T> task);
//提交任务,不过任务有返回值
<T> Future<T> submit(Runnable task, T result);
//提交Runnable任务,会自动封装成Callable任务
Future<?> submit(Runnable task);
这个类是一个抽象类,实现了ExecutorService,主要是为该接口的方法提供一些默认的实现。
这个类就是线程池实现的核心类,线程池的线程管理和阻塞队列管理都是它完成的
**CPU 密集型任务(N+1):**比如圆周率计算
这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
**I/O 密集型任务(2N):**比如大文件耗时处理
这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。