synchronized 关键字是否实现了一种重入锁?请解释其实现原理。

参考回答**

是的,synchronized 关键字实现了一种重入锁。重入锁意味着同一个线程可以多次获取同一个锁,而不会导致死锁。

解释

  • 重入:当一个线程已经持有某个对象的锁时,它可以再次请求该锁而不会被阻塞。这意味着同一线程可以多次进入同步代码块,而不需要等待自己释放锁。
  • 实现原理synchronized 是通过 内置的监视器锁(monitor lock) 来实现的。每个对象都有一个与之关联的监视器锁,当线程进入一个被 synchronized 修饰的代码块时,首先尝试获取该锁。如果该锁已经被当前线程占有,线程就可以继续进入同步代码块,而不被阻塞。

详细讲解与拓展

1. 重入锁的实现原理

Java 中的 synchronized 是基于对象的监视器锁(monitor lock)实现的。每个对象都隐式地关联一个监视器锁。通过 synchronized 修饰的代码块或者方法,会使用该对象的监视器锁来保证同步性。

在实现重入锁时,synchronized 利用了一个线程的 锁计数器 来实现。每当线程获取锁时,锁计数器就会增加。当线程释放锁时,锁计数器减少,直到计数器为 0 时,锁才会被其他线程获取。

工作过程

  1. 初次获取锁:当一个线程第一次访问同步代码块时,它会尝试获取锁。如果锁未被其他线程占用,线程会成功获取该锁,进入同步代码块,并将锁计数器设置为 1。
  2. 重入锁:如果线程在已持有该锁的情况下再次进入同步代码块,锁计数器将增加。例如,如果同一线程再次请求锁,计数器增加到 2。此时,线程可以继续执行同步代码块。
  3. 释放锁:当线程退出同步代码块时,锁计数器减少。如果锁计数器为 0,表示锁已被完全释放,其他线程可以获取该锁。

2. 代码示例:重入锁

public class ReentrantLockExample {
    private synchronized void method1() {
        System.out.println("Method 1 entered.");
        method2();  // 在方法 1 中调用方法 2
    }

    private synchronized void method2() {
        System.out.println("Method 2 entered.");
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        example.method1();  // 调用 method1,自动调用 method2
    }
}

输出

Method 1 entered.
Method 2 entered.

分析

  • method1()method2() 都是同步方法,它们会尝试获取同一个锁。
  • method1() 被调用时,它会成功获取锁并进入方法体。然后在 method1() 中调用 method2(),由于线程已经持有该锁,method2() 可以再次成功获取同一个锁并执行。

这就是重入锁的特性:同一线程可以多次获取同一个锁,而不会阻塞自己。


3. synchronized 的底层实现

synchronized 的实现依赖于 monitor(监视器锁),在 JVM 中每个对象都有一个监视器。JVM 对象锁的底层实现是通过 对象头对象监视器 来管理的。

  1. 对象头:每个对象的对象头中存储了对象的锁信息,包括锁的状态和持有锁的线程信息。
  2. 对象监视器:对象监视器是一个与每个对象相关联的内核级别的锁,它负责管理线程对该对象锁的获取和释放。

当一个线程请求同步代码块时,JVM 会检查该对象的监视器,查看该线程是否已经持有锁。如果该线程已经持有锁,锁计数器将增加(即重入)。如果线程没有持有锁,JVM 会将该线程添加到锁的等待队列,直到锁可用。

4. 重入锁的优势与局限

  • 优势
    1. 避免死锁:由于同一线程可以多次获取锁,避免了因为自己等待自己释放锁导致的死锁问题。
    2. 递归调用支持:当线程在方法内部递归调用同步方法时,不需要担心锁被阻塞,简化了递归算法的设计。
  • 局限
    1. 性能开销:每次获取锁时都需要操作锁计数器,虽然开销相对较小,但频繁的锁获取和释放可能影响性能。
    2. 锁竞争:虽然 synchronized 支持重入,但当多个线程竞争同一锁时,仍然会导致性能瓶颈。

5. 相关拓展:重入锁的其他实现

Java 还提供了显式的 重入锁 实现,如 ReentrantLock 类。ReentrantLock 类与 synchronized 类似,但提供了更细粒度的控制和更强大的功能(如尝试获取锁、定时锁等)。

ReentrantLock 示例

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void method1() {
        lock.lock();  // 获取锁
        try {
            System.out.println("Method 1 entered.");
            method2();
        } finally {
            lock.unlock();  // 释放锁
        }
    }

    public void method2() {
        lock.lock();
        try {
            System.out.println("Method 2 entered.");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        example.method1();  // 调用 method1,自动调用 method2
    }
}

优势

  • 可以尝试获取锁(tryLock())。
  • 支持定时获取锁(lock(long timeout))。
  • 支持中断的锁获取(lockInterruptibly())。

总结

  • synchronized 实现了重入锁,允许同一线程多次进入同步代码块。
  • 其原理是通过锁计数器和监视器机制实现的,当线程获取锁时,计数器增加,释放锁时,计数器减少,直到为 0 才释放锁。
  • 优势:避免了死锁和递归调用的阻塞,简化了编程。
  • 局限:性能可能受到一定影响,尤其是高竞争的情况下。

发表评论

后才能评论