volatile
一个小程序
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App1.java
说明:
每一个线程从内存中读取一个变量,读到的都是一个副本。在当前线程修改变量的值,只会影响到当前线程从内存中读取到的副本的值,并不会影响到内存中本身的变量值。线程之间变量修改是不可见的。如果想要线程之间可见,需要给该变量加上volatile
关键字,或者使用synchronized
关键字触发 内存刷新机制。注意:
如果使用volatile
去修饰一个对象。该对象内部的变量依然是不可见的。
三层缓存
程序从内存中读取数据,每次会读取一行数据,这个数据行的大小是64字节。例如,有两个线程读取不通的数据,读取到的数据又存在公共的部分,因此在两个线程的缓存之间会相互影响,最后导致性能没有充分的释放。 因此可以在需要读取的数据加上 @Contended
注解 并且 在JVM的启动参数上添加-XX:-RestrictContended
,来确保多个线程读取到的变量不在同一个缓存行上。
注意:
volatile和缓存行的概念没有半毛钱关系
线程的乱序执行
触发条件:
- 只要不影响线程的最终一致性。
有序性代码实验:
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App3.java
有序性 和 可见性 实验代码:
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App4.java
汇编指令乱序问题:
比如在main方法中new了一个对象 ,它的会变指令通过jclasslib插件,可以观察到这行代码最后被翻译成5条汇编指令
public class T {
private int m=8;
public T() {
}
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
}
public static void main(String[] args) {
T t= new T();
}
最终获得到汇编指令:
new
指令 意思是申请内存invokespecial
指令为调用类的构造方法。在调用该指令之前,对象还未完全初始化,对象中m
变量还只是默认值,调用该指令之后,m
的值才是8astore_1
为把内存中的对象和栈中的t
做关联
一个存在问题的实验小程序:
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App7.java
原子性
原子性问题小程序:
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App8.java
上锁的本质就是把 并发编程序列化。
实验代码:
https://gitee.com/zxj8524210/my-thread-test/blob/master/src/main/java/org/example/App9.java
volatile 和 synchronized的区别
volatile保障了 有序性和可见性,不保障原子性。
synchronized 保障了 原子性和可见性,不保障有序性。