但是对于非公平锁 , 管理员对打水的人没有要求 。即使等待队伍里有排队等待的人 , 但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时 , 刚好来了一个插队的人 , 这个插队的人是可以直接从管理员那里拿到锁去打水 , 不需要排队 , 原本排队等待的人只能继续等待 。如下图所示:
文章插图
接下来我们通过ReentrantLock的源码来讲解公平锁和非公平锁 。
文章插图
根据代码可知 , ReentrantLock里面有一个内部类Sync , Sync继承AQS(AbstractQueuedSynchronizer) , 添加锁和释放锁的大部分操作实际上都是在Sync中实现的 。它有公平锁FairSync和非公平锁NonfairSync两个子类 。ReentrantLock默认使用非公平锁 , 也可以通过构造器来显示的指定使用公平锁 。
下面我们来看一下公平锁与非公平锁的加锁方法的源码:
文章插图
通过上图中的源代码对比 , 我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors() 。
文章插图
再进入hasQueuedPredecessors() , 可以看到该方法主要做一件事情:主要是判断当前线程是否位于同步队列中的第一个 。如果是则返回true , 否则返回false 。
综上 , 公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁 , 从而实现公平的特性 。非公平锁加锁时不考虑排队等待问题 , 直接尝试获取锁 , 所以存在后申请却先获得锁的情况 。
5. 可重入锁 VS 非可重入锁可重入锁又名递归锁 , 是指在同一个线程在外层方法获取锁的时候 , 再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class) , 不会因为之前已经获取过还没释放而阻塞 。Java中ReentrantLock和synchronized都是可重入锁 , 可重入锁的一个优点是可一定程度避免死锁 。下面用示例代码来进行分析:
文章插图
在上面的代码中 , 类中的两个方法都是被内置锁synchronized修饰的 , doSomething()方法中调用doOthers()方法 。因为内置锁是可重入的 , 所以同一个线程在调用doOthers()时可以直接获得当前对象的锁 , 进入doOthers()进行操作 。
如果是一个不可重入锁 , 那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉 , 实际上该对象锁已被当前线程所持有 , 且无法释放 。所以此时会出现死锁 。
而为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?我们通过图示和源码来分别解析一下 。
还是打水的例子 , 有多个人在排队打水 , 此时管理员允许锁和同一个人的多个水桶绑定 。这个人用多个水桶打水时 , 第一个水桶和锁绑定并打完水之后 , 第二个水桶也可以直接和锁绑定并开始打水 , 所有的水桶都打完水之后打水人才会将锁还给管理员 。这个人的所有打水流程都能够成功执行 , 后续等待的人也能够打到水 。这就是可重入锁 。
文章插图
但如果是非可重入锁的话 , 此时管理员只允许锁和同一个人的一个水桶绑定 。第一个水桶和锁绑定打完水之后并不会释放锁 , 导致第二个水桶不能和锁绑定也无法打水 。当前线程出现死锁 , 整个等待队列中的所有线程都无法被唤醒 。
文章插图
之前我们说过ReentrantLock和synchronized都是重入锁 , 那么我们通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁 。
首先ReentrantLock和NonReentrantLock都继承父类AQS , 其父类AQS中维护了一个同步状态status来计数重入次数 , status初始值为0 。
推荐阅读
- Java虚拟机:Jvm概念和原理详解以及GC机制的分析
- Java|聊聊写简历的那些坑,为什么你投出去的简历总是石沉大海!
- 换一个卧室门锁大概多少钱,卧室门锁芯怎么换
- java 泛型详解
- WiFi系统的无线AP与AC之间的各种问题解析
- 吴裕泰连锁茶餐厅,品味精致茶文化菜肴
- 春吃芽,各种芽的功效大不同,你知道这其中的奥秘吗?
- 茶是毒素解药 不同人群喝不同的茶
- 电子驻车很多车上都有,带你解锁它的隐藏功能,别到卖车还不知道
- 肉苁蓉锁阳泡茶的方法是什么