Android 线程池死锁问题

原创
2020/09/28 17:54
阅读数 2.9K

一、线程池死锁

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锁定的是调用此类方法的对象资源,因此一定注意使用统一资源。

 

 

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部