锁专题(1)java 常见锁介绍,高级程序员必知必会( 三 )


public ReentrantLock() {sync = new NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}例子直接用语言描述可能有点抽象 , 这里作者用从别处看到的一个例子来讲述一下公平锁和非公平锁 。
锁专题(1)java 常见锁介绍,高级程序员必知必会文章插图
输入图片说明
如上图所示 , 假设有一口水井 , 有管理员看守 , 管理员有一把锁 , 只有拿到锁的人才能够打水 , 打完水要把锁还给管理员 。 每个过来打水的人都要管理员的允许并拿到锁之后才能去打水 , 如果前面有人正在打水 , 那么这个想要打水的人就必须排队 。 管理员会查看下一个要去打水的人是不是队伍里排最前面的人 , 如果是的话 , 才会给你锁让你去打水;如果你不是排第一的人 , 就必须去队尾排队 , 这就是公平锁 。
但是对于非公平锁 , 管理员对打水的人没有要求 。 即使等待队伍里有排队等待的人 , 但如果在上一个人刚打完水把锁还给管理员而且管理员还没有允许等待队伍里下一个人去打水时 , 刚好来了一个插队的人 , 这个插队的人是可以直接从管理员那里拿到锁去打水 , 不需要排队 , 原本排队等待的人只能继续等待 。
如下图所示:
锁专题(1)java 常见锁介绍,高级程序员必知必会文章插图
输入图片说明
参考 ReentrantLock 可重入锁
可重入锁、不可重入锁可重入锁可重入锁:即某个线程获得了锁之后 , 在锁释放前 , 它可以多次重新获取该锁 。
可重入锁解决了重入死锁的问题 。
java 的内置锁 synchronized 和 ReentrantLock 都是可重入锁
不可重入锁不可重入锁(自旋锁):不可以再次进入方法A , 也就是说获得锁进入方法A是此线程在释放锁钱唯一的一次进入方法A 。
例子public class SyncTest {public synchronized void syncOne() {System.out.println("方法1执行");syncTwo();}public synchronized void syncTwo() {System.out.println("方法2执行");}}在上面的代码中 , 类中的两个方法都是被内置锁synchronized修饰的 , syncOne()方法中调用syncTwo()方法 。 因为内置锁是可重入的 , 所以同一个线程在调用syncTwo()时可以直接获得当前对象的锁 , 进入 syncTwo() 进行操作 。
如果是一个不可重入锁 , 那么当前线程在调用 syncTwo() 之前需要将执行syncOne()时获取当前对象的锁释放掉 , 实际上该对象锁已被当前线程所持有 , 且无法释放 。 所以此时会出现死锁 。
而为什么可重入锁就可以在嵌套调用时可以自动获得锁呢?
我们通过图示和源码来分别解析一下 。
还是打水的例子 , 有多个人在排队打水 , 此时管理员允许锁和同一个人的多个水桶绑定 。 这个人用多个水桶打水时 , 第一个水桶和锁绑定并打完水之后 , 第二个水桶也可以直接和锁绑定并开始打水 , 所有的水桶都打完水之后打水人才会将锁还给管理员 。 这个人的所有打水流程都能够成功执行 , 后续等待的人也能够打到水 。
这就是可重入锁 。
锁专题(1)java 常见锁介绍,高级程序员必知必会文章插图
输入图片说明
但如果是非可重入锁的话 , 此时管理员只允许锁和同一个人的一个水桶绑定 。
第一个水桶和锁绑定打完水之后并不会释放锁 , 导致第二个水桶不能和锁绑定也无法打水 。 当前线程出现死锁 , 整个等待队列中的所有线程都无法被唤醒 。
锁专题(1)java 常见锁介绍,高级程序员必知必会文章插图
输入图片说明
之前我们说过ReentrantLock和synchronized都是重入锁 , 那么我们通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁 。


推荐阅读