前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >彻底理解Java并发:ReentrantLock锁

彻底理解Java并发:ReentrantLock锁

作者头像
栗筝i
发布2022-12-01 21:39:16
5550
发布2022-12-01 21:39:16
举报
文章被收录于专栏:迁移内容迁移内容

本篇内容包括:为什么使用 Lock、Lock 锁注意事项、ReentrantLock 和 synchronized 对比、ReentrantLock (加锁、解锁、公平锁与非公平锁、ReentrantLock 如何实现可重入)等内容。

一、Lock 锁

1、为什么使用 Lock

synchronized 线程等待时间过长,获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,这将极大的影响程序执行效率。

synchronized 操作场景,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

2、注意事项

也就是说 Lock 提供了比 synchronized 更多的功能。但是要注意以下几点

  • Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
3、ReentrantLock 和 synchronized

ReentrantLock 是 java.util.concurrent.locks 包中的一个类,是独占锁,为最后一个执行 lock 操作成功且为释放锁的线程锁拥有

ReentrantLock 是可重入的互斥锁,虽然具有与 synchronized 相同功能,但是会比 synchronized 更加灵活

ReentrantLock 使用代码实现了和 synchronized 一样的语义,包括可重入,保证内存可见性和解决竞态条件问题等。与 synchronized 相较之下:

  • 便利性:Synchronized 用法更简洁,由编译器去保证锁的加锁和释放; ReenTrantLock 需要手动加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在 finally 中声明释放锁。
  • 锁的细粒度和灵活度:ReenTrantLock 优于 Synchronized

此外,以下特点是 ReenTrantLock 独有:

  • ReenTrantLock 可以指定是公平锁还是非公平锁; synchronized 只能是非公平锁。
  • ReenTrantLock 提供了一个 Condition 类,用来实现唤醒特定的线程; synchronized 要么随机唤醒一个线程要么唤醒全部线程。
  • ReenTrantLock 提供了一种能够中断等待锁的线程的机制。

二、ReentrantLock

ReentrantLock,它是一个“可重入”锁。

什么是“可重入”?简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该锁的使用权”

正经地讲就是:假如访问一个资源A需要获得其锁lock,如果之前没有其他线程获取该锁,那么当前线程就获锁成功,此时该线程对该锁后续所有“请求”都将立即得到“获锁成功”的返回,即同一个线程可以多次成功的获取到之前获得的锁。“可重入”可以解释成“同一个线程可多次获取”。

大致的特性

  • 基本锁的特性:加锁、解锁
  • ReentrantLock的补充特性:可重入、公平、非公平
1、加锁、解锁

这两个方法在源码中加锁方法即为lock(),解锁方法即为unLock() ,实现如下:

代码语言:javascript
复制
//加锁
public void lock() {
    sync.lock();
}

//释放锁
public void unlock() {
    sync.release(1);
}

从上述可以知道这两个方法实际上是操作了一个叫做 sync 的对象,调用该对象的 lock 和 release 操作来实现,sync 是什么东西?ReentrantLock 类的源码片段:

代码语言:javascript
复制
public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;
}

ReentrantLock 实现了 Lock 接口,操作其成员变量 sync 这个 AQS 的子类,来完成锁的相关功能。而 sync 这个成员变量有2种形态:NonfairSync 和 FairSync,在源码中,只有在2个构造函数的地方对sync对象做了初始化

代码语言:javascript
复制
/** 所有锁操作都是基于这个字段 */
private final Sync sync;
/**
 * 通过该构造函数创建额ReentrantLock是一个非公平锁
 */
public ReentrantLock() {
    sync = new NonfairSync();
}
/**
 * 如果入参为true,则创建公平的ReentrantLock;
 * 否则,创建非公平锁
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

这两个对象(NonfairSync和NonfairSync)也是 ReentrantLock 的内部类,FairSync 和 NonFairSync 在类结构上完全一样且均继承于 Sync。

img
img

ReentrantLock的构造函数中,默认的无参构造函数将会把Sync对象创建为NonfairSync对象,这是一个“非公平锁”;而另一个构造函数ReentrantLock(boolean fair)传入参数为true时将会把Sync对象创建为“公平锁”FairSync

2、公平锁与非公平锁
img
img

FairSync 在 tryAquire 方法中,当判断到锁状态字段state == 0 时,不会立马将当前线程设置为该锁的占用线程,而是去判断是在此线程之前是否有其他线程在等待这个锁(执行hasQueuedPredecessors() 方法),如果是的话,则该线程会加入到等待队列中,进行排队(FIFO,先进先出的排队形式)。这也就是为什么 FairSync 可以让线程之间公平获得该锁。

NoFairSync的tryAquire 方法中,没有判断是否有在此之前的排队线程,而是直接进行获锁操作,因此多个线程之间同时争用一把锁的时候,谁先获取到就变得随机了,很有可能线程A比线程B更早等待这把锁,但是B却获取到了锁,A继续等待(这种现象叫做:线程饥饿)

到此,我们已经大致理解了 ReentrantLock 是如何做到不同线程如何“公平”和“非公平”获锁。

3、如何实现可重入

我们有提到加锁操作会对 state 字段进行 +1 操作

这里需要注意到 AQS 中很多内部变量的修饰符都是采用的 volital,然后配合 CAS 操作来保证 AQS 本身的线程安全(因为 AQS 自己线程安全,基于它的衍生类才能更好地保证线程安全),这里的 state 字段就是 AQS 类中的一个用 volitale 修饰的 int 变量

state 字段初始化时,值为 0。表示目前没有任何线程持有该锁。当一个线程每次获得该锁时,值就会在原来的基础上加 1,多次获锁就会多次加 1(指同一个线程),这里就是可重入。因为可以同一个线程多次获锁,只是对这个字段的值在原来基础上加1; 相反 unlock 操作也就是解锁操作,实际是是调用 AQS 的 release 操作,而每执行一次这个操作,就会对 state 字段在原来的基础上减1,当 state==0 的 时候就表示当前线程已经完全释放了该锁。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Lock 锁
    • 1、为什么使用 Lock
      • 2、注意事项
        • 3、ReentrantLock 和 synchronized
        • 二、ReentrantLock
          • 1、加锁、解锁
            • 2、公平锁与非公平锁
              • 3、如何实现可重入
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档