在Java中,监视器和锁有什么区别?

16

使用synchronized关键字方法,使用javap命令查看字节码,发现使用了监视器,如果在实现同步时可能会调用监视器,这是我的理解,对吗?如果不正确,请纠正。它们之间有什么关系?锁和监视器之间的关系是什么?

6个回答

11

根据《锁与同步》的官方文档:

  • 同步块是围绕着一个称为“内部实体”或“监视器锁”的东西建立的。(API 规范通常将此实体简称为“监视器”)。
  • 每个对象都有一个与之关联的内部实体。按照惯例,线程必须在访问对象之前获取该对象的监视器锁,并且在完成后释放监视器锁。在线程拥有锁期间,可以说它拥有该锁。只要线程拥有监视器锁,就没有其他线程能够获取相同的锁。当另一个线程尝试获取锁时,将被阻塞。
  • 当一个线程释放锁时,该操作与任何随后对同一锁的获取之间建立了先行发生关系。

因此,监视器和锁不能比较差异,而是相互补充。Java 中的每个对象都与一个监视器关联,线程可以锁定解锁该监视器。


1
@Harash,你能描述一下这两个概念之间的关系吗?监视器和锁之间的确切关系是什么?锁是监视器的子集还是相反?锁只是监视器的一种实现(而监视器是一个抽象概念)吗?到目前为止,我无法找到任何具体的答案来回答这个问题。 - Martin
4
因此,监视器和锁无法进行比较以找出差异,相反,它们互为补充。你引用的来源似乎表明,在这个上下文中,“锁”和“监视器”这两个术语是同义词 - Matthias Braun

6

锁是一种数据,逻辑上属于堆内存中对象的头部。JVM中的每个对象都有这个锁(或互斥锁),任何程序都可以使用它来协调对该对象的多线程访问。如果任何线程想要访问该对象的实例变量,则该线程必须“拥有”该对象的锁(在锁内存区域设置某个标志)。所有试图访问该对象变量的其他线程都必须等待,直到拥有锁的线程释放该对象的锁(取消标志)。

一旦线程拥有一个锁,它可以多次请求相同的锁,但在将锁提供给其他线程之前,必须相同次数释放锁。例如,如果一个线程请求三次锁,那么该线程将继续拥有锁,直到它释放了三次。

请注意,锁是由线程显式请求时获取的。在Java中,可以使用synchronized关键字、wait和notify来实现。

监视器

监视器是一种同步构造,允许线程同时具有互斥性(使用锁)和合作性,即能够使线程等待某个条件为真(使用等待集)。

换句话说,除了实现锁的数据之外,每个Java对象还与实现等待集的数据在逻辑上相关联。锁帮助线程独立地在共享数据上工作,而不会互相干扰;等待集帮助线程相互合作,共同朝着一个共同目标努力,例如所有等待的线程将被移动到此等待集中,并在锁释放后得到通知。这个等待集通过使用锁(互斥量)来构建监视器。

要获取更多澄清,请参考-

理解线程、监视器和锁

锁和监视器之间的区别 - Java并发


很好的回答。你提供的第一个链接已经失效了,提醒一下。 - theRiley

3

这篇文档 https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html 并不是一个非常好的地方来了解Lock和Monitor之间的区别,尤其是它提到的术语:intrinsic lockmonitor 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(),以防有消费者在等待队列不为空的条件。
在这里,监视器提供了waitnotify的能力来使线程协作。
希望这能帮助你理解监视器和锁之间的差异。
参考文献:

0

其他答案已经提供了这两个术语的定义。让我们来看一个实际的使用案例:

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秒。


0
本文档中,你可以找到你问题的答案:

同步。Java编程语言提供了多种线程间通信机制。其中最基本的方法是同步,它使用监视器实现。Java中的每个对象都与一个监视器相关联,线程可以锁定或解除锁定监视器。


可以说每个对象都有一个监视器,但是当它不进行同步时,监视器就没有用处,因此不会激活调用,这涉及到在代码同步锁定时监视数据的监视器。可以这样理解吗? - DuZhentong
这并没有提供锁的定义。 - Jared Beach

0

监视器是互斥、等待外部条件为真和通知其他线程该事件的能力的组合。

条件和通知部分本质上是协作

提供互斥。这意味着它防止多个线程同时访问相同的数据。

注意,监视器使用互斥。因此,锁用于实现监视器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接