🍀 JVM已经帮我们内置了synchronized关键字来实现同步,为什么还要引入Lock呢?
首先需要明白synchronized是JVM层面的锁,Lock是API层面的锁,synchonized的灵活度是远不及Lock的;在JDK5时 Lock的效率是优于synchronized,在JDK6开始官方对synchronized进行了大量优化,包括锁升级、锁消除、锁粗化等,事实证明在锁竞争激烈的场景,ReentrantLock还是优于synchronized,但是synchronized还有增长空间,官方也推荐关注synchronized,怎么感觉有种亲儿子的待遇。
认识ReentrantLock的具体实现前,我们应该了解一下ReentrantLock的设计意图是什么?为什么要这样设计?而不是只去看一下具体怎么实现的,然后应付一下面试之类的;我们看别人的代码之前应该搞清楚一个事实,我是奔着提高能力去的,想一想它为什么这样设计?心中多一点思考!看一下Doug Lea大神写的并发代码,我们怎么说也会进步的吧~,或许你会说基础太薄弱,一下子无法理解透;理解一下即可,未来某一刻遇到了类似的设计理念,你会深有同感的。
话不多说,进入今天的正题;今天的核心:ReentrantLock只是指挥的,具体在它的内部类。
🍀其中的Lock接口不用多说,这是API层面的锁的统一规范,后续你必须进行遵守,定义一些加锁、解锁、尝试获取锁的接口,因为在API层面,无论你使用什么锁,肯定少不了这几个方法的。
🍀然后ReentranLock内部定义了Sync抽象类,通过组合的方式持有该类
内部的FairSync和NonFairSync通过继承Sync来实现公平锁和非公平锁,扩展性得到提高,耦合度降低;此时你会发现ReentrantLock就像一个"老板"一样,指挥FairSync和NonFairSync这两个主管去做两个任务,而具体的怎么做的一概不管,但是必须给我完成。而FairSync和NonFairSync这两个小主管也会偷懒,把它们相同的部分交给了AbstractQueuedSynchronizer这个底层员,emmm~果然是层层压榨!
首先思考一下ReentrantLock怎么使用的?
官方注释中也定义了一般情况下应该怎么使用ReentrantLock
在try代码块中执行临界区代码,在finally类进行释放锁,保证即便执行临界区代码报错,也会进行锁的释放。
❓ 接下来分析一下ReentrantLock内部有什么东东?(Alt + 7)
📑其中最为突出有3个内部类,首先Sync抽象类,该类继承了AbstractQueuedSynchronizer抽象同步队列(AQS)
AQS可以说是很多同步锁实现的核心,比如说ReentrantLock、Semaphore、CountDownLatch等等,这些同步锁都是基于AQS来实现的(定义一个内部类继承了AQS),所以需要搞清楚AQS这个东西。
AQS即"抽象同步队列",它有5个核心要素:同步状态、等待队列、独占模式、共享模式和条件队列。
✨ (1)同步状态
顾名思义,同步状态就是用来实现锁机制的。如何实现?AQS抽象类中有一个属性state
然后看我们调用lock方法进行加锁的底层实现,首先是调用了ReentrantLock内部类Sync的尝试获取锁方法,看下图
参数传入了一个1,这个如何理解呢?也就是线程CAS尝试将0修改为1,如果修改成功,那么它就是成功抢到锁(这里我说的没包含锁重入的情况),然后我以非公平锁为例,简单分析获取锁源码实现。
需要注意一点就是那个state状态值,为什么会大于1呢?,这时因为ReentrantLock是支持可重入的,一旦该值>1,说明某个线程多次获取了同一把锁(业务复杂时可能会用到)。
✨ (2)等待队列
顾名思义,等待队列就是用来存放等待锁的线程,在AQS抽象类中有一个内部类Node,它是实现双向队列的核心
AQS同步器将线程封装到了Node里面,维护了一个CHL Node FIFO队列,这是一个非阻塞的FIFO队列,意味着在并发条件下向此队列进行插入和删除时不会发生阻塞。它通过自旋+CAS来保证节点插入和移除的原子性。看下面的AQS中的入队代码
✨ (3)独占模式
顾名思义,用来实现独占锁的,AQS的内部类Node定义的EXCLUSIVE 就是用来标识独占模式的。
✨ (4)共享模式
用来实现共享锁的,AQS的内部类Node定义的SHARED就是用来标识共享模式的。
到这里AQS就介绍的差不多了,该专注于公平锁和非公平锁了。
✨首先公平锁和非公平锁的加锁和解锁都是在AQS中实现的,这两种锁都继承了Sync抽象类,查看该抽象类的具体实现。
你会发现它居然在内部了非公锁nonfairTryAcquire获取锁操作,它是因为偏心吗?既然是不公平锁那就不公平一点?😂😂,其实不是的,因为公平锁也会用到这个方法,所以将它抽取到了Sync类中;tryRelease方就不用多说了,用户释放锁,两种锁的释放是一致的。
❓为什么称之为非公平锁,它的不公平体现在哪里?
竞争锁时会有两方面的势力,被唤醒的CLH队列中的线程和非CLH队列中的线程,它们会同时竞争锁;不会因为你来的早我就把锁让给你,一句话:各凭本事!
具体体现在下面代码中:
锁释放时调用了release方法(上面讲过这个方法),方法内部调用了unparkSuccessor唤醒后继节点方法。此时如果来了多个线程调用lock方法想要获取锁
最终会调用到nonfairTryAcquire尝试获取锁方法
所以说非公平就体现在这,但是这样的性能是比较好的,因为可能直接省略了唤醒线程这一步骤。
❓获取锁的流程是怎么样的:
❓为什么称之为公平锁,它的公平体现在哪里?
其实FairSync和NonfairSync的获取锁代码基本上一致,只不过NonfairSync比FairSync多了一步,需要判断当前线程是否是在CLH队列中被唤醒的。
❓ ReentrantLock默认使用的是公平锁还是非公平锁?
是非公平锁,性能优于公平锁,前面解释过。
也可以传入true值,就会使用公平锁。