详解java并发之重入锁-ReentrantLock

 更新时间:2019年03月13日 15:37:27   作者:胖虎。。  
这篇文章主要介绍了java并发之重入锁-ReentrantLock,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

目前主流的锁有两种,一种是synchronized,另一种就是ReentrantLock,JDK优化到现在目前为止synchronized的性能已经和重入锁不分伯仲了,但是重入锁的功能和灵活性要比这个关键字多的多,所以重入锁是可以完全替代synchronized关键字的。下面就来介绍这个重入锁。

正文

ReentrantLock重入锁是Lock接口里最重要的实现,也是在实际开发中应用最多的一个,我这篇文章更接近实际开发的应用场景,为开发者提供直接上手应用。所以不是所有方法我都讲解,有些冷门的方法我不会介绍或一句带过。

一、首先先看声明一个重入锁需要使用到那几个构造方法

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

推荐声明方式

private static ReentrantLock lock = new ReentrantLock(true);
private static ReentrantLock locka = new ReentrantLock();

重点说明:

ReentrantLock提供了两个构造方法,对应两种声明方式。

第一种声明的是公平锁,所谓公平锁,就是按照时间先后顺序,使先等待的线程先得到锁,而且,公平锁不会产生饥饿锁,也就是只要排队等待,最终能等待到获取锁的机会。

第二种声明的是非公平锁,所谓非公平锁就和公平锁概念相反,线程等待的顺序并不一定是执行的顺序,也就是后来进来的线程可能先被执行。

ReentrantLock默认是非公平锁,因为:公平锁实现了先进先出的公平性,但是由于来一个线程就加入队列中,往往都需要阻塞,再由阻塞变为运行,这种上下文切换是非常好性能的。非公平锁由于允许插队所以,上下文切换少的多,性能比较好,保证的大的吞吐量,但是容易出现饥饿问题。所以实际生产也是较多的使用非公平锁。

非公平锁调用的是NonfairSync方法。

二、加入锁之后lock方法到底是怎么处理的(只讲非公平锁)
刚才我们说如果是非公平锁就调用NonfairSync方法,那我们就来看看这个方法都做来什么。

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
 
        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
 
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

重点说明:

读前先知:ReentrantLock用state表示“持有锁的线程已经重复获取该锁的次数”。当state(下文用状态二子代替)等于0时,表示当前没有线程持有锁)。
第一步调用compareAndSetState方法,传了第一参数是期望值0,第二个参数是实际值1,当前这个方法实际是调用了unsafe.compareAndSwapInt实现CAS操作的,也就是上锁之前状态必须是0,如果是0调用setExclusiveOwnerThread方法

private transient Thread exclusiveOwnerThread;
 
    
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

可以看出setExclusiveOwnerThread就是线程设置为当前线程,此时说明有一名线程已经拿到了锁。大家都是CAS有三个值,如果旧值等于预期值,就把新值赋予上,所以当前线程得到了锁就会把状态置为1。

第二步是compareAndSetState方法返回false时,此时调用的是acquire方法,参数传1

tryAcquire()方法实际是调用了nonfairTryAcquire()方法。

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
 
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

注释上说的很明白,请求独占锁,忽略所有中断,至少执行一次tryAcquire,如果成功就返回,否则线程进入阻塞--唤醒两种状态切换中,直到tryAcquire成功。详情见链接tryAcquire()、addWaiter()、acquireQueued()挨个分析。

 好,到日前为止大家清楚了lock()方法到调用过程,清楚了,为什么只有得到锁的当前线程才可以执行,没有得到的会在队列里不停的利用CAS原理试图得到锁,CAS很高效,也就是,为什么ReentrantLock比synchronized高效的原因,缺点是很浪费cpu资源。

三、所有线程都执行完毕后调用unlock()方法

unlock()方法是通过AQS的release(int)方法实现的,我们可以看一下:

public void unlock() {
        sync.release(1);
    }
 
 
public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease()是由子类实现的,我们来看一下ReentrantLock中的Sync对它的实现:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

先通过getState获得状态标识,如果这个标识和要释放的数量相等,就会把当前占有锁的线程设置为null,实现锁的释放,然后返回true,否则把状态标识减去releases再返回false。

以上所述是小编给大家介绍的java并发之重入锁-ReentrantLock详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • Java调用打印机的2种方式举例(无驱/有驱)

    Java调用打印机的2种方式举例(无驱/有驱)

    我们平时使用某些软件或者在超市购物的时候都会发现可以使用打印机进行打印,这篇文章主要给大家介绍了关于Java调用打印机的2种方式,分别是无驱/有驱的相关资料,需要的朋友可以参考下
    2023-11-11
  • Java Servlet3.0异步处理问题

    Java Servlet3.0异步处理问题

    这篇文章主要介绍了Java中Servlet3.0异步处理的原理以及遇到的问题分析,需要的朋友参考一下。
    2017-12-12
  • MyBatis中的SQL映射文件配置结果映射的操作指南

    MyBatis中的SQL映射文件配置结果映射的操作指南

    MyBatis 是一款优秀的 ORM 框架,它提供了多种配置方式来定义 SQL 语句以及结果映射规则,本文将介绍 MyBatis 中的 SQL 映射文件如何配置结果映射,包括常规类型、集合类型等多种情况,需要的朋友可以参考下
    2023-07-07
  • java中servlet实现登录验证的方法

    java中servlet实现登录验证的方法

    做web开发,登录验证是免不了的,今天学习了servlet的登录验证,当然是很简单的,没有使用session,request等作用域对象,所以还是可以直接通过地址访问网页的。
    2013-05-05
  • swagger配置正式环境中不可访问的问题

    swagger配置正式环境中不可访问的问题

    这篇文章主要介绍了swagger配置正式环境中不可访问的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • Java并发编程系列之LockSupport的用法

    Java并发编程系列之LockSupport的用法

    这篇文章主要为大家介绍了Java LockSupport的用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助
    2021-11-11
  • Java爬虫实现Jsoup利用dom方法遍历Document对象

    Java爬虫实现Jsoup利用dom方法遍历Document对象

    本文主要介绍了Java爬虫实现Jsoup利用dom方法遍历Document对象,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • Java 实现文件批量重命名亲测可用(精简版)

    Java 实现文件批量重命名亲测可用(精简版)

    本文给大家分享一段自己写的java代码实现文件批量重命名,亲测试过没有任何问题,大家可以放心使用
    2016-11-11
  • 详解Servlet 3.0/3.1 中的异步处理

    详解Servlet 3.0/3.1 中的异步处理

    这篇文章主要介绍了详解Servlet 3.0/3.1 中的异步处理,实例分析了servlet 3.0异步处理的技巧,非常具有实用价值,需要的朋友可以参考下
    2017-04-04
  • Java虚拟机JVM之server模式与client模式的区别

    Java虚拟机JVM之server模式与client模式的区别

    这篇文章主要介绍了Java虚拟机JVM的client模式和Server模式两者的区别和联系
    2017-12-12

最新评论