JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Java中在监视器(Monitor)内部,是如何做到线程同步的?

wys521 2024-11-07 13:43:30 精选教程 21 ℃ 0 评论

监视器(Monitor)是在Java中管理并发访问共享资源的机制,包括一个锁和一个或多个条件变量,主要用于协调线程之间的同步和通信,在Java中,每个Java对象都自带了一个监视器,这样使得每个对象都可以作为同步操作的基本单位。

下面我们就来介绍一下监视器在Java中的工作原理和线程同步的实现方式。

Synchronized关键字

synchronized是Java中提供的用来实现同步机制的关键字,可以用来修饰方法或者代码块,它确保在同一时间只有一个线程可以执行,从而实现线程同步。

  • 同步方法:在方法声明中使用public synchronized void method() { ... }。这里,锁是当前实例对象(对于实例方法)或类对象(对于静态方法)。
  • 同步代码块:在代码块中使用synchronized(obj) { ... }。这里,锁是obj对象。

内置锁(对象锁)

每个Java对象都有一个内置锁,当一个线程进入synchronized方法或synchronized代码块时,它必须首先获得这个锁。如果锁已经被其他线程持有,那么该线程将被阻塞,直到锁被释放。

监视器的实现原理

监视器的实现依赖于JVM的底层机制,主要涉及到如下的几个方面的内容。

对象头(Object Header)

每个Java对象在内存中都有一个对象头,其中存储了锁状态和线程持有信息,而对象头中的具体信息因JVM实现而异,但通常包括锁标志位、持有锁的线程ID和其他元数据。

锁的状态

JVM通过对象头的标志位来管理锁的状态,常见的锁状态包括,如下一些。

  • 无锁(Unlocked):对象未被任何线程持有。
  • 偏向锁(Biased Locking):对象被单个线程持有,并优化了锁的获取和释放过程。
  • 轻量级锁(Lightweight Locking):对象被多个线程竞争,但没有发生严重的竞争
  • 重量级锁(Heavyweight Locking):对象发生了严重的锁竞争,进入操作系统的重量级锁机制。

锁竞争和性能优化

JVM在实现监视器时,会通过多种优化技术以减少锁竞争对性能的影响,例如偏向锁和轻量级锁都是为了减少线程竞争时的开销,提高程序的并发性能。

  • 偏向锁:当一个线程多次获取同一个锁时,锁会偏向该线程,减少锁的获取和释放操作。
  • 轻量级锁:通过CAS(Compare-And-Swap)操作实现锁的获取和释放,适用于低竞争场景。
  • 重量级锁:当竞争激烈时,JVM使用操作系统提供的线程阻塞和唤醒机制。

监视器的工作原理

监视器的工作机制可以分为如下的三个部分

进入监视器

当一个线程尝试进入synchronized方法或代码块的时候,它必须要获取到相应的监视器的锁,如果这个锁是可以使用的,也就是说没有被其他的线程占用,那么线程就会获取锁并且进入到临界区中。如果锁是不可用的,那么线程就会被阻塞,放入到该监视器的等待队列中,直到锁被释放。

执行临界区代码

当线程获取到锁之后,就会执行synchronized方法或代码块中的代码,期间,其他线程尝试进入该临界区的时候,就会被阻塞,这样就确保了只有一个线程能够执行该代码。

退出监视器

当线程退出synchronized方法或代码块时,它会释放监视器锁,如果有其他线程在等待该锁,则其中一个线程将被唤醒并尝试重新获得锁。

条件变量和等待/通知机制

Java提供了Object类中的wait(), notify(), notifyAll()方法,用于实现更高级的线程间通信。

  • wait():当一个线程调用对象的wait()方法时,它将释放该对象的锁,并进入该对象的等待队列,直到其他线程调用notify()或notifyAll()方法唤醒它。
  • notify():唤醒正在等待该对象锁的单个线程。如果有多个线程在等待,则选择一个(由JVM实现决定)进行唤醒。
  • notifyAll():唤醒所有正在等待该对象锁的线程。这些线程会再次竞争对象锁,只有一个线程能成功获得锁并继续执行。

Java中的监视器示例

以下是一个简单的示例,展示如何使用synchronized关键字和wait/notify方法实现线程同步和通信。

class SharedResource {
    private int count = 0;

    public synchronized void increment() {
        count++;
        notify(); // 通知等待的线程
    }

    public synchronized void decrement() {
        while (count == 0) {
            try {
                wait(); // 释放锁并等待
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        count--;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class MonitorExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();

        // 线程1:增加资源计数
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.increment();
                System.out.println("Incremented: " + resource.getCount());
            }
        });

        // 线程2:减少资源计数
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                resource.decrement();
                System.out.println("Decremented: " + resource.getCount());
            }
        });

        t1.start();
        t2.start();
    }
}

在这个示例中increment()方法和decrement()方法使用synchronized关键字来确保对共享资源的互斥访问。wait()和notify()方法用于实现线程之间的通信,当计数为0时,调用decrement()方法的线程将进入等待状态,直到有其他线程调用increment()方法并通知它。

总结

在Java中,监视器通过内置锁和条件变量来实现线程同步。通过synchronized关键字,线程可以安全地进入和退出临界区,而通过wait(), notify(), notifyAll()方法,线程可以实现复杂的等待/通知机制。监视器确保了在任何时刻,只有一个线程可以执行受保护的代码,从而避免并发冲突。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表