Java并发编程之ReentrantLock解析

 更新时间:2023年12月20日 10:00:53   作者:Brain_L  
这篇文章主要介绍了Java并发编程之ReentrantLock解析,ReentrantLock内容定义了一个抽象类Sync,继承自AQS,而不是自己去继承AQS,所有对ReentrantLock的操作都会转化为对Sync的操作,需要的朋友可以参考下

ReentrantLock

前篇写了JUC的基础AQS,其中介绍了它提供的很多模板方法,但是在实际编程中我们不会直接使用它,而是会使用它的各种实现。

本文将介绍在实际使用中出现频率很高的一种并发锁——ReentrantLock。

从名字上来看,ReentrantLock具有两个特性,一个是可重入一个是锁

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;
    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }
    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);
        }
    }
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            acquire(1);
        }
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }
}

ReentrantLock内容定义了一个抽象类Sync,继承自AQS,而不是自己去继承AQS,所有对ReentrantLock的操作都会转化为对Sync的操作。同时又定义了Sync的两个子类FairSync和NonfairSync,分别用于实现公平锁和非公平锁。除非你在生成ReentrantLock时显示的指明需要公平锁,否则默认采用非公平锁。

可重入

先来看下可重入如何实现,以默认的非公平锁举例。可重入,意味着线程在获取锁之后,还可以再次获取锁,同样,获取了多少次,就要释放多少次,否则资源释放不对,别的线程将永远无法获得锁。

final void lock() {
    //1、CAS将state置为1
    if (compareAndSetState(0, 1))
        //2、设置自己为独占线程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //3、否则尝试获取资源
        acquire(1);
}
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //1、如果state为0,说明当前锁没有被占用
            if (c == 0) {
                //2、CAS尝试将state设为1
                if (compareAndSetState(0, acquires)) {
                    //3、获取锁成功,设置自己为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //4、如果持有当前锁的线程就是自己
            else if (current == getExclusiveOwnerThread()) {
                //5、那么将state增加acquires
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //6、更新state,因为锁已经被自己持有了,所以这里不需要CAS设置
                setState(nextc);
                return true;
            }
            return false;
}
//AQS
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

获取锁的过程中,4、5、6三步即实现了可重入获取。再看下释放。

protected final boolean tryRelease(int releases) {
            //1、每次释放,将state减去释放数
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //2、如果state为0,说明所有资源都已经释放
            if (c == 0) {
                free = true;
                //3、将独占线程置空
                setExclusiveOwnerThread(null);
            }
            //4、更新state
            setState(c);
            return free;
        }

同样,释放时需要将获取的资源依次扣除,什么时候state减为0了,才算该线程持有的所有资源都释放掉了。

公平锁实现可重入同理,不再赘述。

公平与非公平

那么公平锁与非公平锁又有什么区别呢?

非公平锁的获取上面已经分析了,来看下公平锁的获取,看下有什么不同。

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //1、等待队列中是否已经有节点在等待获取锁了
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //2、等待队列以后已经初始化,并且有别的线程正在入队(enq)或者已经入队
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

公平和非公平的区别就在于,尝试获取锁前要看下是否有线程已经在自己之前就开始等待了,如果没有才去竞争。通过这种方式保证公平,即先等先得。

公平锁和非公平锁哪种性能更好呢,公平锁虽然能保证等待最久的线程可以先获得锁,但是这同时也以为着每次都会是不同的线程获取锁,每次都要进行线程切换。《Java并发编程的艺术》进行了测试,表明非公平锁虽然可能造成某些线程一直获取不到锁,但是可以提高整体的吞吐量,所以ReentrantLock将其作为了默认实现。如果是需要保证这种先等先得的特性,也可以使用公平锁。

与synchronized对比

ReentrantLock在加锁和内存语义上提供了与synchronized相同的功能,此外还提供了定时、可中断、公平性等特性。

JDK5时,ReentrantLock的性能要远优于synchronized,但是JDK6引入了synchronized的锁优化,两者之间的差别并没有那么大了。

除非需要一些高级功能,如可定时、可轮询、可中断、公平性等,才使用ReentrantLock,否则应该优先使用synchronized,毕竟大部分人都用习惯了,而且使用简单。

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

相关文章

  • java实现微信公众号消息推送的方法详解

    java实现微信公众号消息推送的方法详解

    这篇文章主要为大家详细介绍了如何利用java实现微信公众号消息推送的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2023-10-10
  • 关于Java中finalize析构方法的作用详解

    关于Java中finalize析构方法的作用详解

    构造方法用于创建和初始化类对象,也就是说,构造方法负责”生出“一个类对象,并可以在对象出生时进行必要的操作,在这篇文章中会给大家简单介绍一下析构方法,需要的朋友可以参考下
    2023-05-05
  • 一文掌握IDEA中的Maven集成与创建

    一文掌握IDEA中的Maven集成与创建

    maven是用来帮助我们快速搭建项目结构与开发环境的好工具,这篇文章主要介绍了一文掌握IDEA中的Maven集成与创建,需要的朋友可以参考下
    2023-02-02
  • Java后台处理Json格式数据的方法

    Java后台处理Json格式数据的方法

    这篇文章主要介绍了Java后台处理Json格式数据的方法的相关资料,非常不错具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • 在Mac OS上安装Java以及配置环境变量的基本方法

    在Mac OS上安装Java以及配置环境变量的基本方法

    这篇文章主要介绍了在Mac OS上安装Java以及配置环境变量的基本方法,包括查看所安装Java版本的方法,需要的朋友可以参考下
    2015-10-10
  • 三分钟教你如何在IDEA中快速创建工程的方法

    三分钟教你如何在IDEA中快速创建工程的方法

    这篇文章主要介绍了三分钟教你如何在IDEA中快速创建工程的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-04-04
  • java输入时如何通过回车(enter)来结束输入

    java输入时如何通过回车(enter)来结束输入

    这篇文章主要介绍了java输入时如何通过回车(enter)来结束输入,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-05-05
  • 详解Spring如何解析占位符

    详解Spring如何解析占位符

    Spring一直支持将属性定义到外部的属性的文件中,并使用占占位符的形式为使用"${}"包装的属性名称,为了使用属性占位符,我们必须配置一个PropertyPlaceholderConfigurer或PropertySourcesPlaceholderConfigurer实例,本文将介绍如何解析占位符
    2021-06-06
  • springBoot集成flowable的流程解析

    springBoot集成flowable的流程解析

    这篇文章主要介绍了springBoot集成flowable的流程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-02-02
  • SpringBoot整合Dubbo框架,实现RPC服务远程调用

    SpringBoot整合Dubbo框架,实现RPC服务远程调用

    Dubbo是一款高性能、轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。今天就来看下SpringBoot整合Dubbo框架的步骤
    2021-06-06

最新评论