AbstractQueuedSynchronizer简介 AQS是一个用来构建和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的RenntrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松的构造出符合我们自己需求的同步器。

AQS核心思想 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁的分配机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH队列是一个虚拟的双向队列。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点来实现锁的分配。

AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改.

1

状态信息通过procted类型的getState,setState,compareAndSetState进行操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12

AQS对资源的共享方式 AQS定义两种资源共享方式

  • Exclusive(独占):只有一个线程能执行,如ReentrantLock。可分为公平锁和非公平锁 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 非公平锁:当线程要获取到锁时,无视队列中的顺序直接去获取锁,谁抢到就是谁的
  • Share(共享):多个线程可同时执行,如Semaphore.COuntDownLatch等等 RenntrantReadWriteLock可以堪称是组合式,因为RenntrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放即可,至于具体线程等待队列的维护,AQS已经在上层已经帮我们实现好了。

AQS底层使用了模板方法模式

1

使用者继承AbstractQueuedSychronized并重写指定的方法。(这些重写方法很简单,无非是对共享资源state的获取和释放)将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

这和我们以往通过实现接口的方式有很大区别

AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:

1
2
3
4
5

默认情况下,每个方法都抛出UnsupportedOperationException。这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final,所以无法被其他类使用,只有这几个方法可以被其他类使用。

以RenntrantLock为例,state初始化为0,表示为锁定状态。A线程lock()时,会调用tryAcquire()独占锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其他线程次啊有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多少次,这样才能保证state是能回到零态的。

AbstractQueuedSynchronizer数据结构 AbstractQueuedSynchronizer类底层的数据结构是使用CLH队列,是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配,其中Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。而Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表,并且可能会有多个Condition queue。

AbstractQueuedSynchronized源码分析

类的继承关系

AbstractQueuedSynchronized继承自AbstractOwnableSynchronizer抽象类,并且实现了Serializable接口,可以进行序列化。

1

其中ABstractQueuedOwnableSynchronizer抽象类的源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

AbstractOwnableSynchronizer抽象类中,可以设置独占资源线程和获取独占资源线程。分别为setExclusiveOwnerThread与getExclusiveOwnerThread方法,这两个方法会被子类调用。

1

类的内部类 - Node类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

每个线程被阻塞的线程都会被封装成一个Node结点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态,状态如下:

  • CANCELLED,值为1,表示当前的线程被取消。
  • SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作。
  • CONDITION,值为-2,表示当前节点在等待condition,也就是在condition queue中。
  • PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行。
  • 值为0,表示当前节点在sync queue中,等待着获取锁。 类的内部类 - ConditionObject类 这个类有点长
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472

此类实现了Condition接口,Condition接口定义了条件操作规范,具体如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Condition接口中定义了await,singnal方法,用来等待条件,释放条件。之后会详细分析ConditionObject的源码

类的属性 属性中包含了头结点head,尾结点tail,状态state、自旋时间spinForTimeoutThreshold,还有ABstractQueuedSynchronizer抽象属性再内存中的偏移地址,通过该偏移地址,可以获取和设置该属性的值,同时还包括一个静态初始化块,用于加载内存偏移地址。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

类的构造方法 此类的构造方法为从抽象类的构造方法,供子类调用

1

类的核心方法-acquire方法 该方法以独占的模式获取资源,忽略中断,即线程在acquire过程中,中断此线程是无效的,源码如下:

1
2
3
4

由上述源码可知,当一个线程调用acquire时,调用方法流程如下:

  • 首先调用tryAcquire方法,调用此方法的线程会试图在独占模式下获取对象状态。此方法应该查询是否允许它在独占模式下获取对象状态,如果允许,则获取它。在AbstractQueuedSynchronizer源码中默认会抛出一个异常,即需要子类去重写此方法完成自己的逻辑,之后会进行分析
  • 若tryAcquire失败,则调用addWaiter方法,addWaiter方法完成的功能是将调用此方法的线程封装成为一个结点并放入Sync queue
  • 调用acquireQueued方法,此方法的功能是Sync queue中的结点不断尝试获取资源,若成功,返回true,若失败,返回false
  • 由于tryAcquire默认实现是抛出异常,所以此时,不进行分析,之后会结合一个例子进行分析 首先分析addWaiter方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

addWaiter方法使用快速添加的方式往sync queue尾部添加结点,如果sync queue队列还没有初始化,则会使用enq插入队列中,enq方法源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18

addWaiter方法使用快速添加的方法往sync queue尾部添加结点,如果sync queue队列还没有初始化,则会使用enq插入队列中,enq方法源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

首先获取当前结点的前驱节点,如果前驱节点是头节点并且能够获取资源,代表该当前节点能够占有锁,设置头节点为当前节点,返回,否则,调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法,首先,我们看shouldParkAfterFailedAccquired方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

只有当该节点的前驱结点的状态为SIGNAL时,才可以对该结点所封装的线程进行park操作。否则,将不能进行park操作。再看parkAndCheckInterrupt方法,源码如下

1
2
3
4
5
6

parkAndCheckInterrupt方法里的逻辑是首先执行park操作,即禁用当前线程,然后返回该线程是否已经被中断。再看final块中的cancelAcquire方法,其源码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

该方法完成的功能就是取消当前线程对资源的获取,即设置当前结点状态为CANCELLED,接着我们看unparkSuccessor方法,源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

该方法的作用就是为了释放node结点的后继结点。

现在,再来看acquireQueued方法的整个的逻辑,逻辑如下:

  • 判断结点的前驱是否为head并且是否2成功获取资源
  • 若步骤1均满足,则设置结点为head,之后会判断是否为finally模块,然后返回
  • 若步骤2不满足,则判断是否需要park当前线程,是否需要park当前线程的逻辑是判断结点的前驱结点状态是否为SINGNAL,若是,则park前结点,否则,不进行park操作。
  • 若park了当前线程,之后某个线程对本线程unpark之后,并且本线程也获得机会运行,那么,将会继续进行步骤1的判断。 类的核心方法-release方法 以独占模式释放对象,其源码如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10

其中,tryRelease的默认实现是抛出异常,需要具体的子类实现,如果tryRelease成功,那么如果头结点不为空并且头节点的状态不为0,则释放头节点的后继结点,unparkSuccessor方法已经分析过