如果您上过大学的操作系统课程,您可能还记得监视器是操作系统中同步的重要概念。它也用于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
- 代码地址:代码Github地址