案例出自 马士兵MAC课程
案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Test01 { public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("t1 start"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 end"); }, "thread-0"); t1.start();
Thread t2 = new Thread(() -> { synchronized (t1) { System.out.println("t2 start"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 isAlive: " + t1.isAlive()); } }, "thread-1"); t2.start(); } }
|
案例现象
在主线程中创建并启动两个线程,线程 t1 睡眠两秒后打印 “t1 end” 后结束,线程 t2 睡眠五秒后打印了 t1 线程的状态
正常来说,t1 线程睡眠两秒并打印 “t1 end” 后就结束了,因此线程 t2 在打印线程 t1 的存活状态应该为 false
然而实际结果为 true
分析原因
1. 线程 t1 在结束的时候需要获取自身对象这把锁,而这把锁被线程 t2 通过 synchronized 持有,因此没有结束
验证
方式一
在 synchronized 之前加上 *t1.join()*。
此时 t1 结束时,t2 并为持有 t1 对象作为锁,可以正常结束,因此 t2 打印 t1 线程状态是否存活为 false。
方式二
在 synchronized 之后加上 *t1.join()*。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class Test01 { public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("t1 start"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 end"); }, "thread-0"); t1.start();
Thread t2 = new Thread(() -> { synchronized (t1) { try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2 start"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1 isAlive: " + t1.isAlive()); } }, "thread-1"); t2.start(); } }
|
分析
- 当代码运行到 t1.join() 时,t2 持有的 t1 线程的对象锁,并且等待 t1 线程执行完成才能继续往下执行
- 然而 t1 线程需要获取自身对象锁才能结束,只有当 t1 线程结束了 t2 线程才能继续往下执行释放 t1 线程的对象锁
- 这段代码为什么可以正常结束而不发生死锁问题呢?
原因
这时我们去关注 join() 方法的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public final void join() throws InterruptedException { join(0); }
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0;
if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); }
if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
|
我们发现,join() 方法的实现最终是通过循环判断当前线程的活跃状态 *isAlive()*,并调用 wait(0) 方法。而 wait(0) 方法是 Object 的方法,他会让当前线程释放它所持有的锁。因此 t1 线程就可以获取其自身的对象锁成功结束。