java并发编程

java 死锁产生的8种场景

2025-03-19  本文已影响0人  饱饱抓住了灵感

一、总结

Java死锁的本质是资源竞争不当的同步控制共同作用的结果。通过合理设计资源分配策略、缩小同步范围、使用超时机制以及选择合适的并发工具,可以显著降低死锁风险。在复杂的多线程场景中,建议结合代码审查和工具分析,确保系统的高可用性。

预防措施

  1. 避免嵌套锁:减少锁的粒度,确保每个锁只保护必要的代码。
  2. 使用超时机制:在锁或线程等待时设置超时时间。
  3. 资源分级:为资源分配全局唯一顺序,避免环形等待。
  4. 优先级继承:高优先级线程等待低优先级线程时,继承请求方的优先级。
  5. 使用并发工具类:如java.util.concurrent包中的LockSemaphoreAtomic类。

诊断工具

二、死锁场景

1. 资源竞争(最常见场景)

场景示例

// 资源A和资源B
Object lockA = new Object();
Object lockB = new Object();

// 线程1
new Thread(() -> {
    synchronized (lockA) {
        System.out.println("Thread 1 holds lockA");
        synchronized (lockB) { // 试图获取资源B
            System.out.println("Thread 1 holds both locks");
        }
    }
});

// 线程2
new Thread(() -> {
    synchronized (lockB) {
        System.out.println("Thread 2 holds lockB");
        synchronized (lockA) { // 试图获取资源A
            System.out.println("Thread 2 holds both locks");
        }
    }
});

死锁原因

解决方案


2. 同步代码块调用外部方法

场景示例

public class DeadlockExample {
    private final Object lock = new Object();

    public void method1() {
        synchronized (lock) {
            method2(); // 调用外部方法
        }
    }

    public void method2() {
        synchronized (lock) { // 可能导致死锁!
            System.out.println("Inside method2");
        }
    }
}

死锁原因

解决方案


3. 哲学家就餐问题(经典案例)

场景说明

5个哲学家围坐,每人需要同时拿到左右两边的筷子(资源)才能进食。若所有人同时拿起左边筷子,将全部无法进食。

Java实现模拟

public class DiningPhilosophers {
    private final Lock[] forks = new Lock[5];
    private final Condition[] conditions = new Condition[5];

    public DiningPhilosophers() {
        for (int i = 0; i < 5; i++) {
            forks[i] = new ReentrantLock();
            conditions[i] = forks[i].newCondition();
        }
    }

    public void eat(int philosopher) throws InterruptedException {
        int left = philosopher;
        int right = (philosopher + 1) % 5;

        forks[left].lock();
        try {
            forks[right].lock(); // 可能死锁!
            System.out.println("Philosopher " + philosopher + " is eating");
            Thread.sleep(1000);
        } finally {
            forks[right].unlock();
            forks[left].unlock();
        }
    }
}

死锁原因

解决方案


4. 线程池配置不当

场景示例

ExecutorService executor = Executors.newFixedThreadPool(2); // 线程池大小为2

for (int i = 0; i < 10; i++) {
    executor.submit(() -> {
        try {
            TimeUnit.SECONDS.sleep(10); // 长时间任务
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

死锁风险

解决方案


5. 并发集合操作不当

场景示例

ConcurrentHashMap<Integer, Integer> map = new ConcurrentHashMap<>();

// 错误写法:在迭代时修改集合
new Thread(() -> {
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        if (entry.getKey() == 1) {
            map.put(2, 3); // 并发修改!
        }
    }
});

// 正确写法:使用迭代器的安全方法
new Thread(() -> {
    map.forEach((k, v) -> {
        // 仅读操作,安全
    });
});

死锁原因

解决方案


6. 数据库连接池泄漏

场景示例

// 未释放连接的代码
Connection conn = DriverManager.getConnection(url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM table");

// 忘记关闭资源

死锁风险

解决方案


7. 信号量与条件变量误用

场景示例

Semaphore semaphore = new Semaphore(1);
Condition condition = new Condition();

public void producer() throws InterruptedException {
    semaphore.acquire();
    // 生产数据
    condition.signalAll();
}

public void consumer() throws InterruptedException {
    semaphore.acquire();
    condition.await(); // 可能死锁!
    // 消费数据
}

死锁原因

解决方案


8. 网络IO阻塞

场景示例

// 未设置超时的HTTP请求
URL url = new URL("http://example.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.connect();
InputStream in = connection.getInputStream();

死锁风险

解决方案

connection.setConnectTimeout(5000); // 5秒连接超时
connection.setReadTimeout(5000); // 5秒读取超时

上一篇 下一篇

猜你喜欢

热点阅读