一、Java中线程的状态
任意线程在任意时刻,只会存在如下一种状态,且线程只有一次的New和一次的Terminated状态,其余中间状态会存在转换关系。
- New
- Runnable
- Blocked
- Waiting
- Time_waiting
- Terminated
需要注意的是,Runnable状态有两种情况,线程正在执行或者线程等待CPU分配资源。
二、线程安全性问题
- 运行结果错误:由于代码原子性引起,多线程共享数据获取和修改的数据异常;
- 发布和初始化导致线程安全问题:由于线程启动的时间成本引起调用方程序异常;
- 活跃性问题:死锁、活锁、饥饿
其他说明:
死锁:A、B线程互相等待资源而且互不释放资源,引起的无线等待;
活锁:A线程等待B线程结果,但B线程由于程序异常不断在队列中循环执行无法返回结果;
饥饿:A线程等待B线程结果,但B线程由于优先级过低,导致始终无法被调度而无限等待;
PS:在先Thread.start()的子线程不一定会先执行,而是取决于线程调度器,应为调用了start方法只是进入了Runnable状态(执行中、准备中),因此可能会是后需的Thread.start()方法先执行子线程具体内容。
三、常见的引起线程安全问题的场景
- 访问共享资源或者变量
- 依赖于时间顺序的操作
- 不同数据存在依赖绑定关系
- 被调用方未声称自己为线程安全,比如ArrayList是线程不安全的,在使用共享数据时,需要使用synchronized关键字
其实质都是在原子性上的问题
四、线程池相关内容
线程池的出现的原因:
线程的创建和回收,均需要系统开销。如果存在大量线程其本身开销较小,那针对这批线程的创建和回收的系统开销将大于线程执行任务本身的开销,则较为不智。于是线程池的就出现了,用于平衡系统资源与线程之间的中间件。
使用线程池的优点:
1、缓解线程生命周期的创建和回收的开销问题,同时缩短线程的响应速度。核心线程的线程会一直存在,不会被回收,缩短了创建线程的时间。
2、统筹内存与CPU的使用,减少系统应为线程重复创建、回收带来的系统冗余开销。
3、线程的统一管理,对线程池中的线程方便统筹管理。
线程池中相关参数:
参数 | 备注 |
corePoolSize | 核心线程数。初始化线程池,线程数量由0缓慢达到核心线程数corePoolSize。 |
maxPoolSize | 最大线程数,当workQueue队列中任务达到最大值后,线程池开始创建新的线程,直到maxPoolSize。 |
keepAliveTime+时间单位 | 空闲线程存活时间,当线程数大于corePoolSize,且存在空余线程时,空余线程会依据keepAliveTime时间,逐渐回收线程与资源。 |
ThreadFactory | 线程工厂,采用不同策略场产生线程。比如所有线程优先级均一致。 |
workQueue | 用于存放任务的队列,当线程数量大于corePoolSize时,任务会先进入workQueue中,而不是直接创建线程,直到workQueue满了,线程池才会开始创建新的线程。 |
Handler | 处理被拒绝后的任务。线程池常见的4钟拒绝策略: 1、AbortPolicy:线程池被拒绝时,抛出异常:RejectExecutionException的,调用者可以感知并捕获异常; 2、DiscardPolicy:线程被拒绝时,异常直接被抛弃,此时不会抛出异常,用户无感知; 3、DiscardOldestPolicy:线程被拒绝时,抛弃workQueue队首的任务,即等待时间最长的任务; 4、CallerRunsPolicy:谁提交任务谁负责执行任务 |
五、线程池的六种常见线程池类型
FixedThreadPool:固定线程数的线程池。其特点是:核心线程池数等于最大线程池数量,所以FixedThreadPool中的线程数会保持正在一定数量的线程数(除了一开始从0开始增加外,后达到核心线程数后就不再变动,线程数固定)。
如果线程数达到核心线程数(最大线程数)后,后续新增的任务将进入workQueue中,一旦队列满了,后续任务就将按照Handle的拒绝策略进行拒绝处理。
CacheThreadPool:可缓存线程池。其特点是:线程数可以最大达到INTEGER的最大值,且woekQueue的数量为0,队列起到一个传递的作用。其线程池中的线程数量是动态变化的,在没有任务时,会检测60s内空闲线程,并将其回收。
ScheduledThreadPool:定时线程池。其特点是:定时或者周期性的执行某任务。该线程池由三种处理周期性或延迟方式:1、延迟固定时间执行;2、延迟固定时间后周期性执行(只看任务开始时间);3、延迟固定时间后按上一个任务执行结束后周期性执行(需要看上一个线程的结束时间)
SingleThreadExecutor:单线程线程池。与FixedThreadPool相似,只是此时的corePoolSize=1。如果单一线程执行方法异常,则线程池会创建一个新的线程执行后续任务。由于线程单一,比较适合按顺序提交并执行的任务。因为线程提交的顺序,不代表线程任务真正执行的顺序。
SingleThreadScheduleExecutor:单线程定时执行线程池。与ScheduledThreadPool相似只是其中的核心线程数设置为1。
ForkJoinPool:分支合并线程池。比较适合存在多个子任务同时并行,最后进行汇总的任务。
六、线程池有关的阻塞队列
对于大部分线程池而言,都是多线程并发执行,这意味着存在多线程同时向任务队列中获取任内的情况,为避免同一任务被多线程同时获取,对任务队列需要有一定的同步或阻塞机制。
FixedThreadPool(线程数固定)和SingleThreadPool(线程数唯一)两种线程池使用LinkedBlockingQueue队列,而LinkedBlockingQueue的上限是Integer.VALUE_MAX,即无上限的队列。但是无上限的队列存在任务过多引起资源内存过度使用,引起OOM(Out Of Memory)的情况。
CacheThreadPool使用SynchrousQueue队列。CacheThreadPool其实不需要用任务队列来存放任务,CacheThreadPool会无限生产线程进行任务处理,任务队列仅做任务转发作用。但同时由于CacheThreadPool的线程数量是无上限的,也存在资源的过度使用和开销问题。
ScheduledThreadPool和SingleThreadScheduledExecutor使用DelayedWorkQueue队列。DelayedWorkQueue的特定是,按延时的时间长度对任务进行排序,符合schedule这类队列对延时一定时间进行执行的要求。
另外针对线程池的创建,建议不使用默认创建方式。应为无论哪种Executors.newFiexedThreadPool(5),对队列中的创建均使用默认参数,即任务队列的最大上限是无限制的,在实际场景中可能会引起资源内存的无限开销,引起系统奔溃。
学习资源:https://blog.csdn.net/zhanghai412/category_10842263.html