监视器是一种控制对象并发访问的机制。
这样可以实现以下功能:
线程 1:
public void a()
{
synchronized(someObject) {
// do something (1)
}
}
线程2:
public void b()
{
synchronized(someObject) {
// do something else (2)
}
}
这可以防止线程1和2同时访问被监视(同步)的部分。其中一个将启动,监视器将防止另一个在第一个完成之前访问该区域。
它不是一个特殊的对象,而是放置在类层次结构根处的同步机制:java.lang.Object
。
还有wait
和notify
方法,也将使用对象的监视器在不同的线程之间进行通信。
监视器是一个实体,它拥有锁和等待集合。在Java中,任何Object
都可以充当监视器。
关于Java中监视器的工作原理的详细说明,请阅读《Java并发编程实战》中的监视器机制部分(上述链接显示了Google图书的预览,该部分可供阅读)。
在并发编程中,我们需要关注两件事:
当一个进程/线程正在执行其临界区时,其他进程/线程不允许执行其临界区。 (每个进程都有一个名为“临界区”的代码段,在其中访问共享数据。)
当线程通过协作共同实现共同目标时,这些线程需要彼此协作。 当它们专注于共同目标时,它们需要进行同步。
监视器用于实现互斥和同步。
不要混淆此临界区与临界区,因为此处所述的临界区属于对象级别,而不是线程级别。 共享数据被视为临界区。
每个对象及其类都与监视器相关联。需要受到并发访问保护的对象的实例变量包括与该对象相关联的监视器的临界区,需要受到并发访问保护的类/类的静态变量包括与该类相关联的监视器的临界区。
此临界区受锁的保护,此锁确保互斥。
等待集也与监视器相关联,用于在线程之间提供协调。
一个入口集用于保存已经请求锁但尚未获得锁的线程。
每个对象都与监视器相关联,该监视器具有一个锁,当它访问共享变量时,每个线程都可以使用此锁锁定或解锁对象。明确来说,这意味着每次只有一个线程可以持有监视器上的锁。尝试锁定该锁的任何其他线程都会被阻塞,直到它们可以获得该锁。当新线程尝试获取锁时,如果已经有一个线程拥有锁,则该线程将在入口集上等待以获取锁。当获得锁的线程完成其临界区时,它将释放锁。因此,下一个线程将获取锁,但此下一个线程将从入口集中取出,并由JVM根据一些标准(如FIFO)确定。
在这里,我们实现了互斥,因为我们向线程提供对对象的独占访问权,并且我们不允许任何其他线程进入其临界区。
使用监视器实现互斥的示例Java代码
class Counter
{
private int count = 0;
public void synchronized Increment() {
int n = count;
count = n+1;
} //Here synchronized is used to indicate those things should be done sequentially.
}
使用与监视器相关的等待集(wait set)以及“等待和通知(wait and notify)”或“信号和继续(signal and continue)”机制可实现同步。在某个线程需要特定状态下的数据,而另一个线程负责将数据置于该状态时(例如生产者/消费者问题),同步非常重要。
当线程调用wait()方法并传递对象时,线程被挂起并添加到等待集中,直到其他线程在同一对象上调用notify()或notifyAll()。
notify()方法用于唤醒处于特定对象的等待集中的线程。有两种通知等待线程的方式:
以下是使用监视器在生产者消费者问题中实现同步的示例Java代码
class Buffer {
private char [] buffer;
private int count = 0, in = 0, out = 0;
Buffer(int size)
{
buffer = new char[size];
}
public synchronized void Put(char c) {
while(count == buffer.length)
{
try { wait(); }
catch (InterruptedException e) { }
finally { }
}
System.out.println("Producing " + c + " ...");
buffer[in] = c;
in = (in + 1) % buffer.length;
count++;
notify();
}
public synchronized char Get() {
while (count == 0)
{
try { wait(); }
catch (InterruptedException e) { }
finally { }
}
char c = buffer[out];
out = (out + 1) % buffer.length;
count--;
System.out.println("Consuming " + c + " ...");
notify();
return c;
}
}
参考以下链接:
http://www.csc.villanova.edu/~mdamian/threads/javamonitors.html#:~:text=Java%20associates%20a%20monitor%20with,the%20monitor%20for%20that%20object
https://howtodoinjava.com/java/multi-threading/how-to-use-locks-in-java-java-util-concurrent-locks-lock-tutorial-and-example/
Class
对象将成为它们的监视器。如果您已经有一个同步的method1()并且您声明method2()为同步的,则不会创建新的监视器,并且实际上调用任一方法(在相同的对象上)将尝试锁定相同的监视器。这往往会让新手措手不及。 - Andrzej Doyle