文章目录
独占锁ReentrantLock原理解析
一、类图结构
Reentrantlock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列中。
从类图中可以看到,ReentrantLock最终还是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。
其中Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。
在这里,AQS的state状态值表示线程获取该锁的可重入次数,在默认情况下,state的值为0表示当前锁没有被任何线程持有。当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置为2,这就是可重入次数。在该线程释放该锁时,会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放了锁。
二、获取锁
1、void lock()方法
当一个线程调用该方法时,说明该线程希望获取锁,如果锁当前没有被其他线程持有并且当前线程之前没有获取过该锁,则当前线程就会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置AQS的状态值为1,然后直接返回。如果当前线程之前已经获取过该锁,则这次只是简单地把AQS的状态值 + 1后返回。如果该锁被其他线程持有,则调用该方法的线程会被加入到AQS队列后阻塞挂起。
如上代码中,ReentrantLock的lock方法委托给了sync类,根据创建ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync,这个锁是一个非公平或者公平锁。这里先看sync的子类NonfairSync,也就是非公平锁。
在代码(1)中,因为默认AQS的状态值为0,所以第一个调用Lock的线程会通过CAS设置状态值为1,CAS成功则表示当前线程获取到了锁,然后setExclusiveOwnerThread设置该锁持有者是当前线程。
如果这时候有其他线程调用lock方法企图获取锁,CAS会失败,然后会调用AQS的acquire方法。注意,传递参数为1,这里贴下AQS的acquire核心代码。
之前说过,AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自己定制化,所以这里代码(3)会调用ReentrantLock重写的tryAcquire方法。我们先看下非公平锁的实现代码。
首先代码(4)会查看当前state是否为0,为0则说明当前该锁空闲,那么就尝试CAS获取该锁,将AQS的状态值从0设置为1,并设置当前锁的持有者为当前线程然后返回true。如果当前状态不为0,则说明锁已经被其他线程持有,所以代码(5)就判断当前线程是否是该锁的持有者,如果当前线程是该锁的持有者,则状态加1,然后返回true,这里需要注意,nextc < 0说明可重入次数溢出了。如果当前线程不是锁的持有者则返回false,然后其会被加入AQS阻塞队列。
非公平的体现在于,多个线程去请求锁,不是先去请求锁的线程就能拿到锁,它是看谁能在锁的状态为0的时候能拿到锁,谁就能持有锁
下面看看公平锁的实现代码
如上代码所示,公平的tryAcquire策略与非公平类似,不同之处在于,代码(8)在设置CAS前添加了hasQueuedPredecessors方法,该方法是实现公平性的核心代码,代码如下:
如上代码中,如果当前线程节点有前驱节点则返回true,否则如果当前AQS队列为空或者当前线程节点是AQS的第一个节点则返回false。其中如果h == t则说明当前队列为空,直接返回false;如果h != t并且s = null则说明有一个元素将要作为AQS的第一个节点入队列(回顾前面的内容,enq函数的第一个元素入队列是两步操作,首先创建一个哨兵头节点,然后将第一个元素插入哨兵节点后面),那么返回true,如果h != t 并且s != null 和s.thread != Thread.currentThread则说明队列里面的第一个元素不是当前线程,那么返回true。
2、void lockInterruptibly()方法
该方法与lock方法类似,它的不同在于,他对中断进行响应,就是当前线程在调用该方法时,如果其他线程调用了当前线程的interrupt()方法,则当前线程会抛出InterruptedException异常,然后返回。
3、boolean tryLock()方法
尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁并返回true,否则返回false。注意,该方法不会引起当前线程阻塞。
4、boolean tryLock(long timeout, TimeUnit unit)方法
尝试获取锁,与tryLock方法不同在于,他设置了超时时间,如果超时时间到没有获取到该锁则返回fasle。
三、释放锁
1、void unlock()方法
尝试释放锁,如果当前线程持有该锁,则调用该方法会让该线程对该线程持有的AQS状态值减1,如果减去1后当前状态值为0,则当前线程会释放该锁,否则仅仅减1而已。如果当前线程没有持有该锁,而调用了该方法则会抛出IllegalMonitorStateException异常,代码如下:
如代码(11)所示,如果当前线程不是该锁的持有者则直接抛出异常,否则查看状态是否为0,为0则说明当前线程要放弃对该锁的持有权,则执行代码(12)把当前锁持有者设置为nll。如果状态值不为0,则仅仅让当前线程对该锁的可重入次数减1。
四、小结
本节介绍了ReentrantLock的实现原理,ReentrantLock的底层是使用AQS实现的可重入独占锁,在这里AQS的状态值为0表示当前锁空闲,为大于等于1的值说明该锁已经被占用。该锁内部有公平和非公平实现,默认情况是非公平实现,另外,由于该锁是独占锁,所以某时只有一个线程可以获取该锁。