1
- 近年并发算法领域大多数研究都侧重非阻塞算法,这种算法用底层的原子机器指令代替锁来确保数据在并发访问中的一致性,非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC以及锁,并发数据结构中。
2
-
3
- 与锁的方案相比,非阻塞算法都要复杂的多,他们在可伸缩性和活跃性上(避免死锁)都有巨大优势。
1
+ 非阻塞算法,用底层的原子机器指令代替锁,确保数据在并发访问中的一致性。
2
+ 非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC及锁,并发数据结构中。
4
3
4
+ 与锁相比,非阻塞算法复杂的多,在可伸缩性和活跃性上(避免死锁)有巨大优势。
5
5
非阻塞算法,即多个线程竞争相同的数据时不会发生阻塞,因此能更细粒度的层次上进行协调,而且极大减少调度开销。
6
6
# 1 锁的劣势
7
7
独占,可见性是锁要保证的。
8
8
9
- 许多JVM都对非竞争的锁获取和释放做了很多优化,性能很不错了。
10
-
11
- 但是如果一些线程被挂起然后稍后恢复运行,当线程恢复后还得等待其他线程执行完他们的时间片,才能被调度,所以挂起和恢复线程存在很大的开销,其实很多锁的力度很小的,很简单,如果锁上存在着激烈的竞争,那么多调度开销/工作开销比值就会非常高。
12
-
13
- 与锁相比volatile是一种更轻量的同步机制,因为使用volatile不会发生上下文切换或者线程调度操作,但是volatile的指明问题就是虽然保证了可见性,但是原子性无法保证,比如i++的字节码就是N行。
9
+ 许多JVM都对非竞争的锁获取和释放做了很多优化,性能很不错。
10
+ 但若一些线程被挂起然后稍后恢复运行,当线程恢复后还得等待其他线程执行完他们的时间片,才能被调度,所以挂起和恢复线程存在很大开销。
11
+ 其实很多锁的粒度很小,很简单,若锁上存在激烈竞争,那么 调度开销/工作开销 比值就会非常高,降低业务吞吐量。
14
12
15
- 如果一个线程正在等待锁,它不能做任何事情,如果一个线程在持有锁的情况下呗延迟执行了,例如发生了缺页错误,调度延迟,那么就没法执行。如果被阻塞的线程优先级较高,那么就会出现priority invesion的问题,被永久的阻塞下去 。
13
+ 而与锁相比,volatile是一种更轻量的同步机制,因为使用volatile不会发生上下文切换或线程调度操作,但volatile的指明问题就是虽然保证了可见性,但是原子性无法保证 。
16
14
15
+ - 若一个线程正在等待锁,它不能做任何事情
16
+ - 若一个线程在持有锁情况下被延迟执行了,如发生缺页错误,调度延迟,就没法执行
17
+ - 若被阻塞的线程优先级较高,就会出现priority invesion问题,被永久阻塞
17
18
# 2 硬件对并发的支持
19
+ 独占锁是悲观锁,对细粒度的操作,更高效的应用是乐观锁,这种方法需要借助** 冲突监测机制,来判断更新过程中是否存在来自其他线程的干扰,若存在,则失败重试** 。
18
20
19
- 独占锁是悲观所,对于细粒度的操作,更高效的应用是乐观锁,这种方法需要借助** 冲突监测机制来判断更新过程中是否存在来自其他线程的干扰,如果存在则失败重试** 。
20
-
21
- 几乎所有的现代CPU都有某种形式的原子读-改-写指令,例如compare-and-swap等,JVM就是使用这些指令来实现无锁并发。
22
-
21
+ 几乎所有现代CPU都有某种形式的原子读-改-写指令,如compare-and-swap等,JVM就是使用这些指令来实现无锁并发。
23
22
## 2.1 比较并交换
24
-
25
23
CAS(Compare and set)乐观的技术。Java实现的一个compare and set如下,这是一个模拟底层的示例:
26
-
27
24
``` java
28
25
@ThreadSafe
29
26
public class SimulatedCAS {
27
+
30
28
@GuardedBy (" this" ) private int value;
31
29
32
30
public synchronized int get () {
@@ -47,9 +45,7 @@ public class SimulatedCAS {
47
45
== compareAndSwap(expectedValue, newValue));
48
46
}
49
47
}
50
-
51
48
```
52
-
53
49
## 2.2 非阻塞的计数器
54
50
``` java
55
51
public class CasCounter {
@@ -67,52 +63,32 @@ public class CasCounter {
67
63
return v + 1 ;
68
64
}
69
65
}
70
-
71
66
```
72
67
Java中使用AtomicInteger。
73
68
74
69
竞争激烈一般时,CAS性能远超基于锁的计数器。看起来他的指令更多,但无需上下文切换和线程挂起,JVM内部的代码路径实际很长,所以反而好些。
75
70
76
- 但激烈程度较高时,它的开销还是较大,但是你会发生这种激烈程度非常高的情况只是理论,实际生产环境很难遇到。
77
- 况且JIT很聪明,这种操作往往能非常大的优化。
71
+ 但激烈程度较高时,开销还是较大,但会发生这种激烈程度非常高的情况只是理论,实际生产环境很难遇到。况且JIT很聪明,这种操作往往能非常大的优化。
78
72
79
73
为确保正常更新,可能得将CAS操作放到for循环,从语法结构看,使用** CAS** 比使用锁更加复杂,得考虑失败情况(锁会挂起线程,直到恢复)。
80
74
但基于** CAS** 的原子操作,性能基本超过基于锁的计数器,即使只有很小的竞争或不存在竞争!
81
75
82
76
在轻度到中度争用情况下,非阻塞算法的性能会超越阻塞算法,因为 CAS 的多数时间都在第一次尝试时就成功,而发生争用时的开销也不涉及** 线程挂起** 和** 上下文切换** ,只多了几个循环迭代。
83
77
没有争用的 CAS 要比没有争用的锁轻量得多(因为没有争用的锁涉及 CAS 加上额外的处理,加锁至少需要一个CAS,在有竞争的情况下,需要操作队列,线程挂起,上下文切换),而争用的 CAS 比争用的锁获取涉及更短的延迟。
84
78
85
- CAS的缺点是它使用调用者来处理竞争问题 ,通过重试、回退、放弃,而锁能自动处理竞争问题,例如阻塞。
79
+ CAS的缺点是,它使用调用者来处理竞争问题 ,通过重试、回退、放弃,而锁能自动处理竞争问题,例如阻塞。
86
80
87
- 原子变量可以看做更好的volatile类型变量。
88
- AtomicInteger在JDK8里面做了改动。
89
- ``` java
90
- public final int getAndIncrement() {
91
- return unsafe. getAndAddInt(this , valueOffset, 1 );
92
- }
93
-
94
- ```
81
+ 原子变量可看做更好的volatile类型变量。AtomicInteger在JDK8里面做了改动。
82
+ ![ ] ( https://img-blog.csdnimg.cn/0f94ab5e4b6045e5aa83d99cbc9c03c4.png )
95
83
JDK7里面的实现如下:
96
- ``` java
97
- public final int getAndAdd(int delta) {
98
- for (;;) {
99
- intcurrent= get();
100
- intnext= current+ delta;
101
- if (compareAndSet(current,next))
102
- returncurrent;
103
- }
104
- }
105
-
106
- ```
107
- Unsafe是经过特殊处理的,不能理解成常规的Java代码,区别在于:
108
- - 1.8在调用getAndAddInt的时候,如果系统底层支持fetch-and-add,那么它执行的就是native方法,使用的是fetch-and-add
109
- - 如果不支持,就按照上面的所看到的getAndAddInt方法体那样,以java代码的方式去执行,使用的是compare-and-swap
84
+ ![ ] ( https://img-blog.csdnimg.cn/d2f94066894a4501b6dd5e6d9ad4a8c1.png )
85
+ Unsafe是经过特殊处理的,不能理解成常规的Java代码,1.8在调用getAndAddInt时,若系统底层:
86
+ - 支持fetch-and-add,则执行的就是native方法,使用fetch-and-add
87
+ - 不支持,就按照上面getAndAddInt那样,以Java代码方式执行,使用compare-and-swap
110
88
111
89
这也正好跟openjdk8中Unsafe::getAndAddInt上方的注释相吻合:
112
- ``` java
113
- // The following contain CAS-based Java implementations used on
114
- // platforms not supporting native instructions
115
- ```
90
+ 以下包含在不支持本机指令的平台上使用的基于 CAS 的 Java 实现
91
+ ![ ] ( https://img-blog.csdnimg.cn/327bda8392cf4158ab94049e67f9b169.png )
116
92
# 3 原子变量类
117
93
J.U.C的AtomicXXX。
118
94
@@ -164,18 +140,11 @@ public class CasNumberRange {
164
140
}
165
141
}
166
142
}
167
-
168
143
```
169
-
170
-
171
144
# 4 非阻塞算法
172
-
173
145
Lock-free算法,可以实现栈、队列、优先队列或者散列表。
174
-
175
146
## 4.1 非阻塞的栈
176
-
177
- Trebier算法,1986年提出的。
178
-
147
+ Trebier算法,1986年提出。
179
148
``` java
180
149
public class ConcurrentStack <E> {
181
150
AtomicReference<Node<E > > top = new AtomicReference<Node<E > > ();
@@ -210,13 +179,9 @@ Trebier算法,1986年提出的。
210
179
}
211
180
}
212
181
}
213
-
214
182
```
215
-
216
183
## 4.2 非阻塞的链表
217
-
218
- 有点复杂哦,实际J.U.C的ConcurrentLinkedQueue也是参考了这个由Michael and Scott,1996年实现的算法。
219
-
184
+ J.U.C的ConcurrentLinkedQueue也是参考这个由Michael and Scott,1996年实现的算法。
220
185
``` java
221
186
public class LinkedQueue <E> {
222
187
@@ -257,19 +222,14 @@ public class LinkedQueue <E> {
257
222
}
258
223
}
259
224
}
260
-
261
225
```
262
-
263
226
## 4.3 原子域更新
264
-
265
- AtomicReferenceFieldUpdater,一个基于反射的工具类,它能对指定类的指定的volatile引用字段进行原子更新。(注意这个字段不能是private的)
227
+ AtomicReferenceFieldUpdater,一个基于反射的工具类,能对指定类的指定的volatile引用字段进行原子更新。(该字段不能是private的)
266
228
267
229
通过调用AtomicReferenceFieldUpdater的静态方法newUpdater就能创建它的实例,该方法要接收三个参数:
268
-
269
230
* 包含该字段的对象的类
270
231
* 将被更新的对象的类
271
232
* 将被更新的字段的名称
272
-
273
233
``` java
274
234
AtomicReferenceFieldUpdater updater= AtomicReferenceFieldUpdater . newUpdater(Dog . class,String . class," name" );
275
235
Dog dog1= new Dog ();
@@ -279,5 +239,4 @@ AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.c
279
239
class Dog {
280
240
volatile String name= " dog1" ;
281
241
}
282
-
283
242
```
0 commit comments