java8 ReentrantLock加解锁原理详解

在文章《java8 多线程并发之AQS详解》里面介绍了AQS的实现原理,ReentrantLock便是基于AQS实现的可重入锁。本文将详细介绍ReentrantLock的实现原理。

一、ReentrantLock

ReentrantLock实现了接口Lock,先来看一下Lock的定义:

public interface Lock {
	//申请锁,如果没有申请到,则阻塞当前线程
	//屏蔽线程中断
    void lock();
	//与lock()方法作用类似,如果发生了线程中断,该线程会抛出InterruptedException异常
    void lockInterruptibly() throws InterruptedException;
	//尝试加锁,如果加锁成功,则返回true,失败返回false
	//无论加锁成功还是失败,该方法都会立即返回
    boolean tryLock();
	//尝试加锁,如果加锁成功,立即返回true,
	//如果失败则会阻塞当前线程time时间,如果在time期间内申请到了,则返回true,如果没有申请到,返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	//解锁
	void unlock();
	//返回与锁相关的Condition对象
    Condition newCondition();
}

ReentrantLock还有一个特性是公平锁和非公平锁。默认情况下,ReentrantLock是非公平锁,可以将构造方法的入参设置为true,这样ReentrantLock就变成了公平锁。

	public ReentrantLock(boolean fair){}

公平锁与非公平锁的区别:
使用公平锁时,线程获取锁的顺序是按照线程申请锁的顺序来分配的,即先来先得,而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,它允许插队:当一个线程请求非公平锁时,如果在发出请求的同时该锁变成可用状态,那么这个线程会跳过队列中所有的等待线程而获得锁。 非公平的ReentrantLock并不提倡插队行为,但是无法防止某个线程在合适的时候进行插队。
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中。
相对来说,非公平锁的性能要高于公平锁。
非公平锁性能高于公平锁性能的原因:
线程进入RUNNABLE状态后,可以开始执行,但是到实际线程执行是要比较久的时间。而且,在一个锁释放之后,其他的线程会需要重新来获取锁。其中经历了持有锁的线程释放锁,其他线程从挂起恢复到RUNNABLE状态,其他线程请求锁,获得锁,线程执行,这一系列步骤。如果这个时候,存在一个线程直接请求锁,可能就避开挂起到恢复RUNNABLE状态的这段消耗,所以性能更优。
假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。
当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升可能不会出现。

二、源码分析

首先看一下ReentrantLock的构造方法:

    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

从构造方法中可以看到,公平锁与非公平锁是由FairSync和NonfairSync两个类来实现的。而且ReentrantLock中的大部分方法都是直接调用FairSync和NonfairSync类中方法,比如lock()方法:

    public void lock() {
        sync.lock();
    }

因此下面主要分析类FairSync和NonfairSync。
在介绍FairSync和NonfairSync之前,先介绍它们的基类Sync 。

1、Sync

Sync是ReentrantLock的内部抽象类,它继承自AQS(AbstractQueuedSynchronizer )。在介绍Sync源码之前,先介绍一下AQS中的属性state。在ReentrantLock中该属性表示是否有线程占用了锁。属性state的定义如下:

    private volatile int state;

这个属性非常重要。如果state=0表示当前没有线程占用锁,其他线程可以申请锁,如果state>0,表示有线程已经占用了锁,一个线程可以多次占用锁,每占用一次,state就加1,state的值表示占用锁的个数;释放锁时,每释放一次,state响应的减1,等state=0了表示锁释放完毕。因为该属性的重要性,所以在代码中使用CAS更新该属性。可以调用AQS中的getState()方法获得该属性值。
下面看一下Sync的源码:

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
		//申请加锁,子类必须实现
        abstract void lock();
		//申请非公平锁,入参acquires表示申请锁的个数,一般是1
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //getState()是基类中的方法,getState()可以返回基类中state属性的值,
            //如果返回0,表示当前没有线程持有锁,
            //如果返回大于0,表示锁已经被线程占用了
            int c = getState();
            if (c == 0) {
            	//使用CAS更新基类的属性state,如果state不是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");
                //使用CAS更新基类中的属性state
                setState(nextc);
                return true;
            }
            return false;
        }
		//释放锁,入参releases一般是1,由AQS中的release()方法调用
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            //当前释放锁的线程与持有锁的线程不是同一个,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //c=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、FairSync

FairSync表示公平锁,继承自Sync,祖先类是AQS。

	//代码有删减
    static final class FairSync extends Sync {
    	//申请锁,里面调用了AQS中的acquire()方法
    	//acquire()里面首先调用本类中的tryAcquire(),如果没有加锁成功,
    	//则将当前线程放入队列中,等待下次申请锁
        final void lock() {
            acquire(1);
        }

        //公平锁与非公平锁之间的区别就体现在tryAcquire()方法上,每次加锁都要调用该方法
        //该方法首先检查锁是否被占用了,如果是当前线程占用的,这次是再次加锁,那么允许加锁,该方法返回true;
        //如果没有线程占用锁,那么检查队列中是否有线程等待锁,如果有则返回false,没有则加锁。
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
           	//调用父类方法获得属性state的值
            int c = getState();
            //c=0表示当前没有线程占用锁
            if (c == 0) {
            	//hasQueuedPredecessors()方法用于检查队列中是否有线程等待锁
                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;
        }
    }

当ReentrantLock是公平锁时,每次调用ReentrantLock.lock()申请锁时最终都会通过AQS调用到tryAcquire()方法尝试加锁,在tryAcquire()方法里面,首先检查是否有线程已经加锁了:

  • 如果没有,则检查队列中是否有等待申请锁的线程(AQS将申请锁失败的线程放到一个FIFO队列中),如果有这样的线程,则tryAcquire()返回false,如果没有,则尝试加锁;
  • 如果有线程已经加过锁了,那么检查持有锁的线程是否是当前线程,如果是则允许重入,修改state值。

在AQS里面,根据tryAcquire()方法的返回值有不同的处理,如果返回true,表示加锁成功,AQS里面不会再有其他处理,如果返回false,那么将当前线程放入队列尾,并阻塞当前线程。只有当队列中前面所有的线程都执行结束了,当前线程才会被唤醒并申请锁。

从tryAcquire()方法可以知道,公平锁是严格按照先进先出的原则申请锁的。

3、NonfairSync

NonfairSync表示非公平锁,也是继承自Sync。

    static final class NonfairSync extends Sync {
        final void lock() {
        	//与FairSync不同,直接先申请锁,如果申请失败了,才会调用AQS的acquire()
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
		//调用父类的方法加锁
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

看完FairSync,NonfairSync就很好理解了。与FairSync不同,申请锁之前,不会调用hasQueuedPredecessors()判断队列中是否有其他线程等待锁,而是直接尝试加锁,只有加锁失败了,才会将当前线程放入队列中。之后的处理过程,与FairSync类似。
注意:
ReentrantLock里面不带参数的tryLock()方法虽说也是加锁方法,但是无论是公平锁还是非公平锁,使用tryLock()加锁时,都不会检查队列是否是空,tryLock()里面直接调用了Sync类的nonfairTryAcquire()方法加锁:

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

参考文章

https://blog.csdn.net/zhang199416/article/details/70792587

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值