Object的对象监视器

1,160 阅读4分钟

如果您上过大学的操作系统课程,您可能还记得监视器是操作系统中同步的重要概念。它也用于Java同步。这篇文章使用类比来解释“监视器”的基本概念。

1. 什么是监视器?

监视器可以视为包含特殊房间的建筑物。特殊房间一次只能容纳一个客户(线程)。房间通常包含一些数据和代码。

如果客户要占用Special Room,则必须先进入Hallway(Entry Set)以等待。调度程序将根据某些条件(例如FIFO)选择一个。如果由于某种原因他被停职,他将被送往Wait Room,并安排以后再进入Special Room。如上图所示,该建筑Building中有3个房间。

简而言之,监视程序是监视线程对特殊房间的访问的工具。它确保只有一个线程可以访问受保护的数据或代码。

2. 如何用Java实现?

在Java虚拟机中,每个对象和类在逻辑上都与监视器monitor关联。

为了实现监视器的互斥功能,将一个锁(有时称为互斥锁mutex)与每个对象和类关联。这在操作系统书籍中称为信号量semaphore,互斥体是二进制信号量。

如果一个线程对某些数据拥有锁,那么只有拥有该锁的线程释放该锁,其他线程才能获得该锁。如果在进行多线程编程时始终需要编写信号量,那将不方便。幸运的是,由于JVM自动为我们执行了此操作,因此我们不需要这样做。

为了声明一个监视区域,这意味着多个线程无法访问数据,Java提供了同步语句和同步方法。一旦代码中嵌入了synchronized关键字,它就是一个监视区域。 JVM自动在后台实现锁。

3. 在Java同步代码中,监视器是哪一部分?

我们知道每个对象/类都与一个Monitor关联。我认为可以说每个对象都有一个监视器,因为每个对象可以有自己的关键部分,并且能够监视线程序列。 为了启用不同线程的协作,Java中java.lang.Object提供了wait()notify()来挂起一个线程和唤醒另一个正在此对象上等待的线程。此外,还有其他3个版本:

wait(long timeout, int nanos)
wait(long timeout) notified by other threads or notified by timeout. 
notify(all)

这些方法只能在同步语句或同步方法中调用(获取到对象的监视器)。原因是,如果一个方法不需要相互排斥,则不需要监视线程之间或进行协作,则每个线程都可以自由访问该方法。

简而言之,当我们调用wait()时,这将迫使当前线程等待,直到其他某个线程在同一对象上调用notify()notifyAll()为止。 为此,当前线程必须拥有对象的监视器。根据Javadocs,在以下情况下可能会发生这种情况:

  • 我们已经为给定对象执行了同步实例方法
  • 我们已经在给定对象上执行了同步块的主体
  • 通过对Class类型的对象执行同步的静态方法 请注意,一次只有一个活动线程可以拥有一个对象的监视器。

通过notify()/notiryAll()被唤醒的线程,不保证立刻能获取到对象的监视器,需要与其他线程进行竞争去锁定object,在竞争的过程中,其没有任何的特权和劣势,众生平等。

4. 代码实战

需求:从A~Z中,有26个字母,通过两个线程one by one的依次打印这个26个字母。

/**
 * print one by one, use two thread.
 *
 * @author shandianlala@gmail.com
 * @version 1.0
 * @date 2020-04-18 11:18
 */
public class ThreadSchedulingTest {

    public volatile AtomicInteger readIndex = new AtomicInteger(0);

    public List<String> stringList = Arrays.asList("ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""));

    public ReentrantLock lock = new ReentrantLock(true);

    /**
     * 使用对象监视器的方式实现(object monitor)
     */
    @Test
    public void testThreadScheduling() {
        Thread thread1 = generateThread("Thread-0");
        Thread thread2 = generateThread("Thread--1");
        thread1.start();
        thread2.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread1 in main method, state=" + thread1.getState().name());
        System.out.println("thread2 in main method, state=" + thread2.getState().name());
    }

    public Thread generateThread(String threadName) {
        Thread thread = new Thread(() -> {
            synchronized (stringList) {
                String name = Thread.currentThread().getName();
                while (readIndex.get() < stringList.size()) {
                    System.out.println(name + " | " + stringList.get(readIndex.get()));
                    readIndex.getAndIncrement();
                    try {
                        stringList.notify();
                        stringList.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.setName(threadName);
        return thread;
    }

}

测试用例输出:

Thread-0 | A
Thread--1 | B
Thread-0 | C
Thread--1 | D
Thread-0 | E
Thread--1 | F
Thread-0 | G
Thread--1 | H
Thread-0 | I
Thread--1 | J
Thread-0 | K
Thread--1 | L
Thread-0 | M
Thread--1 | N
Thread-0 | O
Thread--1 | P
Thread-0 | Q
Thread--1 | R
Thread-0 | S
Thread--1 | T
Thread-0 | U
Thread--1 | V
Thread-0 | W
Thread--1 | X
Thread-0 | Y
Thread--1 | Z

5. 参考文章