目录
- [同一个线程一把锁, 加锁两次](#同一个线程一把锁, 加锁两次)
- [两个线程两把锁, 相互获取对方的锁](#两个线程两把锁, 相互获取对方的锁)
- [M个线程, N把锁(第二种情况的推广)](#M个线程, N把锁(第二种情况的推广))
- 死锁问题的概念
- 解决死锁问题
同一个线程一把锁, 加锁两次
java
public class demo17 {
public static void main(String[] args) {
Object locker = new Object();
Thread t = new Thread(()->{
synchronized(locker){
synchronized (locker){
System.out.println("hello world");
}
}
});
t.start();
}
}
- 看这段代码, 我们对同一个线程的代码加了两把锁. 根据我们在线程安全里面介绍锁的使用和执行过程可以分析出:
- 我们外面的第一把locker(锁)先拿到, 然后执行, 结果里面又要拿到locker,如果我们里面想拿到locker这把锁, 那么就必须要把代码执行完, 但是我们在我们代码现在的执行中, 我们又要拿到locker这把锁. 这就矛盾了.

- 但是我们看运行结果却是正常的, 怎么回事呢?
可重入锁解决
- 如果第一次加锁成功后, 第二次加锁的时候会判断第二次加锁的线程和第一次加锁的线程是否是同一个线程. 如果是同一个, 那么直接跳过第二次加锁的操作. 也就是没有真正加锁. 如果不是同一个线程, 才真正加锁
两个线程两把锁, 相互获取对方的锁
java
public class demo18 {
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t1 获取到 locker1");
sleep(1000);
synchronized (locker2) {
System.out.println("t1 获取到 locker2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker2) {
System.out.println("t2 获取到 locker2");
sleep(1000);
synchronized (locker1) {
System.out.println("t2 获取到 locker1");
}
}
});
t1.start();
t2.start();
}
}
- 这段代码在执行t1线程时, t1抢占了locker1这把锁, 这个时候t1又要获取locker2这把锁. 但是在执行t2线程时, t2抢占了locker2这把锁, 这个时候t2又要获取locker1这把锁.
- 这个时候t1占着locker1, 想获取locker2这把锁. 但是locker2这把锁被t2占着. 必须要等t2线程执行完后才能解锁.
- 但是如果t2线程要执行完, 就要获得locker1这把锁, 可是locker1这把锁被t1线程占着. 就必须要等待t1线程执行完后解锁.
- 结果t1在等t2执行完, t2也在等待t1线程执行完. 他们两个循环等待.
M个线程, N把锁(第二种情况的推广)
哲学家就餐问题

- 那么5个哲学家(线程), 同时那个五个筷子(锁). 这个时候他们都差一根筷子才能吃意大利面. 他们都在循环等待旁边的哲学家放下筷子, 自己才能吃到意大利面
- 可是这5个哲学家都是老顽固, 只要吃不到面条, 他们就不会放下手中的筷子. 所以他们都在等别人放下筷子.
- 他们都不让步, 这里就导致没有一个哲学家(线程), 能吃到面条. 就形成了死锁
死锁问题的概念
Java多线程中的死锁问题是指两个或多个线程互相持有对方所需的资源而无法继续执行的情况。这种情况下,线程无法释放已经占有的资源,也无法获取自己所需的资源,导致程序无法继续执行下去。
- 通常,发生死锁问题需要满足以下四个条件:
- 锁是互斥的, 也就是一个资源同时只能被一个线程占用(这个是synchronized的特点, 是无法改变的)
- 锁不可被抢占, 也就是A线程如果抢占了locker这把锁, B线程不能直接从A线程哪里抢过来, 必须等待A线程把这把锁使用完. (这一点对于synchronized是无法改变的)
- 请求和保持, 也就是A线程在保持拥有locker1这把锁的状态下, 还请求获取locker2这把锁. (吃着碗里的,想着锅里的)
- 循环等待(也就是我们上面的代码例子中, 每个线程等在循环等待其他线程释放自己所需要的锁)
解决死锁问题
前面两个条件是synchronized特性我们不能改变, 只能尝试改变请求和保持, 循环等待这两个条件. 这2个条件只要打破一个, 我们就可以破除死锁.
- 避免使用多个锁:尽量减少使用多个锁,如果必须使用多个锁,确保获取锁的顺序是一致的,以减少死锁的可能性。
- 加锁顺序:多个线程获取锁的顺序要保持一致,避免出现循环等待条件。
- 加锁时限:在获取锁的时候设置超时时间,如果一段时间内没有获取到锁,就放弃当前的操作,释放已经持有的锁,避免长时间等待导致死锁。
- 锁检测:通过监控线程的状态和资源的使用情况,及时检测并解决潜在的死锁问题。
哲学家就餐问题的解决
- 我们这里采用约定加锁的顺序: 任意一个线程多把锁的时候, 要按照编号大小顺序来加锁.(拿筷子)
- 对应到我们的代码实现就是这样的, 让多个线程按照编号小的顺序拿锁
java
public class demo18 {
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Object locker1 = new Object();
Object locker2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t1 获取到 locker1");
sleep(1000);
synchronized (locker2) {
System.out.println("t1 获取到 locker2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (locker1) {
System.out.println("t2 获取到 locker2");
sleep(1000);
synchronized (locker2