Java多线程并发之ReentrantLock

 更新时间:2023年04月26日 11:50:46   作者:JayChou_Code  
这篇文章主要介绍了Java 多线程并发ReentrantLock,文中有非常详细的代码示例,对正在学习java的小伙伴们有非常好的帮助,需要的朋友可以参考下

ReentrantLock

公平锁和非公平锁

这个类是接口 Lock的实现类,也是悲观锁的一种,但是它提供了 lockunlock方法用于主动进行锁的加和拆。在之前使用的 sychronized关键字是隐式加锁机制,而它是显示加锁,同时,这个类的构造方法提供了公平和非公平的两种机制。

什么是公平和非公平呢?就是多线程对共享资源进行争夺的时候,会出现一个线程或几个线程完全占有共享资源,使得某些线程在长时间处于等待状态。公平就是要等待时间过长的线程先获得锁。

而在 ReentrantLock类中,提供了公平锁和非公平锁的使用。

ReentrantLock源码中,构造器提供了一个参数入口,

public ReentrantLock(boolean fair) {
	sync = fair ? new FairSync() : new NonfairSync();
}

当fair为true的时候,会创造一个 FairSync对象给 sync属性,FairSync是继承自 Sync的类,其中有一个 Lock方法,而在 ReentrantLock的Lcok中使用的是 sync属性的 Lock方法,故能够保证“公平”。

使用非公平锁就不需要在构造器中传参数。

在使用的时候,需要手动上锁和解锁。

使用公平锁,会将占优势的线程进行限制,恢复挂起的线程,但是这个过程在CPU层面来讲,是存在明显时间差异的,非公平锁的执行效率相对更高,所以一般来说不建议使用公平锁,除非现实业务上需要符合实际需求。

重入锁

ReentrantLock本身还支持重入的功能。

重入锁(Reentrant Lock)是一种支持重入的独占锁,它允许线程多次获取同一个锁,在释放锁之前必须相应地多次释放锁。重入锁通常由两个操作组成:上锁(lock)和解锁(unlock)。当一个线程获取了重入锁后,可以再次获取该锁而不被阻塞,同时必须通过相同数量的解锁操作来释放锁。

重入锁具有如下特点:

  • 重入性:重入锁允许同一个线程多次获取同一把锁,避免了死锁的发生。
  • 独占性:与公平锁和非公平锁一样,重入锁也是一种独占锁,同一时刻只能有一个线程持有该锁。
  • 可中断性:重入锁支持在等待锁的过程中中断该线程的执行。
  • 条件变量:在使用 java.util.concurrent.locks.Condition 类配合重入锁实现等待/通知机制时,等待状态总是与重入锁相关联的。

重入锁相对于 synchronized 关键字的优势在于,重入锁具有更高的灵活性和扩展性,支持公平锁和非公平锁、可中断锁和可轮询锁等特性,能够更好地满足多线程环境下的并发控制需要。synchroized也有重入性。

ReentrantLock lock = new ReentrantLock(true);
public void get(){
    while(true){
        try{
            lock.lock();
            lock.lock();
        }catch(Exception exception){

        }finally{
            lock.unlock();
            lock.unlock();
        }
    }
}

可重入的前提 lock是同一个对象,而关键字 synchroized的 Monitor也是同一个对象充当,才能判定为重入。

public void get(){
    while(true){
        synchronized(this){
            System.out.println("外层");
            synchronized(this){
                System.out.println("内层");
            }
        }
    }
}

那么Java是怎么检测锁的重入和获取锁的次数的呢?在之前说过的 ObjectMobitor的C++源代码中有 _recursions和_count来记录锁的重入次数和线程获取锁的次数。这样在Java层面就表示一个锁对象都拥有一个锁计数器 _count和一个指向持有这个锁的线程的指针 _owner只有当前持有锁的线程才能使得计数器+1,其他线程只有等待锁被释放(计数器置0)才能持有并+1。

在源码中,非公平锁的lock方法如下:

//ReentrantLock类中:
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
//0的参数为是expect,是期望值,而1是update,是更新值

在执行comparaAndSetState方法的时候,它会询问锁的计数器(在底层执行compareAndSwapInt的本地方法),并期望数值为0,如果为0返回true,然后设置执行线程主是当前线程。如果非0,那么他就会执行acquire

//AbstractQueuedSynchronizer类中:
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//这里的tryAcquire,需要在其继承的子类中进一步实现对应的功能
//子类可以根据自己的需要重新定义tryAcquire(int arg)的实现方式,从而实现更优秀的锁控制方案:
//而在其子类FairSync中便覆盖了这个方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

将线程放入等待队列。

同时计数器是通过 unlock来-1,所以 lock和unlock次数不匹配就会产生死锁,也就是当两个线程调用同一个 ReentrantLock如果一个线程中的上锁解锁次数不相等,那么计数器没有被清零,当另一个线程请求锁的时候,看到锁计数器不是0,就认为被的线程仍然持有它,所以一直等待它被释放。需要了解底层的可以去看AQS中的release方法。

而在 ReentrantLock中有一个抽象内部类 Sync,它继承自抽象类AbstractQueuedSynchronizer(简称AQS),这个类中有一个内部 Node类,当有线程等待这把锁的时候,会创建一个等待队列,放置这些处于等待的线程。(AQS实现比较复杂,有兴趣可以看看“竹子爱熊猫”大佬的文章。)

小结

ReentrantLock类中,有内部类三个,Sync,FairSync,NonfairSync,他们的关系是Sync是后两个的父类,后两个是兄弟类,同时Sync继承自AQS类,在AQS中有很多实现公平和非公平、可重入的机制,而具体实现效果的是Sync,FairSync,NonfairSync

疑惑

在下列代码中,为什么在第一个线程的最后加上.join(),没有使得线程阻塞,而没有它就会阻塞?

Lock lock = new ReentrantLock();
new CompletableFuture().runAsync(() -> {
    lock.lock();
    try{
        System.out.println(1);
        TimeUnit.SECONDS.sleep(2);
    }catch(Exception e){
    }finally{
}});
//上面加上.join()
new CompletableFuture().runAsync(() -> {
    lock.lock();
    try{
        System.out.println(2);
    }catch(Exception e){
    }finally{
        lock.unlock();
}}).join();

到此这篇关于Java多线程并发之ReentrantLock的文章就介绍到这了,更多相关ava  ReentrantLock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java数据结构之散列表详解

    Java数据结构之散列表详解

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。本文将为大家具体介绍一下散列表的原理及其代码实现
    2022-01-01
  • java中springMVC获取请求参数的方法

    java中springMVC获取请求参数的方法

    这篇文章主要介绍了java中springMVC获取请求参数的方法,springmvc是spring框架的一个模块,springmvc和spring无需通过中间整合层进行整合,需要的朋友可以参考下
    2023-05-05
  • JAVA流控及超流控后的延迟处理实例

    JAVA流控及超流控后的延迟处理实例

    这篇文章主要介绍了JAVA流控及超流控后的延迟处理,以实例形式较为详细的分析了Java进行流量控制的技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2014-12-12
  • java语言描述Redis分布式锁的正确实现方式

    java语言描述Redis分布式锁的正确实现方式

    这篇文章主要介绍了java语言描述Redis分布式锁的正确实现方式,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Mybatis逆向工程笔记小结

    Mybatis逆向工程笔记小结

    MyBatis官方为我们提供了一个逆向工程,通过这个逆向工程,只需要建立好数据表,MyBatis就会根据这个表自动生成pojo类、mapper接口、sql映射文件,本文主要介绍了Mybatis逆向工程笔记小结,具有一定的参考价值,感兴趣的可以了解一下
    2024-05-05
  • Java结构性设计模式中的装饰器模式介绍使用

    Java结构性设计模式中的装饰器模式介绍使用

    装饰器模式又名包装(Wrapper)模式。装饰器模式以对客户端透明的方式拓展对象的功能,是继承关系的一种替代方案,本篇文章以虹猫蓝兔生动形象的为你带来详细讲解
    2022-09-09
  • Linux服务器Java进程消失问题解决

    Linux服务器Java进程消失问题解决

    这篇文章主要介绍了Linux服务器Java进程消失问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • 基于restTemplate遇到的编码问题及解决

    基于restTemplate遇到的编码问题及解决

    这篇文章主要介绍了restTemplate遇到的编码问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java实现替换PDF中的字体功能

    Java实现替换PDF中的字体功能

    文档中可通过应用不同的字体来呈现不一样的视觉效果,通过字体来实现文档布局、排版等设计需要。本文将详细为大家介绍如何利用Java实现替换PDF文中的字体,需要的可以参考一下
    2022-03-03
  • 一文带你搞懂Java中Object类和抽象类

    一文带你搞懂Java中Object类和抽象类

    这篇文章主要为大家详细介绍了Java中Object类和抽象类的定义与使用,文中的示例代码讲解详细,对我们学习Java有一定帮助,需要的可以参考一下
    2022-08-08

最新评论