面试题系列:Synchronized 和 ReentrantLock 的实现原理是什么?它们有什么区别?

whdahanh 发布于 2023-07-14 257 次阅读


作者平台:

| CSDN:https://blog.csdn.net/qq_41153943(ID:江夏、)

| 掘金:https://juejin.cn/user/651387938290686(ID:jiangxia_1024

| 知乎:https://www.zhihu.com/people/1024-paper-96(ID:江夏)

| GitHub:https://github.com/JiangXia-1024?tab=repositories

| 微信公众号:1024笔记

本文一共1784字,预计阅读11分钟

1、什么是Synchronized和ReentrantLock

Synchronized和ReentrantLock都是Java中实现线程同步的机制,它们的目的都是为了保证多个线程访问共享资源时的互斥性和可见性。

Synchronized 的实现原理是基于 Java 中的内置监视器锁(Intrinsic Lock)或称作对象锁(Object Lock),每个 Java 对象都有一个与之关联的锁。

当一个 synchronized 代码块执行时,会首先尝试获得内置监视器锁。如果锁已经被其他线程持有,则当前线程将进入阻塞状态,直到该锁被释放为止;如果锁未被持有,则当前线程获得该锁。在 synchronized 块结束时,会自动释放锁。

ReentrantLock 则是通过 java.util.concurrent.locks.ReentrantLock 类来实现的,它利用了 Java中的AQS(AbstractQueuedSynchronizer) 来实现线程的同步,它允许重入特性,即同一线程可以对已经获取到的锁再次进行请求,如果不支持重入,则会java.lang.IllegalMonitorStateException异常。

2、ReentrantLock 和 synchronized的区别

一般来说ReentrantLock 和 synchronized 相比,具有以下优点:

1. 可以实现公平锁和非公平锁:ReentrantLock 的构造函数中提供了一个布尔类型的参数 fair,可以选择创建公平锁还是非公平锁。而 synchronized 总是非公平锁。

2. 支持多个 Condition 对象:ReentrantLock 中的 Condition 类可以创建多个等待队列,以便更细粒度地控制线程的唤醒。

3. 支持中断等待锁的线程:通过 ReentrantLock 的 lockInterruptibly 方法,可以实现对等待锁的线程进行中断操作。synchronized 则无法中断等待锁的线程。

4. 提供了一些高级功能:如 tryLock() 尝试获得锁但不阻塞;lockInterruptibly() 能够响应线程的中断请求。

在使用 ReentrantLock 时,需要手动释放锁,否则可能会导致死锁等问题。

总结下来,synchronized 和 ReentrantLock 都可以实现线程同步,但在特定的场景下有着各自的优缺点。通常情况下,synchronized 更加简单易用,而 ReentrantLock 在某些复杂场景下能够提供更丰富的功能。

3、代码演示

接下来代码分别演示Synchronized和ReentrantLock的使用:

使用 synchronized 实现线程同步的示例代码:

public class SynchronizedDemo {    private int count = 0;
public synchronized void increment() { // 对临界区进行加锁保护 count++; }
public static void main(String[] args) throws Exception { final SynchronizedDemo demo = new SynchronizedDemo();
// 创建两个线程并启动 Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { demo.increment(); } } }); t1.start();
Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { demo.increment(); } } }); t2.start();
// 等待两个线程执行完毕 t1.join(); t2.join();
// 输出结果 System.out.println("count = " + demo.count); }}

在 increment 方法上添加 synchronized 关键字,可以实现对临界区代码块的互斥访问,从而能够确保多个线程在同时对共享变量 count 进行修改时不会发生冲突。

使用 ReentrantLock 实现线程同步的示例代码如下:

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo { private int count = 0; private Lock lock = new ReentrantLock();
public void increment() { // 上锁 lock.lock(); try { count++; } finally { // 释放锁 lock.unlock(); } }
public static void main(String[] args) throws Exception { final ReentrantLockDemo demo = new ReentrantLockDemo();
// 创建两个线程并启动 Thread t1 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { demo.increment(); } } }); t1.start();
Thread t2 = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { demo.increment(); } } }); t2.start();
// 等待两个线程执行完毕 t1.join(); t2.join();
// 输出结果 System.out.println("count = " + demo.count); }}
这段代码和使用 synchronized 的示例代码类似,不同之处在于它使用了 ReentrantLock 类来实现互斥访问。首先创建了一个 ReentrantLock 对象,在 increment方法中进行加锁和解锁操作。

在使用 ReentrantLock 时,最好在 finally 块中进行解锁操作,确保线程不会因为被锁死而无法释放锁。另外,在声明 ReentrantLock 变量时,尽量将其设置成 private,并给出相应的 getter/setter 方法,这样能够防止直接修改内部状态。

4、总结

Synchronized 和 ReentrantLock 都可以实现线程同步,但在特定的场景下有着各自的优缺点。通常情况下,synchronized 更加简单易用,而 ReentrantLock 在某些复杂场景下能够提供更丰富的功能。

最后感谢大家的关注!

-END-


相关推荐:

                 我就知道你在看!

本篇文章来源于微信公众号:作者:原创 江夏 1024笔记 1024笔记 微信号 Jang_1024 功能介绍 努力就行了,其他的交给时间! 发表于
转载地址:https://mp.weixin.qq.com/s/AN0eKCARZAw11C1c040-qQ



微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2023-07-14