文章目录
- 线程从创建到死亡的状态
- 同步、异步
- 线程同步方法
- 线程池的种类、优势、运行流程
- AQS
- synchronized 和 java.util.concurrent.locks.Lock 的异同
- 如何确保N个线程可以访问N个资源,但同时又不导致死锁?
线程从创建到死亡的状态
- 新建(new):新创建了一个线程对象。
- 可运行(runabble):线程对象创建后,其他线程(比如main线程)调用了该对象的start() 方法。
该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权。 - 运行(runnning):可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。
- 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu的使用权,即让出了 cpu timeskice,暂时停止运行。直到线程进入可运行状态(runnable)状态,才有机会再次获取 cpu timeslice 转到 运行(running)状态。
- 阻塞分三种情况
- 等待阻塞:运行(running)的线程执行了 object.wait()方法,JVM会吧该线程放入等待队列(waitting queue)中。
- 同步阻塞:运行(running)的线程在获取对象的同步锁是,若该同步锁被别的线程占用,则JVM会吧该线程放入锁池(lock pool)中。
- 其他阻塞:运行(running)的线程执行 Thread.sleep(long ms)或者Thread.join()方法,或者发出了 I/O 请求时,JVM会吧该想爱你成设置为阻塞状态。当 sleep() 超时时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runabble)状态。
- 死亡(dead):线程run(),main()方法执行结束,或者因为异常退出了 run() 方法,则该线程结束生命周期。死亡的想爱你成不可再次复生。
同步、异步
- 同步:线程T1强依赖于线程T2,T1向T2发了一个请求,T1必须等到T2的回复才能继续自己的操作,若T2迟迟不响应,则T1就一直处于等待(阻塞状态)
- 异步:与同步相反,T1向T2发送了一个请求,不管T2是否有响应,都不妨碍T1的正常运行。
线程同步方法
- wait():使线程变成阻塞状态,释放锁。Object类方法,在对某对象使用了此方法之后,只有再对此对象使用notify() 或者 notifyall()方法,才能使此对象转为就绪状态,获得对象锁,即可进入运行状态
- sleep():Thread类方法,暂时使线程转为阻塞状态,不过,不释放锁。导致线程暂停执行指定时间,把执行机会让给其他线程,监控状态依旧保持,到时间后会自动激活。
- notify():随机使一个阻塞线程转为就绪状态,具体激活哪个线程,由JVM确定,不是按优先级确定
- notifyall():激活所有阻塞线程,至于哪个线程可以运行,需要看他们争抢资源的能力了。
线程池的种类、优势、运行流程
- newCachedThreadPool:创建一个可缓存线程池,如果线程池长度(最大数量,Interger.MAX_VALUE)超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在池队列中等待。
- newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor::创建一个单线程化的线程池,他只会用唯一的工作线程来执行任务,保证所有任务按指定顺序(FIFO,LIFO,优先级)执行。并且在任意给定的时间不会有多个线程是活动的 。
- 优势:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务达到时,任务可以不需要等到线程创建就能执行。
- 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和和监控。
- 运行流程:
- 线程池参数说明:
- corePoolSize(线程池的基本线程数)
- maximumPoolSize(线程池最大线程数)
- keepAliveTime(线程活动保持时间)
- TimeUnit(线程活动保持时间的单位)
- workQueue(任务队列)
- ArrayBlockingQueue:基于数组结构的游街阻塞队列,FIFO排序
- LinkedBlockingQueue:基于链表的阻塞队列,按FIFO对元素排序(吞吐量通常要高于ArrayBlockingQueue)。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchronousQueue:不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
- PriorityBlockingQueue:具有优先级的无线阻塞队列
- ThreadFactory:用于设置创建线程的工厂,可以设置名字
- RejectedExecutionHandler:(饱和策略)当队列和线程池都满了,说明线程池处于饱和状态,必须采用一种策略处理新任务时抛出异常
- JDK1.5提供的四种策略
- AbortPolicy:直接抛出异常
- CallerRunsPolicy:只用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
- DiscardPolicy:不处理,直接丢弃。
- 当然也可以根据应用场景来实现 RejectedExecutionHandler 接口自定义策略。比如:记录日志或持久化不能处理的任务。
- 当提交一个新任务到线程池时,线程池的处理流程如下:
[外链图片转存失败(img-sssACCEt-1565752889832)(images/ThreadPool.png)]
1. 首先线程池判断 核心线程池(corePoolSize)是否已满?
- 没满:创建一个核心线程去处理任务
- 满了:任务被放进任务队列workQueue排队等待执行
2. 其次判断等待队列workQueue是都已满?
- 没满:任务被放进任务队列workQueue排队等待执行
- 满了:判断整个线程池的线程数是否达到了maximumPoolSize
3. 判断整个线程池的线程数是否达到了maximumPoolSize?
- 未达到:创建非核心线程执行任务
- 达到了:根据饱和策略拒绝该任务。
AQS
- 实现锁的框架
- 内部实现的关键:FIFO队列,state状态。
- 定义了内部类 ConditionObject
- 拥有两种线程模式:独占模式和共享模式
synchronized 和 java.util.concurrent.locks.Lock 的异同
- 同:Lock 可以完成 synchronized 的所有功能
- 不同:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 后自动的释放锁,而 Lock是显式加锁,并且必须在finally从句中释放锁
如何确保N个线程可以访问N个资源,但同时又不导致死锁?
- 一种简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
- 死锁产生的四个条件:
- 互斥条件:一个资源每次只能被一个进程使用。
- 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可强行占有:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。、
- 这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,然而只要上述条件之一不满足,就不会发生死锁。
- 处理死锁的基本方法:
- 死锁预防:通过设置某些限制条件,去破坏死锁的四个条件中的一个或几个条件,来预防发生死锁。(但由于所施加的限制条件往往太严格,因而导致系统资源利用率和系统吞吐量降低)
- 死锁避免:允许前三个必要条件,但通过一定的选择,确保永远不会到达死锁点,因此死锁避免比死锁预防允许更多的并发。
- 死锁检测:不采用任何限制性措施,允许系统在运行过程中发生死锁,但可通过设置的检测机构及时检测死锁的发生,并精确的确定死锁相关的进程和资源,然后采取是适当的措施,从系统中将已发生的死锁清除掉。
- 死锁解除:与死锁检测配套的一种措施。当检测到系统中已发生死锁,需将进程从思索状态中解脱出来。
- 常用方法:撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程。死锁检测和解除有可能使系统获得较好的资源利用率和吞吐率,但在实现上难度也最大。
- **死锁预防:**破坏死锁的四个条件中的一个或几个
- 互斥:它是设备固有的属性所决定的,不仅不能改变,还应该加以保证。
- 占有且等待:为预防占有且等待条件,可以要求进程一次性的请求所有需要的资源,并且阻塞这个进程直到所有请求都同时满足。这个方法比较低效。
- 不可强行占有:预防方法:
- 如果一个进程请求当前被另一个进程占有的一个资源,则操作系统可以抢占另外一个进程,要求它释放资源。
- 如果占有某些资源的一个进程进行进一步的资源请求被拒绝是,则该进程必须释放它最初占有的资源。
- 循环等待:通过定义资源类型的线性顺序来访问。如果一个进程已经分配了R类资源,那么接下来请求的资源只能是那些排在R类型之后的资源类型。该方法比较低效。
- **死锁避免:**允许前三个条件,
- 两种死锁避免算法:
- 进程启动拒绝:如果一个进程的请求会导致死锁,则不启动该进程。
- 资源分配拒绝:如果一个进程增加的资源请求会导致死锁,则不允许此分配(银行家算法)
- 死锁检测和解除
- 死锁解除算法:剥夺资源和撤销进程。