概述
本篇将介绍公平锁、非公平锁、可重入锁、自旋锁相关理论知识,同时结合相关源码和Demo进行解析,主要是以ReentrantLock作为例子。
公平锁
公平锁定义
公平锁是指线程按照申请所的顺序来获取锁,跟队列先进先出一样,排队按顺序获取锁
公平锁源码解析
1. 首先,看下ReentrantLock构造函数:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
- 可见,ReentrantLock默认是非公平锁 NonfairSync
- 构造函数支持传入参数 fair,假设我们现在传入true,就会创建公平锁 FairSync
2. FairSync源码:
final void lock() {
acquire(1);
}
- lock 就是我们调用的加锁方法,内部调用acquire,记住这个参数值1,后面会用到
3. acquire源码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire:此处尝试获取锁,true获取成功并且return,false获取失败并执行下一步代码
- addWaiter:给等待队列添加一个节点,标记当前线程正在等待获取锁,返回当前添加的节点Node
- acquireQueued:死循环(自旋锁)的方式,等待获取到锁资源,成功获取了才return
非公平锁
非公平锁定义
- 非公平锁指的是多个线程获取锁的顺序不一定是申请的顺序,有可能后面申请的线程反而比前面的线程优先获取。
- 非公平锁在申请锁资源时,会先尝试占有锁,成功了就独占锁资源,失败了就进入排队队列
- 高并发环境下,由于存在抢占锁的机制,可能会造成优先级反转或者饥饿现象。也就是说,原本排在第2的线程,顺序变成滞后。或者存在有的线程,长时间一直获取不到锁。
非公平锁源码解析
扫描二维码关注公众号,回复: 11929908 查看本文章
- 看下NonfairSync.lock方法:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
- compareAndSetState:尝试以CAS方式设置state=1,也就是抢先占有锁,成功了就设置独占锁的线程为当前线程
- acquire:上一步抢占锁失败了,就执行该方法。acquire跟公平锁源码一样,这里不贴出来了,有差别的是tryAcquire方法。
- tryAcquire源码:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
- 整个方法跟公平锁的基本一样,不同点是少了hasQueuedPredecessors方法。也就是说,不会先判断队列是否有等待线程,先占用再说,失败了再进入线程等待。
可重入锁(也叫作递归锁)
可重入锁定义
- 可重入锁指的是同一个线程获得锁之后,可以再次获得锁而不会发生死锁
- 加锁的次数和解锁的次数必须相同,ReentrantLock需要手动加锁解锁,synchronized会自动解锁
可重入锁Demo
public class 可重入锁App
{
private synchronized static void sendSMS(){
System.out.println(Thread.currentThread().getName() + " sendSMS");
sendEmail();
}
private synchronized static void sendEmail(){
System.out.println(Thread.currentThread().getName() + " sendEmail");
}
public static void main( String[] args )
{
new Thread(() -> {
sendSMS();
}, "Test-1").start();
}
}
- 定义了2个加锁方法:sendSMS、sendEmail。其中,sendSMS内部又调用了sendEmail
- 创建个线程执行sendSMS,可以看到2个方法都能正常调用,不会产生堵塞
输出结果:
Test-1 sendSMS
Test-1 sendEmail
Test-2 sendSMS
Test-2 sendEmail
自旋锁
自旋锁定义
- 当某个线程尝试获取锁资源失败,会无限次数地再次获取,直到成功为止
- 缺点是循环会消耗CPU资源
自旋锁Demo
公平锁的acquireQueued方法,==for( ; ; ){ }==就是自旋锁的方式