什么是 Monitor?

Java 虚拟机给每个对象和 class 字节码都设置了一个监听器 Monitor,用于检测并发代码的重入,同时在 Object 类中还提供了 notify 和 wait 方法来对线程进行控制

在线程的同步起着关键的作用, 假设有如下代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class ThreadTest {
		private String str;
		
    private synchronized void println2(String str){
    		this.str = str;
    }

    public static synchronized void println(){
        System.out.println("hello world");
    }
}

在上述方法中, 当执行到非静态方法 println2 时, 需要获取当前对象 this 的 Monitor , 获取后, 其他需要获取该对象的Monitor 的线程会被堵塞。

当执行到静态方法 println 时, 需要获取到当前类的字节码的 Monitor (因为静态方法不属于对象, 而属于类) , 获取后, 其他需要 Monitor 的线程会被堵塞。

假设有如下的示例, 分别有线程 1 和线程 2 , 一个线程去赋值数据, 一个线程在得到数据后使用这个数据, (听起来有点拗口, 不如想象成一个线程去请求数据, 一个线程在请求到数据之后将数据渲染到界面上)这里线程 1 和线程 2 都模拟了一下耗时操作, 这里是写死耗时时间, 在实际的情况中, 可能是不定时的, 我们来看看打印的值。

 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
33
34
35
36
public class ThreadTest {
    private String str;

    public synchronized void initStr() {
        str = "hello world";
    }

    public synchronized void printStr() {
        System.out.println(str);
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();

        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadTest.printStr();
        });
        thread.start();

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            threadTest.initStr();
        });
        thread2.start();

    }
}

打印的结果

1
null

这里打印的结果可想而知, 相信大家都可以猜到, 但是这里并不符合我们的需求, 我们需要的是一个线程请求到数据后, 另外一个线程使用这个数据, 稍加改造一下

1
2
3
4
5
6
 public synchronized void printStr() {
        while (str == null) {

        }
        System.out.println(str);
  }

我们在这里加一个判断, 当 str 为 null 的时候, 死循环, 就不打印他的值, 也就是说当另外一个线程赋值成功后, 这里将不为null, 然后就会打印值, 真的是这样吗? run 一下程序后会发现, 一直也不会打印, 这是为什么?

这是因为, 当我们执行到 printStr 的时候, 持有了当前类对象的 Monitor, 而这个时候恰好另外一个线程去赋值了, 发现这个时候当前类对象的 Monitor 被别人给持有了, 将发生堵塞, 那么将一直不会被执行。

再进行下一步改造, 如果我们在没有拿到数据之前稍微等一会, 并且不持有 Monitor , 然后另外一个赋值的时候不是刚刚好可以赋值了吗? 赋值成功后, 我们再通知线程已经有值了, 再打印一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 public synchronized void initStr() {
        str = "hello world";
        notifyAll();
    }

    public synchronized void printStr() {
        while (str == null) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    System.out.println(str);

打印结果

1
hello world

wait: 当前线程会进入等待, 释放当前的 Monitor

notifyAll: 唤醒所有在等待的线程