使用synchronized关键字方法,使用javap命令查看字节码,发现使用了监视器,如果在实现同步时可能会调用监视器,这是我的理解,对吗?如果不正确,请纠正。它们之间有什么关系?锁和监视器之间的关系是什么?
使用synchronized关键字方法,使用javap命令查看字节码,发现使用了监视器,如果在实现同步时可能会调用监视器,这是我的理解,对吗?如果不正确,请纠正。它们之间有什么关系?锁和监视器之间的关系是什么?
根据《锁与同步》的官方文档:
- 同步块是围绕着一个称为“内部实体”或“监视器锁”的东西建立的。(API 规范通常将此实体简称为“监视器”)。
- 每个对象都有一个与之关联的内部实体。按照惯例,线程必须在访问对象之前获取该对象的监视器锁,并且在完成后释放监视器锁。在线程拥有锁期间,可以说它拥有该锁。只要线程拥有监视器锁,就没有其他线程能够获取相同的锁。当另一个线程尝试获取锁时,将被阻塞。
- 当一个线程释放锁时,该操作与任何随后对同一锁的获取之间建立了先行发生关系。
因此,监视器和锁不能比较差异,而是相互补充。Java 中的每个对象都与一个监视器关联,线程可以锁定或解锁该监视器。
锁
锁是一种数据,逻辑上属于堆内存中对象的头部。JVM中的每个对象都有这个锁(或互斥锁),任何程序都可以使用它来协调对该对象的多线程访问。如果任何线程想要访问该对象的实例变量,则该线程必须“拥有”该对象的锁(在锁内存区域设置某个标志)。所有试图访问该对象变量的其他线程都必须等待,直到拥有锁的线程释放该对象的锁(取消标志)。
一旦线程拥有一个锁,它可以多次请求相同的锁,但在将锁提供给其他线程之前,必须相同次数释放锁。例如,如果一个线程请求三次锁,那么该线程将继续拥有锁,直到它释放了三次。
请注意,锁是由线程显式请求时获取的。在Java中,可以使用synchronized关键字、wait和notify来实现。
监视器
监视器是一种同步构造,允许线程同时具有互斥性(使用锁)和合作性,即能够使线程等待某个条件为真(使用等待集)。
换句话说,除了实现锁的数据之外,每个Java对象还与实现等待集的数据在逻辑上相关联。锁帮助线程独立地在共享数据上工作,而不会互相干扰;等待集帮助线程相互合作,共同朝着一个共同目标努力,例如所有等待的线程将被移动到此等待集中,并在锁释放后得到通知。这个等待集通过使用锁(互斥量)来构建监视器。
要获取更多澄清,请参考-
这篇文档 https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html 并不是一个非常好的地方来了解Lock和Monitor之间的区别,尤其是它提到的术语:intrinsic lock
、monitor lock
和简单的monitor
,这似乎表明monitor和lock可以互换使用。
但这并不正确。
Monitor是用于多线程同步的一种结构。它包括一个锁和几个条件变量。条件变量是一个队列,线程可以将它们放在上面,当给定条件不如所需时,其他某个线程可以唤醒这些线程,使得该条件成立。条件变量是帮助线程协作的一种方式。
在简单的同步情况下,我们仅使用monitor提供的锁,例如这个例子:
class SimpleCase {
int counter;
synchronized inc() int {
return counter++;
}
}
inc()
操作的线程无需协作,只需要锁定以使线程互斥,从而使counter
线程安全。
但在更复杂的情况下,不仅需要互斥(mutex),还需要协作。
例如,有界消费者/生产者问题:多个消费者和生产者从队列中消费和发送消息。由于消息队列具有最大大小,因此需要协作,当队列已满时,无法再发送消息;当队列为空时,无法再消费消息。
以下是显示生产者的代码:
package monitor;
public class Producer {
BoundedQueue queue;
public Producer(BoundedQueue queue) {
this.queue = queue;
}
public void send(int msg) throws InterruptedException {
synchronized (queue) {
// wait till there is room to produce
while (queue.isFull()) {
queue.wait();
}
// business logic here
queue.add(msg);
System.out.println("sent:" + msg + ", from:" + Thread.currentThread().getName());
// before exit, call notify() to wake up waiting threads
queue.notifyAll();
}// implicit release the lock when exiting the synchronized block
}
}
BoundedQueue
作为监视器。当队列满时,生产者需要wait()
,而当队列有可用空槽时,生产者需要被notified以从等待中唤醒。在生产者向队列发送数据后,还需要调用notifyAll()
,以防有消费者在等待队列不为空的条件。wait
和notify
的能力来使线程协作。其他答案已经提供了这两个术语的定义。让我们来看一个实际的使用案例:
public class Test {
public synchronized static void testMonitor() throws InterruptedException {
Thread.sleep(1000);
System.out.println("done");
}
public static void testLock(Object lock) throws InterruptedException {
synchronized (lock){
Thread.sleep(1000);
System.out.println("done");}
}
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
List<Thread> threadList = new ArrayList<>();
for(int i=0 ;i< 10; i++){
Thread thread = new Thread(() -> {
try {
Test.testMonitor();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadList.add(thread);
thread.start();
}
for(int i=0 ;i< 10; i++){
threadList.get(i).join();
}
long end = System.currentTimeMillis();
System.out.println((end - start)/1000);
}
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
List<Thread> threadList = new ArrayList<>();
for(int i=0 ;i< 10; i++){
Object lock = new Object();
Thread thread = new Thread(() -> {
try {
Test.testLock(lock);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadList.add(thread);
thread.start();
}
for(int i=0 ;i< 10; i++){
threadList.get(i).join();
}
long end = System.currentTimeMillis();
System.out.println((end - start)/1000);
}}
第一种方法testMonitor使用监视器(内部锁)。运行第一个主方法将打印执行时间为10秒。
第二种方法testLock使用对象锁。运行第二个主方法将打印执行时间为1秒。
同步。Java编程语言提供了多种线程间通信机制。其中最基本的方法是同步,它使用监视器实现。Java中的每个对象都与一个监视器相关联,线程可以锁定或解除锁定监视器。