• 如何在工程中避免死锁现象的发生?
  • 发布于 2个月前
  • 203 热度
    0 评论
  • 王晶
  • 0 粉丝 40 篇博客
  •   

前言:

死锁是多线程编程中常见的问题之一,如果不正确处理,可能会导致程序崩溃或停止响应。为了避免死锁,我们需要了解其根本原因,然后采取相应的措施来解决问题。这包括避免资源分配问题、算法设计问题和代码实现问题等。通过采用上述方法,我们可以有效地减少死锁的风险,并提高多线程应用程序的正确性和可靠性。今天我们就聊聊我们日常写代码时该如何避免死锁的发生。

1、设置超时时间

由于 synchronized不具备尝试锁的能力,可以使用 Lock的tryLock(long timeout, TimeUnit unit) 替代。造成超时的可能性多:发生了死锁、线程陷入死循环、线程执行很慢,无论是哪种情况,只要过了超时时间,就认为失败获取锁失败的时候,要执行一些应对措施:比如打日志、发报警邮件、重启等
/**
 * 堆代码 duidaima.com
 * 使用Lock中的tryLock()避免死锁
 */
publicclass TryLockDeaLock implements Runnable {
    int flag = 1;
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag == 1){
                try {
                    if (lock1.tryLock(800, TimeUnit.MILLISECONDS)){
                        System.out.println("线程1,拿到lock1");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                            System.out.println("线程1,成功获取到 lock1 & lock2");
                            //释放
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        }else {
                            System.out.println("线程1,获取lock2,失败,已重试");
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("线程1,获取lock1,失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (flag ==0){
                try {
                    if (lock2.tryLock(3000, TimeUnit.MILLISECONDS)){
                        System.out.println("线程2,拿到lock2");
                        Thread.sleep(new Random().nextInt(1000));
                        if (lock1.tryLock(3000,TimeUnit.MILLISECONDS)){
                            System.out.println("线程2,成功获取到 lock1 & lock2");
                            //释放
                            lock2.unlock();
                            lock1.unlock();
                            break;
                        }else {
                            System.out.println("线程2,获取lock1,失败,已重试");
                            lock1.unlock();
                            Thread.sleep(new Random().nextInt(1000));
                        }
                    }else {
                        System.out.println("线程2,获取lock2,失败,已重试");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //主函数
    public static void main(String[] args) {
        TryLockDeaLock t1 = new TryLockDeaLock();
        TryLockDeaLock t2 = new TryLockDeaLock();
        t1.flag = 1;
        t2.flag = 0;
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);

        thread1.start();
        thread2.start();
    }
}
2、多使用并发工具类,而不是自己设计锁

ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等,实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高,多用并发集合少用同步集合,并发集合比同步集合的可扩展性更好,并发场景需要用到map,首先想到用ConcurrentHashMap。


3、尽量降低锁的使用粒度:
用不同的锁而不是一个锁

4、如果能使用同步代码块,就不使用同步方法
在代码块中,可以自己指定锁对象(方便掌控锁)

5、给你的线程起个有意义的名字
如果遇到问题,在debug和排查时事倍功半,更容易定位到问题,框架和JDK都遵守这个最佳实践

6、避免锁嵌套
比如下图中在已经持有 lock1 的情况下,还要继续请求 lock2,锁互相嵌套,更容易导致死锁

7、分配资源前先看能不能收回
银行家算法:在放贷之前,首先对资源的进行有效计算,看看资源能不能收回来。

8、不要几个功能用同一把锁
每一个功能都要有自己专门的锁,专锁专用
用户评论