Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 948cc41

Browse files
fix
1 parent 8b789e3 commit 948cc41

File tree

3 files changed

+218
-126
lines changed

3 files changed

+218
-126
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# 前言
2+
定义俩共享变量及俩方法:
3+
- 第一个方法,
4+
- 第二个方法
5+
- (r1,r2)的可能值有哪些?
6+
![](https://img-blog.csdnimg.cn/05139ccfbb40447a869632ff35959841.png)
7+
8+
在单线程环境下,可先调用第一个方法,最终(r1,r2)为(1,0)
9+
也可以先调用第二个方法,最终为(0,2)。
10+
11+
![](https://img-blog.csdnimg.cn/20200404214401993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
12+
# 1 Java内存模型的意义
13+
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTkyYTFmZGY0OGJlMTllMDYucG5n?x-oss-process=image/format,png)
14+
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzZTVlOWE0OWFkZWI1YTEucG5n?x-oss-process=image/format,png)
15+
JMM 与硬件内存架构对应关系![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVlMTM3NGEwYWJmOWM5MjkucG5n?x-oss-process=image/format,png)
16+
JMM抽象结构图
17+
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ0ZWE4ODQzYTg4YTk0MGQucG5n?x-oss-process=image/format,png)
18+
内存模型描述程序的可能行为。
19+
20+
Java虚拟机规范中试图定义一种Java内存模型,`来屏蔽掉各种硬件和os的内存访问差异`,规定:
21+
- 线程如何、何时能看到其他线程修改过的共享变量的值
22+
- 必要时,如何同步地访问共享变量
23+
24+
以实现让Java程序在各种平台下都能达到一致性的内存访问效果。
25+
26+
JMM通过检查执行跟踪中的每个读操作,并根据某些规则检查该读操作观察到的写操作是否有效来工作。
27+
28+
只要程序的所有执行产生的结果都可由JMM预测。具体实现者任意实现,包括操作的重新排序和删除不必要的同步。
29+
30+
JMM决定了在程序的每个点上可以读取什么值。
31+
## 1.1 共享变量(Shared Variables)
32+
可在线程之间共享的内存称为`共享内存或堆内存`。所有实例字段、静态字段和数组元素都存储在堆内存。
33+
不包括局部变量与方法参数,因为这些是线程私有的,不存在共享。
34+
35+
对同一变量的两次访问(读或写),若有一个是写请求,则是冲突的!
36+
# 2 主内存与工作内存
37+
工作内存缓存
38+
![](https://img-blog.csdnimg.cn/20191014024209488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
39+
JMM的主要是定义了`各个变量的访问规则`,在JVM中的如下底层细节:
40+
- 将变量存储到内存
41+
- 从内存中取出变量值
42+
43+
为获得较好执行效率,JMM并未限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互,也没有限制即时编译器调整代码执行顺序这类权限。
44+
45+
JMM规定:
46+
- 所有变量都存储在主内存(Main Memory)
47+
- 每条线程有自己的工作内存(Working Memory)
48+
保存了该线程使用到的`变量的主内存副本拷贝`(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝)
49+
线程对变量的所有操作(读,赋值等)都必须在工作内存进行,不能直接读写主内存中的变量
50+
volatile变量依然有工作内存的拷贝,,是他特殊的操作顺序性规定,看起来如同直接在主内存读写
51+
不同线程间,无法直接访问对方工作内存中的变量,线程间变量值的传递均要通过主内存
52+
53+
线程、主内存、工作内存三者的交互关系:
54+
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTEyMjA5YjEyZDU3OGEyZWQucG5n?x-oss-process=image/format,png)
55+
![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiM2QzN2MxNTVjZDgyZDgucG5n?x-oss-process=image/format,png)
56+
57+
JVM模型与JMM不是同一层次的内存划分,基本毫无关系的,硬要对应起来,从变量,内存,工作内存的定义来看
58+
- 主内存 《=》Java堆中的对象实例数据部分
59+
- 工作内存 《=》虚拟机栈中的部分区域
60+
61+
从更底层的层次来看:
62+
- 主内存直接对应物理硬件的内存
63+
- 为更好的运行速度,虚拟机(甚至硬件系统的本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存器,因为程序运行时主要访问读写的是工作内存
64+
# 3 内存间同步操作
65+
## 3.1 线程操作的定义
66+
### 操作定义
67+
write要写的变量以及要写的值。
68+
read要读的变量以及可见的写入值(由此,我们可以确定可见的值)。
69+
lock要锁定的管程(监视器monitor)。
70+
unlock要解锁的管程。
71+
外部操作(socket等等..)
72+
启动和终止
73+
### 程序顺序
74+
如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的
75+
76+
本规范只涉及线程间的操作;
77+
一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节
78+
79+
JMM 本身已经定义实现了以下8种操作来完成,且都具备`原子性`
80+
- lock(锁定)
81+
作用于主内存变量,把一个变量标识为一条线程独占的状态
82+
- unlock(解锁)
83+
作用于主内存变量,把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定
84+
unlock之前必须将变量值同步回主内存
85+
- read(读取)
86+
作用于主内存变量,把一个变量的值从主内存传输到工作内存,以便随后的load
87+
- load(载入)
88+
作用于工作内存变量,把read从主内存中得到的变量值放入工作内存的变量副本
89+
- use(使用)
90+
作用于工作内存变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作
91+
- assign(赋值)
92+
作用于工作内存变量,把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
93+
- store(存储)
94+
作用于工作内存变量,把工作内存中一个变量的值传送到主内存,以便随后的write操作使用
95+
- write(写入)
96+
作用于主内存变量,把store操作从工作内存中得到的值放入主内存的变量中
97+
98+
- 把一个变量从主内存`复制`到工作内存
99+
就要顺序执行read和load
100+
101+
- 把变量从工作内存`同步`回主内存
102+
就要顺序地执行store和write操作
103+
104+
JMM只要求上述两个操作必须`按序执行`,而没有保证连续执行
105+
也就是说read/load之间、store/write之间可以插入其它指令
106+
如对主内存中的变量a,b访问时,一种可能出现的顺序是read a->readb->loadb->load a
107+
108+
JMM规定执行上述八种基础操作时必须满足如下
109+
## 3.1 同步规则
110+
だいやまーく 对于监视器 m 的解锁与所有后续操作对于 m 的加锁 `同步`(之前的操作保持可见)
111+
だいやまーく对 volatile变量v的写入,与所有其他线程后续对v的读同步
112+
113+
だいやまーく `启动` 线程的操作与线程中的第一个操作同步
114+
だいやまーく 对于每个属性写入默认值(0, false, null)与每个线程对其进行的操作同步
115+
だいやまーく 线程 T1的最后操作与线程T2发现线程T1已经结束同步。( isAlive ,join可以判断线程是否终结)
116+
だいやまーく 如果线程 T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出*InterruptedException*异常,或者调用*Thread.interrupted**Thread.isInterrupted*
117+
118+
- 不允许read/load、store/write操作之一单独出现
119+
不允许一个变量从主内存读取了但工作内存不接收,或从工作内存发起回写但主内存不接收
120+
- 不允许一个线程丢弃它的最近的assign
121+
即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存
122+
- 新变量只能在主内存"诞生",不允许在工作内存直接使用一个未被初始化(load或assign)的变量
123+
换话说就是一个变量在实施use,store之前,必须先执行过assign和load
124+
- 如果一个变量事先没有被load锁定,则不允许对它执行unlock,也不允许去unlock一个被其它线程锁定的变量
125+
- 对一个变量执行unloack前,必须把此变量同步回主内存中(执行store,write)
126+
127+
> 参考
128+
> - https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.1
Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
1-
近年并发算法领域大多数研究都侧重非阻塞算法,这种算法用底层的原子机器指令代替锁来确保数据在并发访问中的一致性,非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC以及锁,并发数据结构中。
2-
3-
与锁的方案相比,非阻塞算法都要复杂的多,他们在可伸缩性和活跃性上(避免死锁)都有巨大优势。
1+
非阻塞算法,用底层的原子机器指令代替锁,确保数据在并发访问中的一致性。
2+
非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC及锁,并发数据结构中。
43

4+
与锁相比,非阻塞算法复杂的多,在可伸缩性和活跃性上(避免死锁)有巨大优势。
55
非阻塞算法,即多个线程竞争相同的数据时不会发生阻塞,因此能更细粒度的层次上进行协调,而且极大减少调度开销。
66
# 1 锁的劣势
77
独占,可见性是锁要保证的。
88

9-
许多JVM都对非竞争的锁获取和释放做了很多优化,性能很不错了。
10-
11-
但是如果一些线程被挂起然后稍后恢复运行,当线程恢复后还得等待其他线程执行完他们的时间片,才能被调度,所以挂起和恢复线程存在很大的开销,其实很多锁的力度很小的,很简单,如果锁上存在着激烈的竞争,那么多调度开销/工作开销比值就会非常高。
12-
13-
与锁相比volatile是一种更轻量的同步机制,因为使用volatile不会发生上下文切换或者线程调度操作,但是volatile的指明问题就是虽然保证了可见性,但是原子性无法保证,比如i++的字节码就是N行。
9+
许多JVM都对非竞争的锁获取和释放做了很多优化,性能很不错。
10+
但若一些线程被挂起然后稍后恢复运行,当线程恢复后还得等待其他线程执行完他们的时间片,才能被调度,所以挂起和恢复线程存在很大开销。
11+
其实很多锁的粒度很小,很简单,若锁上存在激烈竞争,那么 调度开销/工作开销 比值就会非常高,降低业务吞吐量。
1412

15-
如果一个线程正在等待锁,它不能做任何事情,如果一个线程在持有锁的情况下呗延迟执行了,例如发生了缺页错误,调度延迟,那么就没法执行。如果被阻塞的线程优先级较高,那么就会出现priority invesion的问题,被永久的阻塞下去
13+
而与锁相比,volatile是一种更轻量的同步机制,因为使用volatile不会发生上下文切换或线程调度操作,但volatile的指明问题就是虽然保证了可见性,但是原子性无法保证
1614

15+
- 若一个线程正在等待锁,它不能做任何事情
16+
- 若一个线程在持有锁情况下被延迟执行了,如发生缺页错误,调度延迟,就没法执行
17+
- 若被阻塞的线程优先级较高,就会出现priority invesion问题,被永久阻塞
1718
# 2 硬件对并发的支持
19+
独占锁是悲观锁,对细粒度的操作,更高效的应用是乐观锁,这种方法需要借助**冲突监测机制,来判断更新过程中是否存在来自其他线程的干扰,若存在,则失败重试**
1820

19-
独占锁是悲观所,对于细粒度的操作,更高效的应用是乐观锁,这种方法需要借助**冲突监测机制来判断更新过程中是否存在来自其他线程的干扰,如果存在则失败重试**
20-
21-
几乎所有的现代CPU都有某种形式的原子读-改-写指令,例如compare-and-swap等,JVM就是使用这些指令来实现无锁并发。
22-
21+
几乎所有现代CPU都有某种形式的原子读-改-写指令,如compare-and-swap等,JVM就是使用这些指令来实现无锁并发。
2322
## 2.1 比较并交换
24-
2523
CAS(Compare and set)乐观的技术。Java实现的一个compare and set如下,这是一个模拟底层的示例:
26-
2724
```java
2825
@ThreadSafe
2926
public class SimulatedCAS {
27+
3028
@GuardedBy("this") private int value;
3129

3230
public synchronized int get() {
@@ -47,9 +45,7 @@ public class SimulatedCAS {
4745
== compareAndSwap(expectedValue, newValue));
4846
}
4947
}
50-
5148
```
52-
5349
## 2.2 非阻塞的计数器
5450
```java
5551
public class CasCounter {
@@ -67,52 +63,32 @@ public class CasCounter {
6763
return v + 1;
6864
}
6965
}
70-
7166
```
7267
Java中使用AtomicInteger。
7368

7469
竞争激烈一般时,CAS性能远超基于锁的计数器。看起来他的指令更多,但无需上下文切换和线程挂起,JVM内部的代码路径实际很长,所以反而好些。
7570

76-
但激烈程度较高时,它的开销还是较大,但是你会发生这种激烈程度非常高的情况只是理论,实际生产环境很难遇到。
77-
况且JIT很聪明,这种操作往往能非常大的优化。
71+
但激烈程度较高时,开销还是较大,但会发生这种激烈程度非常高的情况只是理论,实际生产环境很难遇到。况且JIT很聪明,这种操作往往能非常大的优化。
7872

7973
为确保正常更新,可能得将CAS操作放到for循环,从语法结构看,使用**CAS**比使用锁更加复杂,得考虑失败情况(锁会挂起线程,直到恢复)。
8074
但基于**CAS**的原子操作,性能基本超过基于锁的计数器,即使只有很小的竞争或不存在竞争!
8175

8276
在轻度到中度争用情况下,非阻塞算法的性能会超越阻塞算法,因为 CAS 的多数时间都在第一次尝试时就成功,而发生争用时的开销也不涉及**线程挂起****上下文切换**,只多了几个循环迭代。
8377
没有争用的 CAS 要比没有争用的锁轻量得多(因为没有争用的锁涉及 CAS 加上额外的处理,加锁至少需要一个CAS,在有竞争的情况下,需要操作队列,线程挂起,上下文切换),而争用的 CAS 比争用的锁获取涉及更短的延迟。
8478

85-
CAS的缺点是它使用调用者来处理竞争问题,通过重试、回退、放弃,而锁能自动处理竞争问题,例如阻塞。
79+
CAS的缺点是,它使用调用者来处理竞争问题,通过重试、回退、放弃,而锁能自动处理竞争问题,例如阻塞。
8680

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)
9583
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
11088

11189
这也正好跟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)
11692
# 3 原子变量类
11793
J.U.C的AtomicXXX。
11894

@@ -164,18 +140,11 @@ public class CasNumberRange {
164140
}
165141
}
166142
}
167-
168143
```
169-
170-
171144
# 4 非阻塞算法
172-
173145
Lock-free算法,可以实现栈、队列、优先队列或者散列表。
174-
175146
## 4.1 非阻塞的栈
176-
177-
Trebier算法,1986年提出的。
178-
147+
Trebier算法,1986年提出。
179148
```java
180149
public class ConcurrentStack <E> {
181150
AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
@@ -210,13 +179,9 @@ Trebier算法,1986年提出的。
210179
}
211180
}
212181
}
213-
214182
```
215-
216183
## 4.2 非阻塞的链表
217-
218-
有点复杂哦,实际J.U.C的ConcurrentLinkedQueue也是参考了这个由Michael and Scott,1996年实现的算法。
219-
184+
J.U.C的ConcurrentLinkedQueue也是参考这个由Michael and Scott,1996年实现的算法。
220185
```java
221186
public class LinkedQueue <E> {
222187

@@ -257,19 +222,14 @@ public class LinkedQueue <E> {
257222
}
258223
}
259224
}
260-
261225
```
262-
263226
## 4.3 原子域更新
264-
265-
AtomicReferenceFieldUpdater,一个基于反射的工具类,它能对指定类的指定的volatile引用字段进行原子更新。(注意这个字段不能是private的)
227+
AtomicReferenceFieldUpdater,一个基于反射的工具类,能对指定类的指定的volatile引用字段进行原子更新。(该字段不能是private的)
266228

267229
通过调用AtomicReferenceFieldUpdater的静态方法newUpdater就能创建它的实例,该方法要接收三个参数:
268-
269230
* 包含该字段的对象的类
270231
* 将被更新的对象的类
271232
* 将被更新的字段的名称
272-
273233
```java
274234
AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.class,String.class,"name");
275235
Dog dog1=new Dog();
@@ -279,5 +239,4 @@ AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.c
279239
class Dog {
280240
volatile String name="dog1";
281241
}
282-
283242
```

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /