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只会唤醒一个线程。

posted @ 2022-04-01 14:58  r1-12king  阅读(283)  评论(0编辑  收藏  举报