共计 2808 个字符,预计需要花费 8 分钟才能阅读完成。
1. 偏心锁和非偏心锁
1.1 基本概念
- 偏心锁:线程依照到来的先后顺序,排队期待应用资源。
- 非偏心锁:线程不肯定依照先后顺序应用资源,而是可能呈现“插队”的状况。
拿游乐场期待娱乐我的项目举例,一般游客只能依照先后顺序排队期待应用游乐设施,这就是 偏心锁
,然而一般入口加上优速通,显然 VIP 游客能够快人一步,这就有点 非偏心锁
的意思了。
1.2 ReentrantLock 的偏心锁和非偏心锁
在《【后端面经 -Java】Synchronize 和 ReentrantLock 区别》这篇博客中,咱们比照过 synchronized
和ReentrantLock
的区别,其中 synchronized
是一种 非偏心锁
,而ReentrantLock
默认是 非偏心锁
,然而也可设置为 偏心锁
。
具体设置形式如下:
// 生成一个偏心锁
static Lock lock = new ReentrantLock(true);
// 生成一个非偏心锁
static Lock lock = new ReentrantLock(false);
static Lock lock = new ReentrantLock();// 默认参数就是 false,这种写法也可
通过更改构造函数中的参数,咱们能够批改 ReentrantLock
的锁类型,true
示意偏心锁,false
示意非偏心锁。构造函数具体代码如下所示:
public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();//FairSync 示意偏心锁,NonfairSync 示意非偏心锁}
2. 加锁流程
2.1 ReentrantLock 和 AQS 的关系
在【后端面经 -Java】AQS 详解这篇博客中,咱们具体解说了 AQS
的原理,其中提到了
AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如罕用的 ReentrantLock。
可就是说,ReentrantLock
也是通过 AQS
来实现的,而自定义同步锁须要实现 AQS
框架中的 tryAcquire()
和tryRelease()
办法或者 tryAcquireShared()
和tryReleaseShared()
办法。
因而,ReentrantLock
的加锁流程咱们可用查看 tryAcquire()
办法理解。
2.2 偏心锁 - 加锁流程
偏心锁的 tryAcquire()
办法源码如下所示:
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {if (!hasQueuedPredecessors() && compareAndSetState(0,acquires)) {// 这里判断了队列中是不是还有其余线程在期待 && 以后资源是否可用?// 间接获取资源
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {// 如果有其余线程在期待或者资源不可用,线程进入期待态,排队期待
int nextc = c + acquires;
if (nextc < 0) {throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
代码流程如下所示:
- 查看是否有其余线程在期待资源。
- 如果没有其余线程在期待,查看资源是否可用,如果资源可用,间接获取资源。
- 如果有其余线程在期待或者资源不可用(正在被应用),线程乖乖排到队尾,并切换为期待唤醒的休眠态。
2.3 非偏心锁 - 加锁流程
非偏心锁的 tryAcquire()
办法源码如下所示:
final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {if (compareAndSetState(0, acquires)) { // 这里只判断了资源是否可用,而没有判断是否有其余线程在期待
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
和 偏心锁
相比,非偏心锁
的加锁流程只是少了对 其余线程是否期待 的判断,因而,非偏心锁
的加锁流程如下所示:
- 查看资源是否可用,如果资源可用,间接获取资源。
- 如果资源不可用,不须要管是否有线程在排队,还是排在期待队列队尾。
2.4 加锁流程和性能的关系
偏心锁能保障线程获取资源的公平性,然而性能较低;
而非偏心锁尽管无奈保障公平性,然而性能更高,因而在大多数状况下,咱们都会应用非偏心锁。
-
对于“偏心锁性能低,非偏心锁性能高”的解释
了解这个论断,咱们须要晓得偏心锁和非偏心锁申请资源的流程。- 对于偏心锁,当一个线程创立之后,它会看是否有其余线程在期待资源,也就是看看
排队队伍外面有没有人
,如果有其余线程在期待,它就乖乖排到队尾,并切换为期待唤醒的休眠态。而如果没有其余线程在期待,它就间接获取资源。 - 对于非偏心锁,当一个线程创立之后,它会间接试着去获取资源,不论队伍里有没有人,如果这个时候正好资源被开释,那么非偏心锁因为是抢着应用资源的,提出资源申请比首个在队列中期待的线程要早,因而资源会间接给它。如果获取资源失败,它才会乖乖去队尾排队期待。
- 对于偏心锁,当一个线程创立之后,它会看是否有其余线程在期待资源,也就是看看
对于线程状态的切换,从休眠态到就绪态,这部分是须要工夫进行上下文切换的,因而,偏心锁每次都间接进入休眠态期待被唤醒,这自身就是很消耗工夫的事件,因而咱们才说 偏心锁性能低,非偏心锁性能高。
(非偏心锁尽管不偏心,然而性能高,真的是很讥刺的一种状况呐。)
3. 面试问题模仿
Q:偏心锁是什么?加锁流程是什么?
A:偏心锁是指在资源获取过程中,线程依照到来程序排队应用资源的一种锁机制,而非偏心锁则可能呈现不按程序的随机获取状况。
偏心锁的加锁流程体现在tryAcquire()
源码局部,当一个线程节点创立之后,它会判断以后是否有其余线程在期待以及资源是否可用,如果两个条件都满足,它则获取资源,如果不满足,它则乖乖排到队尾,期待被唤醒。
参考文献
- 面试突击 46:偏心锁和非偏心锁有什么区别?
- 讲一讲偏心锁和非偏心锁,为什么要“非偏心”?
- ReentrantLock 加锁过程源码详解