java Join() 方法的锁的释放
Thread 的join() 方法是否会释放锁?
- 答: 会!
问题在于,Thread.join()释放的到底是那个对象的锁.
有如下代码:
1 public class TestJoin { 2 3 public static void main(String[] args) throws InterruptedException { 4 Object oo = new Object(); 5 6 Thread thread1 = new MyThread("thread0 -- ", oo); 7 thread1.start(); 8 9 synchronized (oo) { 10 for (int i = 0; i < 100; i++) { 11 if (i == 5) { 12 try { 13 thread1.join(); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 System.out.println(Thread.currentThread().getName() + " -- " + i); 19 } 20 } 21 } 22 } 23 24 class MyThread extends Thread { 25 26 private String name; 27 private Object oo; 28 29 public MyThread(String name, Object oo) { 30 this.name = name; 31 this.oo = oo; 32 } 33 34 @Override 35 public void run() { 36 synchronized (oo) { 37 for (int i = 0; i < 100; i++) { 38 System.out.println(name + i); 39 } 40 } 41 } 42 }
结果如下:
可以看到,在运行到main--5的时候卡住了。
-- 先使用jps找到出问题程序的进程号
-- jstack pid 来查看线程堆栈,关键信息如下图
可以看到,注意到 Thread-0 在等待锁 <0x000000076ac2b1f0>,
但是main持有锁<0x000000076ac2b1f0>, 这又是什么情况呢? 难道 main 没有释放锁?
我们看thread.join() 的源码:
1 public final synchronized void join(long millis) 2 throws InterruptedException { 3 long base = System.currentTimeMillis(); 4 long now = 0; 5 6 if (millis < 0) { 7 throw new IllegalArgumentException("timeout value is negative"); 8 } 9 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 while (isAlive()) { 16 long delay = millis - now; 17 if (delay <= 0) { 18 break; 19 } 20 wait(delay); 21 now = System.currentTimeMillis() - base; 22 } 23 } 24 } public final synchronized void join(long millis) 25 throws InterruptedException { 26 long base = System.currentTimeMillis(); 27 long now = 0; 28 29 if (millis < 0) { 30 throw new IllegalArgumentException("timeout value is negative"); 31 } 32 33 if (millis == 0) { 34 while (isAlive()) { 35 wait(0); 36 } 37 } else { 38 while (isAlive()) { 39 long delay = millis - now; 40 if (delay <= 0) { 41 break; 42 } 43 wait(delay); 44 now = System.currentTimeMillis() - base; 45 } 46 } 47 }
可以看到,其实调用的是 this.wait(),也就是说释放的是Thread-0 这个对象的锁,对象oo的锁一直没有被释放,因此阻塞了。
验证一下,这次锁住thread-0对象
1 package com.test.java; 2 3 public class TestJoin { 4 5 public static void main(String[] args) { 6 Object oo = new Object(); 7 8 Thread thread1 = new MyThread("thread1 -- ", oo); 9 thread1.start(); 10 11 synchronized (thread1) { 12 for (int i = 0; i < 5; i++) { 13 if (i == 3) { 14 try { 15 thread1.join(); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 System.out.println(Thread.currentThread().getName() + " -- " + i); 21 } 22 } 23 } 24 25 } 26 27 class MyThread extends Thread { 28 29 private String name; 30 private Object oo; 31 32 public MyThread(String name, Object oo) { 33 this.name = name; 34 this.oo = oo; 35 } 36 37 @Override 38 public void run() { 39 synchronized (this) { 40 for (int i = 0; i < 5; i++) { 41 System.out.println(name + i); 42 } 43 } 44 } 45 46 }
结果如下:
程序正常运行结束。
再接下来我们看一下如果调用wait() 方法,应该是怎么个情况呢
1 package com.test.java; 2 3 public class TestJoin { 4 5 public static void main(String[] args) { 6 Object oo = new Object(); 7 8 Thread thread1 = new MyThread("thread0 -- ", oo); 9 thread1.start(); 10 11 synchronized (oo) { 12 for (int i = 0; i < 5; i++) { 13 if (i == 2) { 14 try { 15 oo.wait(); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 System.out.println(Thread.currentThread().getName() + " -- " + i); 21 } 22 } 23 } 24 } 25 26 class MyThread extends Thread { 27 28 private String name; 29 private Object oo; 30 31 public MyThread(String name, Object oo) { 32 this.name = name; 33 this.oo = oo; 34 } 35 36 @Override 37 public void run() { 38 synchronized (oo) { 39 for (int i = 0; i < 5; i++) { 40 System.out.println(name + i); 41 } 42 oo.notifyAll(); 43 } 44 } 45 }
程序正常结束。
解释:
先说两个概念:锁池和等待池
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
然后再来说notify和notifyAll的区别
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
- 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
综上,所谓唤醒线程,另一种解释可以说是将线程由等待池移动到锁池,notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而notify只会唤醒一个线程。