J.U.C剖析与解读1(Lock的实现) 前言
为了节省各位的时间,我简单介绍一下这篇文章。这篇文章主要分为三块:Lock的实现,AQS的由来(通过演变的方式),JUC三大工具类的使用与原理剖析。
Lock的实现:简单介绍ReentrantLock,ReentrantReadWriteLock两种JUC下经典Lock的实现,并通过手写简化版的ReentrantLock和ReentrantReadWriteLock,从而了解其实现原理。
AQS的由来:通过对两个简化版Lock的多次迭代,从而获得AQS。并且最终的Lock实现了J.U.C下Lock接口,既可以使用我们演变出来的AQS,也可以对接JUC下的AQS。这样一方面可以帮助大家理解AQS,另一方面大家可以从中了解,如何利用AQS实现自定义Lock。而这儿,对后续JUC下的三大Lock工具的理解有非常大的帮助。
JUC三大工具:经过前两个部分的学习,这个部分不要太easy。可以很容易地理解CountDownLatch,Semaphore,CyclicBarrier的内部运行及实现原理。
不过,由于这三块内容较多,所以我将它拆分为三篇子文章进行论述。
一,介绍 LockLock接口位于J.U.C下locks包内,其定义了Lock应该具备的方法。
Lock 方法签名:
void lock():获取锁(不死不休,拿不到就一直等)
boolean tryLock():获取锁(浅尝辄止,拿不到就算了)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException:获取锁(过时不候,在一定时间内拿不到锁,就算了)
void lockInterruptibly() throws InterruptedException:获取锁(任人摆布,xxx)
void unlock():释放锁
Condition newCondition():获得Condition对象
ReentrantLock 简介ReentrantLock是一个可重入锁,一个悲观锁,默认是非公平锁(但是可以通过Constructor设置为公平锁)。
Lock应用ReentrantLock通过构造方法获得lock对象。利用lock.lock()方法对当前线程进行加锁操作,利用lock.unlock()方法对当前线程进行释放锁操作。
Condition应用通过
ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition();获得Condition对象(Condition是J.U.C下locks包下的接口)。
通过Condition对象的.await(*),可以将当前线程的线程状态切换到Waiting状态(如果是有参,则是Time Waiting状态)。而.signal(),.signalAll()等方法则正好相反,恢复线程状态为Runnable状态。
ReentrantReadWriteLock 简介ReentrantLock和Synchronized功能类似,更加灵活,当然,也更加手动了。
大家都知道,只有涉及资源的竞争时,采用同步的必要。写操作自然属于资源的竞争,但是读操作并不属于资源的竞争行为。简单说,就是写操作最多只能一个线程(因为写操作涉及数据改变,多个线程同时写,会产生资源同步问题),而读操作可以有多个(因为不涉及数据改变)。
所以在读多写少的场景下,ReentrantLock就比较浪费资源了。这就需要一种能够区分读写操作的锁,那就是ReentrantReadWriteLock。通过ReentrantReadWriteLock,可以获得读锁与写锁。当写锁存在时,有且只能有一个线程持有锁。当写锁不存在时,可以有多个线程持有读锁(写锁,必须等待读锁释放完,才可以持有锁)。
Lock及Condition应用 ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); readLock.lock(); readLock.unlock(); readLock.newCondition(); ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); writeLock.lock(); writeLock.unlock(); writeLock.newCondition();与之前ReentrantLock应用的区别,就是需要通过lock.readLock()与lock.writeLock()来获取读锁,写锁,再进行加锁,释放锁的操作,以及Condition的获取操作。
二,手写ReentrantLock 获取需求终于上大餐了。
首先第一步操作,我们需要确定我们要做什么。
我们要做一个锁,这里姑且命名为JarryReentrantLock。
这个锁,需要具备以下特性:可重入锁,悲观锁。
另外,为了更加规范,以后更好地融入到AQS中,该锁需要实现Lock接口。
而Lock的方法签名,在文章一开始,就已经写了,这里不再赘述。
当然,我们这里只是一个demo,所以就不实现Condition了。另外tryLock(long,TimeUnit)也不再实现,因为实现了整体后,这个实现其实并没有想象中那么困难。
JarryReentrantLock实现原理既然需要已经确定,并且API也确定了。
那么第二步操作,就是简单思考一下,如何实现。
类成员方面:首先,我们需要一个owner属性,来保存持有锁的线程对象。
其次,由于是可重入锁,所以我们需要一个count来保存重入次数。
最后,我们需要一个waiters属性,来保存那些竞争锁失败后,还在等待(不死不休型)的线程对象。
类方法方面: