笔记(下):mysql-DuplicateUpdate和java的threadpool的“死锁“

模拟一下这个场景:

我搞了2个线程池,分别是nio线程池和业务线程池,模拟并发20个请求, 注意看process2方法里的注释,如果去掉那里的代码的话 就不会有这个死锁问题


/**
 * @author yuzd
 */
public class PoolTest {

    // 模拟nio线程池
    static ThreadPoolExecutor nioExecutor = new ThreadPoolExecutor(20, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new CustomerNamedThreadFactory("nio", false),
            new ThreadPoolExecutor.AbortPolicy());

    // 业务线程池
    static ThreadPoolExecutor buExecutor = new ThreadPoolExecutor(20, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new CustomerNamedThreadFactory("bu", true),
            new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {

        // 模拟是http请求并发20个
        IntStream.rangeClosed(1, 20).parallel().forEach((index) -> {
            // 交给nio线程池处理            
            nioExecutor.execute(() -> {
                try {
                    httpHandler(index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        });
    }


   
    static void httpHandler(int index) throws ExecutionException, InterruptedException {
        System.out.println(Thread.currentThread().getName() + " request index :" + index + " staring");
        // 交给业务线程池处理     
       
        Future parentFuture = buExecutor.submit(() -> process1(index));
        String p1Rt = parentFuture.get();  // nio线程在wait
        System.out.println(Thread.currentThread().getName() + " request index :" + p1Rt + " ending");
    }

    // future1
    static String process1(int index) throws ExecutionException, InterruptedException {
        System.out.println( Thread.currentThread().getName() + " process1 index :" + index + " staring");
        Future childFuture = buExecutor.submit(() -> process2(index));
        String p2Rt = childFuture.get();  // 这里是bu线程在wait   这里会发生死锁
        
        System.out.println(Thread.currentThread().getName() + " process1 index :" + index + " ending");
        return p2Rt;
    }

    // future2
    static String process2(int index) throws InterruptedException, ExecutionException {
        System.out.println(Thread.currentThread().getName() + " process2 index :" + index + " staring");
        // 加上就会死锁 
        // 只要不一下子产生足够数量的task(把core全部占掉)就不会死锁 加了这里就会把core全部占据 导致task进入到queue,core线程在wait future.get 无法被释放, 而queue的任务在等待它释放产生新的线程
        Future submit = buExecutor.submit(() -> {
            try {
                Thread.sleep(1000);
                return String.valueOf(index);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        submit.get(); 
        System.out.println(Thread.currentThread().getName() + " process2 index :" + index + " ending");
        return String.valueOf(index);
    }
}

用visualvm分析线程dump,很难直接发现有异常,异步的很难检测,排查起来比较复杂,只看到是在wait

笔记(下):mysql-DuplicateUpdate和java的threadpool的“死锁“_第1张图片

image

用jstack没有发现deadlock

笔记(下):mysql-DuplicateUpdate和java的threadpool的“死锁“_第2张图片

 image

在实际项目中我也看到过一个项目中共用一个线程池,线程池被封装成一个util方法,要执行异步的都用它,这个场景尤其要注意这个场景,也建议大家用带有超时的方式 Future.get(xxxx)

总结:

死锁就是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的现象,若无外力作用,他们都无法推进下去

简单来说就是A和B若同时都有一个资源,在此之外还想拥有对方的资源,由于资源如果在某个人手里,其他人就无法获得,所以变成了你不让我,我不让你,就僵持下了。

二、死锁产生的原因

  1. 互斥条件:一个资源只能被一个线程占有,当这个资源被占有后其他线程就只能等待
  2. 不可剥夺条件:当一个线程不主动释放资源时,此资源一直被拥有线程占有
  3. 请求并持有条件:线程已经拥有了一个资源后,又尝试请求新的资源
  4. 环路等待条件:产生死锁一定是发生了线程资源环形链

预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
 

你可能感兴趣的:(java,算法,数据结构)