Java内存模型与线程

Java内存模型与线程

Java内存模型

上图的8个操作都是原子性的

1.Java内存模型规定所有变量(类变量和实例变量)都需要保存在主内存中

2.每条线程都有自己的工作内存,工作内存中保存了对主内存某些变量的副本,线程只能对自己的工作内存中的变量副本进行读取或写入操作,不允许直接访问主内存。

3.不同线程之间无法访问对方的工作内存中的变量,线程间变量的通信通过主内存进行。

4.只要按照上述的8条原子操作的顺序对变量进行写入或读取,那么在并发环境下每个变量都是线程安全的。

volatile关键字

当一个变量用关键字volatile修饰后,具有2个特性:

  1. 保证此变量对所有线程的可见性

  2. 防止指令重排序

volatile并不保证原子性

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
/**
* @author Zeng
* @date 2020/4/10 15:51
*/
public class VolatileTest {

public static volatile int race = 0;

public static void increase() {
race++;
}

private static final int THREADS_COUNT = 20;

public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(race);
}

}
  • 执行上面的代码理应得到200000,但实际上却小于这个值。

问题出于race++,它并不是一个原子操作,使用javap -c VolatileTest.class得到反编译的字节码文件

increase()方法中race++被拆分成四个步骤执行,有可能会发生下图的情况,线程B的每个步骤都刚好在线程A之后。就会发现值加少的情况。

volatile可以保证线程安全的两个条件

  1. 运算结果不依赖变量的当前值,或者能够确保同一时刻只有单一的线程修改变量的值
  2. 变量不需要与其它状态变量参与不变性约束

例如下面的shutdownRequested变量值不依赖任何值,不参与不变性约束,此时volatile控制并发可以保证线程安全

1
2
3
4
5
6
7
8
9
volatile boolean shutdownRequested;
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
//work
}
}

防止指令重排序

什么是指令重排?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test{
private int A;
private int B;
public void setA(){
A = 1;
}
public int readA() {
return A;
}
}
//线程A
dosomethingA();
setA();
//线程B
while(readA() != 1){
Thread.sleep(100);
}
doSomethingBAfterThreadAFinished();

setA()有可能被提前到dosomethingA()之前执行,导致ThreadBThreadA还没完成动作时就已经开始执行自己的方法。

巨人的肩膀:

https://juejin.im/post/5e17a5735188254c0a0409f1#heading-3

0%