前言
阅读前你需要:
- 了解ReentrantLock和synchronized的基本使用
- 了解AQS
在Java开发中,通常情况下使用到的锁(单机情况)有:
- ReentrantLock类
- synchronized关键字
而关于公平锁与非公平锁的概念则是能够按照抵达顺序依次分配锁的则是公平锁,否则是非公平锁。也就是先来的先分配,后来的排队等待。
其中,ReentrantLock支持公平锁和非公平锁。而synchronized是非公平锁。于是我产生了一个疑问:非公平锁到底有多不公平?
实验
以下实验将使用睡眠来模拟业务操作,代码如下:
public class Sleep {
public static void sleep(TimeUnit timeUnit, long time) {
try {
timeUnit.sleep(time);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
synchronized 关键字
示例代码
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
new Thread(SyncWordDemo::test).start();
}
}
public static synchronized void test() {
System.out.println("locked " + Thread.currentThread().getName());
Sleep.sleep(TimeUnit.NANOSECONDS, 10000);
}
}
示例代码很简单,通过不断创造线程并让线程抢锁并模拟执行业务(睡眠)
执行的结果大致如下:
locked Thread-0
locked Thread-17
locked Thread-16
locked Thread-13
locked Thread-1
locked Thread-52
locked Thread-12
locked Thread-9
locked Thread-8
locked Thread-34
locked Thread-133
locked Thread-5
locked Thread-4
locked Thread-2
locked Thread-182
locked Thread-178
locked Thread-175
locked Thread-174
locked Thread-181
locked Thread-335
locked Thread-170
locked Thread-264
locked Thread-287
locked Thread-167
locked Thread-296
locked Thread-166
locked Thread-163
locked Thread-162
locked Thread-159
locked Thread-169
locked Thread-307
locked Thread-165
locked Thread-302
locked Thread-164
locked Thread-530
locked Thread-584
locked Thread-1310
locked Thread-276
locked Thread-157
locked Thread-153
locked Thread-152
locked Thread-1613
locked Thread-51
locked Thread-1724
locked Thread-131
locked Thread-130
locked Thread-47
locked Thread-1735
locked Thread-1774
locked Thread-127
locked Thread-1800
locked Thread-129
locked Thread-128
locked Thread-46
locked Thread-125
locked Thread-247
locked Thread-49
locked Thread-1974
locked Thread-122
locked Thread-2011
locked Thread-2025
locked Thread-121
locked Thread-119
locked Thread-120
locked Thread-2181
locked Thread-117
locked Thread-2236
locked Thread-239
locked Thread-116
locked Thread-115
locked Thread-114
locked Thread-37
locked Thread-234
locked Thread-113
locked Thread-112
locked Thread-109
locked Thread-108
locked Thread-107
locked Thread-106
locked Thread-229
locked Thread-105
locked Thread-32
locked Thread-26
locked Thread-100
locked Thread-2611
locked Thread-222
locked Thread-2630
locked Thread-2659
locked Thread-98
locked Thread-2713
locked Thread-220
locked Thread-2721
locked Thread-2751
locked Thread-23
locked Thread-95
locked Thread-217
locked Thread-92
locked Thread-215
locked Thread-214
locked Thread-91
locked Thread-90
locked Thread-2850
locked Thread-209
locked Thread-2890
locked Thread-89
locked Thread-88
locked Thread-86
locked Thread-2998
locked Thread-206
locked Thread-82
locked Thread-3042
locked Thread-85
locked Thread-84
locked Thread-81
locked Thread-79
locked Thread-201
locked Thread-75
locked Thread-74
locked Thread-80
locked Thread-3127
locked Thread-197
locked Thread-70
locked Thread-77
locked Thread-67
locked Thread-3363
locked Thread-66
locked Thread-73
locked Thread-76
locked Thread-3389
locked Thread-72
locked Thread-69
locked Thread-68
locked Thread-65
locked Thread-64
locked Thread-61
locked Thread-11
locked Thread-60
locked Thread-57
locked Thread-3541
locked Thread-3578
locked Thread-56
locked Thread-53
locked Thread-63
locked Thread-3668
locked Thread-3680
locked Thread-3690
locked Thread-62
locked Thread-59
locked Thread-58
locked Thread-55
locked Thread-3792
locked Thread-54
locked Thread-132
locked Thread-50
locked Thread-3837
locked Thread-3861
locked Thread-126
locked Thread-124
locked Thread-43
locked Thread-42
locked Thread-123
locked Thread-48
locked Thread-3992
locked Thread-39
locked Thread-4042
locked Thread-38
locked Thread-45
locked Thread-44
locked Thread-118
locked Thread-40
locked Thread-111
locked Thread-36
locked Thread-35
locked Thread-103
locked Thread-104
locked Thread-31
locked Thread-102
locked Thread-101
locked Thread-30
locked Thread-29
locked Thread-4329
locked Thread-27
locked Thread-4373
locked Thread-4392
locked Thread-96
locked Thread-22
locked Thread-28
locked Thread-4507
locked Thread-25
locked Thread-19
locked Thread-24
locked Thread-15
locked Thread-4568
locked Thread-18
locked Thread-21
locked Thread-20
locked Thread-4649
locked Thread-14
locked Thread-10
locked Thread-7
locked Thread-6
locked Thread-4748
locked Thread-3
locked Thread-4735
locked Thread-4798
locked Thread-4776
locked Thread-4755
locked Thread-4838
locked Thread-4795
locked Thread-4794
locked Thread-4791
locked Thread-4790
locked Thread-4851
locked Thread-4787
locked Thread-5049
locked Thread-4786
locked Thread-4783
locked Thread-4782
locked Thread-4778
locked Thread-4775
locked Thread-4774
locked Thread-4771
locked Thread-4899
locked Thread-4770
locked Thread-4767
locked Thread-4766
locked Thread-4763
locked Thread-4762
locked Thread-4973
locked Thread-4830
locked Thread-4773
locked Thread-4772
locked Thread-4769
locked Thread-4765
locked Thread-4991
locked Thread-4822
locked Thread-4764
locked Thread-4761
locked Thread-4760
locked Thread-4754
locked Thread-4757
locked Thread-5016
locked Thread-4756
locked Thread-4753
locked Thread-5028
locked Thread-4717
locked Thread-4750
locked Thread-4752
locked Thread-4809
locked Thread-4749
locked Thread-4746
locked Thread-5096
locked Thread-4745
locked Thread-4741
locked Thread-4742
locked Thread-4743
locked Thread-5626
locked Thread-4744
locked Thread-4777
locked Thread-4738
locked Thread-5136
locked Thread-5091
locked Thread-4739
locked Thread-4737
locked Thread-4799
locked Thread-4736
locked Thread-4734
locked Thread-4702
locked Thread-4731
locked Thread-4732
locked Thread-4730
locked Thread-5173
locked Thread-4727
locked Thread-4729
locked Thread-5198
locked Thread-4728
locked Thread-4779
locked Thread-4726
locked Thread-4725
locked Thread-4723
locked Thread-4722
locked Thread-5258
locked Thread-4721
locked Thread-4720
locked Thread-4719
locked Thread-5267
locked Thread-4718
locked Thread-4751
// 太长了就贴到这里
结果
打印结果确实是非公平的,但并不是毫无规律地那种非公平。从结果中大致能总结出几条规律:
- 不是先请求先得到锁(废话)
- 某个时间节点后,后来的线程可能先得到锁,且与他相邻的更早创建的线程可能跟随着一起得到锁。说起来有点拗口,(以下
x代表数字)参考上面数据中从xx突然跳到47xx,然后477x递减到471x。当然中间也是有不规律插队的情况,毕竟这是非公平锁嘛
看到这里你可能还是没什么感觉,毕竟结果显示获得锁的线程确实不是有序的。且第二点的规律也不能说明什么。那么不妨再来看看ReentrantLock的非公平锁的结果吧
ReentrantLock
示例代码
public class NofairDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(false);
for (int i = 0; i < 1000000; i++) {
new Thread(() -> {
lock.lock();
System.out.println("locked " + Thread.currentThread().getName());
Sleep.sleep(TimeUnit.NANOSECONDS, 100);
lock.unlock();
}).start();
}
}
}
代码和synchronized关键字是差不多的,就不做解释了,直接看结果:
locked Thread-0
locked Thread-71
locked Thread-1
locked Thread-2
locked Thread-143
locked Thread-3
locked Thread-198
locked Thread-228
locked Thread-4
locked Thread-278
locked Thread-296
locked Thread-5
locked Thread-334
locked Thread-6
locked Thread-7
locked Thread-412
locked Thread-423
locked Thread-9
locked Thread-484
locked Thread-8
locked Thread-489
locked Thread-10
locked Thread-12
locked Thread-11
locked Thread-524
locked Thread-13
locked Thread-572
locked Thread-604
locked Thread-14
locked Thread-15
locked Thread-16
locked Thread-844
locked Thread-696
locked Thread-683
locked Thread-933
locked Thread-17
locked Thread-970
locked Thread-18
locked Thread-1017
locked Thread-19
locked Thread-1072
locked Thread-20
locked Thread-21
locked Thread-22
locked Thread-1120
locked Thread-24
locked Thread-25
locked Thread-26
locked Thread-1170
locked Thread-23
locked Thread-1237
locked Thread-28
locked Thread-27
locked Thread-31
locked Thread-32
locked Thread-29
locked Thread-1284
locked Thread-1300
locked Thread-1319
locked Thread-30
locked Thread-1358
locked Thread-33
locked Thread-1405
locked Thread-35
locked Thread-1571
locked Thread-34
locked Thread-36
locked Thread-1676
locked Thread-37
locked Thread-1692
locked Thread-38
locked Thread-1756
locked Thread-39
locked Thread-1791
locked Thread-1814
locked Thread-1817
locked Thread-40
locked Thread-41
locked Thread-1868
locked Thread-1880
locked Thread-1902
locked Thread-1937
locked Thread-42
locked Thread-2007
locked Thread-2045
locked Thread-43
locked Thread-2069
locked Thread-44
locked Thread-2098
locked Thread-45
locked Thread-2142
locked Thread-46
locked Thread-47
locked Thread-2155
locked Thread-48
locked Thread-2281
locked Thread-2298
locked Thread-52
locked Thread-2306
locked Thread-2343
locked Thread-50
locked Thread-2404
locked Thread-51
locked Thread-2324
locked Thread-49
locked Thread-2489
locked Thread-53
locked Thread-2527
locked Thread-2549
locked Thread-2546
locked Thread-58
locked Thread-2577
locked Thread-55
locked Thread-2613
locked Thread-56
locked Thread-2673
locked Thread-2707
locked Thread-57
locked Thread-2748
locked Thread-54
locked Thread-2802
locked Thread-59
locked Thread-2841
locked Thread-2878
locked Thread-65
locked Thread-2932
locked Thread-61
locked Thread-2868
locked Thread-62
locked Thread-3025
locked Thread-63
locked Thread-64
locked Thread-3068
locked Thread-2967
locked Thread-3258
locked Thread-3156
locked Thread-60
locked Thread-3214
locked Thread-66
locked Thread-3259
locked Thread-3276
locked Thread-67
locked Thread-68
locked Thread-69
locked Thread-3326
locked Thread-3350
locked Thread-70
locked Thread-3403
locked Thread-72
locked Thread-3597
locked Thread-73
locked Thread-3333
locked Thread-74
locked Thread-3538
locked Thread-75
locked Thread-3769
locked Thread-76
locked Thread-3792
locked Thread-3830
locked Thread-77
locked Thread-3889
locked Thread-3929
locked Thread-3946
locked Thread-78
locked Thread-79
locked Thread-4001
locked Thread-80
locked Thread-81
locked Thread-82
locked Thread-4052
locked Thread-83
locked Thread-4090
locked Thread-84
locked Thread-4115
locked Thread-4181
locked Thread-85
locked Thread-4237
locked Thread-86
locked Thread-87
locked Thread-4346
locked Thread-88
locked Thread-4378
locked Thread-89
locked Thread-4448
locked Thread-90
locked Thread-91
locked Thread-4491
locked Thread-92
locked Thread-100
locked Thread-4580
locked Thread-4589
locked Thread-101
locked Thread-4632
locked Thread-102
locked Thread-96
locked Thread-104
locked Thread-4738
locked Thread-4775
locked Thread-98
locked Thread-4824
locked Thread-4860
locked Thread-99
locked Thread-4917
locked Thread-93
locked Thread-4973
locked Thread-94
locked Thread-5012
locked Thread-95
locked Thread-103
locked Thread-97
locked Thread-105
locked Thread-106
locked Thread-114
locked Thread-108
locked Thread-109
locked Thread-5173
locked Thread-110
locked Thread-5205
locked Thread-118
locked Thread-5290
locked Thread-113
locked Thread-5310
locked Thread-119
locked Thread-5355
locked Thread-5368
locked Thread-115
locked Thread-5387
locked Thread-5425
locked Thread-116
locked Thread-117
locked Thread-5534
locked Thread-111
locked Thread-5543
locked Thread-5551
locked Thread-112
locked Thread-107
locked Thread-5629
locked Thread-120
locked Thread-5649
locked Thread-121
locked Thread-5693
locked Thread-5687
locked Thread-122
locked Thread-5873
locked Thread-5891
locked Thread-123
locked Thread-5938
locked Thread-124
locked Thread-6006
locked Thread-125
locked Thread-126
locked Thread-6069
locked Thread-6093
locked Thread-6133
locked Thread-127
locked Thread-6189
locked Thread-128
locked Thread-6205
locked Thread-129
locked Thread-6234
locked Thread-130
locked Thread-131
locked Thread-6286
locked Thread-132
locked Thread-6305
locked Thread-133
locked Thread-134
locked Thread-6373
locked Thread-135
locked Thread-136
locked Thread-6445
locked Thread-137
locked Thread-6472
locked Thread-6484
locked Thread-138
locked Thread-6509
locked Thread-139
locked Thread-140
locked Thread-6557
locked Thread-141
locked Thread-142
locked Thread-144
locked Thread-6640
locked Thread-145
locked Thread-146
locked Thread-6675
locked Thread-148
locked Thread-147
locked Thread-6657
locked Thread-6689
locked Thread-6717
locked Thread-150
locked Thread-6773
locked Thread-6750
locked Thread-149
locked Thread-6799
locked Thread-6809
locked Thread-151
locked Thread-152
locked Thread-6844
locked Thread-153
locked Thread-154
locked Thread-6834
locked Thread-156
locked Thread-6949
locked Thread-6959
locked Thread-155
locked Thread-7044
locked Thread-6977
locked Thread-157
locked Thread-7136
locked Thread-158
locked Thread-7232
locked Thread-7027
locked Thread-159
locked Thread-160
locked Thread-162
locked Thread-7117
locked Thread-164
locked Thread-163
locked Thread-7170
locked Thread-161
locked Thread-7036
locked Thread-7265
locked Thread-165
locked Thread-7318
locked Thread-166
locked Thread-7358
locked Thread-167
locked Thread-7398
locked Thread-7412
locked Thread-168
locked Thread-7450
locked Thread-7490
locked Thread-169
locked Thread-7553
locked Thread-170
locked Thread-7596
locked Thread-171
locked Thread-7639
locked Thread-174
locked Thread-175
locked Thread-7833
locked Thread-176
locked Thread-7875
locked Thread-177
locked Thread-7894
locked Thread-172
locked Thread-7948
locked Thread-173
locked Thread-7986
locked Thread-8018
locked Thread-178
locked Thread-8053
locked Thread-179
locked Thread-182
locked Thread-8110
locked Thread-183
locked Thread-180
locked Thread-181
locked Thread-184
locked Thread-185
locked Thread-189
locked Thread-187
locked Thread-188
locked Thread-8243
locked Thread-186
locked Thread-190
locked Thread-8294
locked Thread-192
locked Thread-8322
locked Thread-191
locked Thread-8341
locked Thread-193
locked Thread-195
locked Thread-194
locked Thread-8368
locked Thread-8371
locked Thread-196
locked Thread-197
locked Thread-199
locked Thread-200
locked Thread-201
locked Thread-8395
locked Thread-8423
locked Thread-202
locked Thread-203
locked Thread-8467
locked Thread-204
locked Thread-205
locked Thread-206
locked Thread-8482
locked Thread-8513
locked Thread-207
locked Thread-8543
locked Thread-208
locked Thread-8580
locked Thread-209
locked Thread-8638
locked Thread-8654
locked Thread-210
locked Thread-8735
locked Thread-8716
locked Thread-8709
locked Thread-211
locked Thread-8767
locked Thread-212
locked Thread-8801
locked Thread-213
locked Thread-214
locked Thread-8880
locked Thread-8903
locked Thread-215
locked Thread-8959
locked Thread-216
locked Thread-217
locked Thread-8990
locked Thread-218
locked Thread-9047
locked Thread-219
locked Thread-221
// 下面就不贴了
结果
从结果可以看出大致规律:
- 确实是非公平锁(废话)
- 有插队现象,但隐隐约约能看出来大体上还是按照先来的先获得锁。参考数据从头开始看,从
1到100,虽然中间可能插入几十,几百的,但1到100的顺序看起来是有序且递增的。似乎还是有那么一点公平在里面?
实验总结
synchronized关键字:有后来者插队现象;且同处于等待锁时,后来的更容易获得锁?
ReentrantLock类:有后来者插队现象;且同处于等待锁时,先来的更容易获得锁?
是什么促使了ReentrantLock和synchronized的非公平逻辑不太一样?
原理
ReentrantLock
构造器
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
当传入fair=false时,sync=new NonfairSync()
NofairSync里长这样
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); // c=被上锁的次数,0=没被上锁
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;
}
// 其他无关方法就不贴了
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1)) // 立即尝试获得锁,如果成功就不用排队
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 重点,这是AQS获得资源的方法
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); // AQS的模板方法,实现为非公平尝试获得锁
}
}
截止目前为止,用文字描述非公平锁上锁流程:
- 立即尝试获得一次锁,获得成功即上锁
- 获得失败则调用
AQS.acquire(1)
接下来看AQS的方法。注意,公平锁与非公平锁的AQS处理逻辑是一样的,也就是说,下面的代码即能运行公平锁也能运行非公平锁。实现公平锁的关键逻辑在多调用一个方法: hasQueuedPredecessors,即判断队列中是否有其他线程在等待(言外之意:如果有其他线程在排队,则按照排队顺序依次获取;否则我管你的,大家一起抢就完事了)。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 立即尝试获得一次锁。这是模板方法,会调用子类实现。
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 这里有两步:1、先把当前线程包装成Node添加到队尾;2、不断尝试获得锁,直到成功或取消。
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false; // 是否被中断,默认没有被中断
for (;;) {
final Node p = node.predecessor(); // predecessor的中文是前任者,在AQS的语境下是前一个节点。相对的,还有后继者,就是后一个节点。
if (p == head && tryAcquire(arg)) { // 如果前任=头结点,那么立即尝试获得锁
// 👇 获得锁成功的逻辑
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 是否在获得锁失败的时候进行park。这里的park就是LockSupport.park()。等待锁持有者释放后唤醒当前线程。
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
}
截止目前为止,再次用文字描述非公平锁上锁流程:
-
立即尝试获得一次锁,获得成功即上锁
-
获得失败则调用
AQS.acquire(1) -
立即尝试获得一次锁,获得成功即上锁
-
获得失败则将本线程包装成
Node,加入队尾 -
立即判断一次,当前任节点是头结点时立即获得一次锁,获得成功即上锁
-
判断前任节点状态
- 如果
waitStatus=0则调整成SIGNAL然后再走一次步骤5 - 如果
waitStatus=SIGNAL则本线程可以安心地睡去了,等被唤醒的时候重复步骤5
- 如果
步骤1、3、5中可以看出,非公平锁在初次上锁时有非常多次立即抢锁的机会,当其他线程还在park的时候就直接开抢。
步骤5,从park中醒来后只有前任节点是锁持有者的时候才开始抢锁,这揭示了为什么获得锁的线程是先来的容易获得锁。
所以体现出来的结果是:有后来者插队现象;且同处于等待锁时,先来的更容易获得锁
更多ReentrantLock和AQS的原理解读可以参考美团的文章:tech.meituan.com/2019/12/05/…
synchronized
原理可以参考文章:https://juejin.cn/post/6844903640197513230。重点关注同步原理、自旋锁、自适应自旋锁。 重点关注同步原理、自旋锁、自适应自旋锁。
源码可以参考文章:https://blog.csdn.net/ckiuick/article/details/128339591
虽然synchronized在修饰方法、对象、类时有不同的实现,但其上锁的核心逻辑是一样的。
用文字描述synchronized上锁流程:
- 立即尝试获得一次锁,获得成功即上锁
- 获得锁失败,如果自旋次数未用尽则重复步骤1,否则线程阻塞等待下次唤醒时继续自旋
由于jdk的优化:
- 越容易获得锁的资源,给予的自旋次数越多
- 越不容易获得锁的资源,给予的自旋次数越少
将原理套用在前面实验结论中就明了了,从线程1到线程10000依次初始化并尝试获得锁的过程中:
- 当初始化到1-1000时,1-100是刚初始化的,自旋次数多,很容易获得锁。但后来的100-999线程初始化时也有自旋次数可以使用,于是容易造成插队的情况
- 当初始化到1000-2000时,1000-1100是刚初始化的,自旋次数多,容易获得锁。但前1-1000不一定在上一阶段全部都获得过锁。于是1-2000号线程同时竞争,由于1-1000号前面自旋过不少次,但都没有成功获得锁,于是自旋次数被jvm降低。体现出来的结果是:此阶段下,1-1000号比1000-2000号更不容易获得锁(因为自旋次数少,且抢锁的人增多)
- 往后逻辑同上
所以体现出来的结果是:有后来者插队现象;且同处于等待锁时,后来的更容易获得锁