美文网首页程序员
java锁(5)可重入锁ReentrantLock实现详解

java锁(5)可重入锁ReentrantLock实现详解

作者: 桥头放牛娃 | 来源:发表于2019-07-18 23:29 被阅读9次

    1、ReentrantLock的特性

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁。ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个Conditon。

    可重入性:

    是指可以支持一个线程对锁的重复获取与释放。原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞,并且lock和unlock的调用次数必须相等才会释放锁。

    公平锁/非公平锁:

    公平锁:是指线程获取锁的顺序和调用lock的顺序一样,FIFO(先进先出)方式获取和释放锁。

    非公平锁:线程获取锁的顺序和调用lock的顺序无关,能否获取锁取决于调用时机。

    synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。

    2、ReentrantLock实现

    ReentrantLock的所有锁相关操作都是通过Sync类实现,Sync继承于AbstractQueuedSynchronizer同步队列,并实现一些通用的接口实现。

    NonfairSync继承于Sync,实现了非公平的方式获取锁;

    FairSync继承于Sync,实现了公平的方式获取锁;

    ReentrantLock类实现结构如下:

    ReentrantLock类实现结构.png

    2.1、Sync实现

    //sync继承于AQS
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
    
        //抽象方法,需子类实现
        abstract void lock();
    
        //实现了非公平方式获取锁
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取当前锁状态,此状态c>0表示有线程获取到锁,重入的次数为c
            int c = getState();
            //无线程获取锁?
            if (c == 0) {
                //CAS方式获取锁,成功则设置获取独占锁的线程
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //获得锁的线程就是当前线程?则获取次数加1,并设置状态(即次数)
            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;
        }
    
        //释放锁
        protected final boolean tryRelease(int releases) {
            //计算需要更新的状态值
            int c = getState() - releases;
            //若当前线程未获得锁,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //若将要更新的状态值为0,表示当前线程最后一次释放锁;
            //此时锁才会真正的释放,其他线程才能获取;
            //否则表示当前线程获取锁的次数大于释放锁的次数
            if (c == 0) {
                free = true;
                //清除记录的独占锁的线程
                setExclusiveOwnerThread(null);
            }
            //更新状态
            setState(c);
            return free;
        }
    
        //判断当前线程释放获取独占锁
        protected final boolean isHeldExclusively() {
           //当前线程为记录的获取独占锁的线程,则表示当前线程获得独占锁
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    
        //创建新的条件队列
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    
        
        //获取获取独占锁的线程对象
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
    
        //获取当前线程获取锁的次数
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        
        //判断锁是否被占用
        final boolean isLocked() {
            return getState() != 0;
        }
    }
    

    2.2、非公平锁NonfairSync的实现

    //非公平锁NonfairSync的实现,其继承于Sync
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    
        //lock接口实现;
        //首先通过CAS试图获取锁,获取成功则设置锁的Owner;
        //否则调用acquire获取锁,acquire又或调用tryAcquire获取锁,
        //而tryAcquire是通过非公平的方式获取锁。
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        //非公平方式获取锁
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    

    2.3、公平锁FairSync的实现

    //公平锁FairSync 的实现,其继承于Sync
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
    
        //lock接口实现,自己调用acquire获取锁;
        //acquire又会调用tryAcquire获取锁,而tryAcquire是通过公平(FIFO)
        //的方式获取锁。
        final void lock() {
            acquire(1);
        }
    
        //公平的方式获取锁
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取当前锁状态,此状态c>0表示有线程获取到锁,重入的次数为c
            int c = getState();
            //无线程获取锁?
            if (c == 0) {
                //当前节点无前驱节点并且当前线程CAS更新状态成功;、
                //表示当前线程公平的获取到锁
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //获得锁的线程就是当前线程?则获取次数加1,并设置状态(即次数)
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    

    2.4、锁可重入的实现原理

    通过以上源码分析可知,当某个节点获取到锁时,会通过setExclusiveOwnerThread()方法记录获取独占锁的线程Thread;当某个线程获取锁时,当锁已被占用,会判断占用锁的线程是否为当前线程;是则直接更新锁状态,表示获取到锁;否则获取锁失败。

    2.5、锁公平与非公平的实现原理

    公平锁是通过FairSync实现的,其在tryAcquire获取锁时,会判断同步队列中当前节点是否有前驱节点;有前驱节点,则获取锁失败,进入同步队列,等待获取锁;无前驱节点时,表示当前节点是同步队列中等待锁时间最长的节点,则当前节点优先获取锁资源。

    非公平锁是通过NonfairSync实现的,其在lock及tryAcquire时,会先通过CAS的方式尝试获取锁,获取失败才会进入同步队列等待。这就导致当某个线程刚释放锁,而同步队列中被unpark的头节点还未CAS获取到锁的时间间隙,当前线程先于同步队列头结点通过CAS获取锁。使得某些线程会等待很长时间才会获得锁,这是非公平性的。

    ReentrantLock的构造方法如下:

    //无参构造方法,会通过非公平的方式实现锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    
    //带参数的构造方法;
    //fire=true:会通过公平的方式构造锁;
    //fire=false:会通过非公平的方式构造锁;
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    

    相关文章

      网友评论

        本文标题:java锁(5)可重入锁ReentrantLock实现详解

        本文链接:https://www.haomeiwen.com/subject/gkldlctx.html