Java多线程(二)——Java对象的Monitor机制

Java多线程(二)——Java对象的Monitor机制

一、概述

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

在java.lang.Object类中有如下代码:

public class Object {
    ...
    private transient int shadow$_monitor_;
    public final native void notify();
    public final native void notifyAll();
    public final native void wait() throws InterruptedException;
    public final void wait(long millis) throws InterruptedException {
        wait(millis, 0);
    }
    public final native void wait(long millis, int nanos) throws InterruptedException;
    ...
}

二、Monitor机制

Monitor的机制如下图:

这里写图片描述

结合上图来分析Object的Monitor机制。

Monitor可以类比为一个特殊的房间,这个房间中有一些被保护的数据,Monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有Monitor,退出房间即为释放Monitor。

当一个线程需要访问受保护的数据(即需要获取对象的Monitor)时,它会首先在entry-set入口队列中排队(这里并不是真正的按照排队顺序),如果没有其他线程正在持有对象的Monitor,那么它会和entry-set队列和wait-set队列中的被唤醒的其他线程进行竞争(即通过CPU调度),选出一个线程来获取对象的Monitor,执行受保护的代码段,执行完毕后释放Monitor,如果已经有线程持有对象的Monitor,那么需要等待其释放Monitor后再进行竞争。

再说一下wait-set队列。当一个线程拥有Monitor后,经过某些条件的判断(比如用户取钱发现账户没钱),这个时候需要调用Object的wait方法,线程就释放了Monitor,进入wait-set队列,等待Object的notify方法(比如用户向账户里面存钱)。当该对象调用了notify方法或者notifyAll方法后,wait-set中的线程就会被唤醒,然后在wait-set队列中被唤醒的线程和entry-set队列中的线程一起通过CPU调度来竞争对象的Monitor,最终只有一个线程能获取对象的Monitor。

需要注意的是:

  • 当一个线程在wait-set中被唤醒后,并不一定会立刻获取Monitor,它需要和其他线程去竞争
  • 如果一个线程是从wait-set队列中唤醒后,获取到的Monitor,它会去读取它自己保存的PC计数器中的地址,从它调用wait方法的地方开始执行。

三、Monitor的实现

前面已经分析了Monitor的机制,那么在Java中是如何实现的呢?

即通过synchronized关键字实现线程同步来获取对象的Monitor。synchronized同步分为以下两种方式:

同步代码块
synchronized(Obejct obj) {
    //同步代码块
    ...
}

上述代码表示在进入同步代码块之前,先要去获取obj的Monitor,如果已经被其他线程获取了,那么当前线程必须等待直至其他线程释放obj的Monitor

这里的obj可以是类.class,表示需要去获取该类的字节码的Monitor,获取后,其他线程无法再去获取到class字节码的Monitor了,即无法访问属于类的同步的静态方法了,但是对于对象的实例方法的访问不受影响

同步方法
public class Test {
    public static Test instance;

    public int val;

    public synchronized void set(int val) {
        this.val = val;
    }

    public static synchronized void set(Test instance) {
        Test.instance = instance;
    }

}

上述使用了synchronized分别修饰了非静态方法和静态方法。
非静态方法可以理解为,需要获取当前对象this的Monitor,获取后,其他需要获取该对象的Monitor的线程会被堵塞。
静态方法可以理解为,需要获取该类字节码的Monitor(因为static方法不属于任何对象,而是属于类的方法),获取后,其他需要获取字节码的Monitor的线程会被堵塞。

四、Object的notify方法和wait方法详解

上面在讲述Monitor机制的时候已经分析了notify和wait的用法,这里具体分析下。

wait方法

wait有三个重载方法,分别如下:

wait()
wait(long millis)
wait(long millis, int nanos) 

后面两个传入了时间参数(nanos表示纳秒),表示如果指定时间过去还没有其他线程调用notify或者notifyAll方法来将其唤醒,那么该线程会自动被唤醒。

调用obj.wait方法需要注意的是,当前线程必须获取到了obj的Monitor,才能去调用其wait方法,即wait必须放在同步方法或同步代码块中。(调用的是obj.wait(),而不是Thread.currentThread.wait())

notify方法

notify有两个方法notify和notifyAll,前者只能唤醒一个正在等待这个对象的monitor的线程,具体由JVM决定,后者则会唤醒所有正在等待这个对象的monitor的线程

需要关注的点:

  • 调用notify方法,并不意味着释放了Monitor,必须要等同步代码块结束后才会释放Monitor
  • 在调用notify方法时,必须保证其他线程处于wait状态,否则调用notify没有任何效果,导致之后其他线程永远处于堵塞状态

猜你喜欢

转载自blog.csdn.net/boyeleven/article/details/81390738