Java如何避免死锁

什么是死锁

死锁是指多个线程因为互相等待对方释放资源而无法继续执行的情况。当线程A持有资源 X,但需要获取资源 Y,而线程B持有资源 Y,但需要获取资源 X,两个线程互相等待对方释放资源,导致程序无法继续执行。

死锁产生的条件

死锁产生需要满足以下四个条件:

  1. 互斥条件:资源不能被多个线程同时持有。
  2. 请求与保持条件:线程在持有某个资源的同时,又请求其他资源。
  3. 不剥夺条件:资源只能由持有者释放,不能被其他线程强制剥夺。
  4. 循环等待条件:存在一个等待队列,每个线程等待的资源都被其他线程占有。

如何避免死锁

1. 避免使用多个锁

一个简单的方法是尽量避免使用多个锁。将多个锁合并成一个锁,可以避免多个线程持有不同的锁而产生死锁的问题。

2. 按顺序获取锁

为了避免循环等待条件,可以规定线程获取锁的顺序。当多个线程都按照相同的顺序获取锁时,就能避免循环等待的情况。

private static final Object lock1 = new Object();
private static final Object lock2 = new Object();

public void thread1() {
    synchronized (lock1) {
        synchronized (lock2) {
            // 业务逻辑
        }
    }
}

public void thread2() {
    synchronized (lock1) {
        synchronized (lock2) {
            // 业务逻辑
        }
    }
}

3. 设置获取锁的超时时间

如果一个线程在获取锁的时候,如果超过了一定的时间还没有获取到锁,可以主动放弃锁的获取,避免死锁的发生。可以使用tryLock()方法来设置锁获取的超时时间。

private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();

public void thread1() {
    try {
        if (lock1.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (lock2.tryLock(1, TimeUnit.SECONDS)) {
                    // 业务逻辑
                }
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

public void thread2() {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (lock1.tryLock(1, TimeUnit.SECONDS)) {
                    // 业务逻辑
                }
            } finally {
                lock1.unlock();
            }
        }
    } finally {
        lock2.unlock();
    }
}

4. 使用Lock的tryLock()方法

ReentrantLock类提供了tryLock()方法,该方法可以尝试获取锁,如果获取成功返回true,否则返回false,这样可以根据返回结果来决定是否执行相关操作,避免死锁的发生。

private static final Lock lock1 = new ReentrantLock();
private static final Lock lock2 = new ReentrantLock();

public void thread1() {
    if (lock1.tryLock()) {
        try {
            if (lock2.tryLock()) {
                // 业务逻辑
            }
        } finally {
            lock2.unlock();
        }
    }
    lock1.unlock();
}

public void thread2() {
    if (lock2.tryLock()) {
        try {
            if (lock1.tryLock()) {
                // 业务逻辑
            }
        } finally {
            lock1.unlock();
        }
    }
    lock2.unlock();
}

5. 使用线程池

使用线程池来管理线程的创建和销毁,可以有效地避免死锁的发生。线程池可以复用线程,减少线程的创建和销毁的开销,同时可以通过线程池的配置来控制线程的数量,避免出现过多的线程导致系统资源耗尽的