一、线程池死锁
1、资源互斥死锁
这种死锁是最常见的经典死锁,假定存在 A、B 2个任务,A需要B的资源,B需要A的资源,双方都无法得到时便出现了死锁。
//首先我们先定义两个final的对象锁.可以看做是共有的资源.
final Object lockA = new Object();
final Object lockB = new Object();
//生产者A
class ProductThreadA implements Runnable{
@Override
public void run() {
//这里一定要让线程睡一会儿来模拟处理数据 ,要不然的话死锁的现象不会那么的明显.这里就是同步语句块里面,首先获得对象锁lockA,然后执行一些代码,随后我们需要对象锁lockB去执行另外一些代码.
synchronized (lockA){
//这里一个log日志
Log.e("CHAO","ThreadA lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
//这里一个log日志
Log.e("CHAO","ThreadA lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//生产者B
class ProductThreadB implements Runnable{
//我们生产的顺序真好好生产者A相反,我们首先需要对象锁lockB,然后需要对象锁lockA.
@Override
public void run() {
synchronized (lockB){
//这里一个log日志
Log.e("CHAO","ThreadB lock lockB");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA){
//这里一个log日志
Log.e("CHAO","ThreadB lock lockA");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//这里运行线程
ProductThreadA productThreadA = new ProductThreadA();
ProductThreadB productThreadB = new ProductThreadB();
Thread threadA = new Thread(productThreadA);
Thread threadB = new Thread(productThreadB);
threadA.start();
threadB.start();
2、Submit递归调用死锁
原理是在固定的线程池数量中,不断的submit 任务,然后等待返回结果,但是线程池数量是固定的,从头到尾所有的线程没执行完成,某次submit时就没有足够的线程来处理任务,所有任务都处于等待。
ExecutorService pool = Executors.newSingleThreadExecutor(); //使用一个线程数模拟
pool.submit(() -> {
try {
log.info("First");
pool.submit(() -> log.info("Second")).get();
//上一个线程没有执行完,线程池没有线程来提交本次任务,会处于等待状态
log.info("Third");
} catch (InterruptedException | ExecutionException e) {
log.error("Error", e);
}
});
3、线程池线程size不足造成的死锁
该类死锁一般是把一个线程池用于多个任务。
假定A,B 两个业务各需要2各线程处理业务,任务内部存在互相没有关联的lock,为了方便重用线程池,我们使用了一个线程池,最大线程数设置为2。
情形一:A,B有序执行,不会造成死锁
情形二: A、B并发执行,造成死锁
情形二出现的原因是A,B各分配了一个线程,当他们执行的条件都不满足的时处于wait状态,这时线程池没有更多的线程提供,将导致A、B处于死锁。
4、RejectedExecutionHandler 使用不当造成的“死锁”
严格意义上不能称为死锁,但是这也是非常容易忽视的问题。
一般处理任务时,触发该RecjectedExecutionHandler的情况分为2类,主要是"线程池关闭"、“线程队列和线程数已经达到最大容量”,那么问题一般出现在前者,如果线程池shutdown关闭之后,我们尝试在该Handler中重新加入任务到线程池,那么会造成死循环问题。
5、锁住死循环
锁住死循环本身也是一种死锁,导致其他想获取锁资源的线程无法正常获取中断
6、File Lock 与 同步锁 死锁
这类起始页做容易忽视
二、线程池优化方案
常见的方法如下几种
- 1、可以有序执行(有序执行不等于一定慢,如果是IO密集型,可以采用多路复用或者协程)
- 2、不要共用同一线程池,如果要共用,避免加速,阻塞和悬挂
- 3、使用公共锁资源的wait(long timeout)机制,让线程超时
- 4、如果过于担心线程池不能回收,建议使用keepaliveTime+allowCoreThreadTimeOut,回收线程但不影响线程状态,可以继续提交任务。
- 5、必要时扩大线程池大小
三、notify和wait机制注意事项
notify和wait锁定的是调用此类方法的对象资源,因此一定注意使用统一资源。