JMM
对volatile的理解
volatile 是java虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
那么可见性与JMM相关
什么是JMM
Java内存模型,不存在的东西,是一个概念,是一个约定
- 线程加锁前,必须读取主存中最新值到工作内存中
- 线程解锁前,必须立刻将共享变量刷回到主存中
- 加锁与解锁必须是同一把锁
解释:线程操作变量时,
1、是先将主存中的数据复制到自己的工作内存中
2、对自己内存的内容进行操作
3、操作完成后解锁时,需要将更新的内容,同步更新到主存中
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
会存在一个问题,
-
如果线程A读取了数据,但是没有停止,没有将数据刷新到主存中
-
线程B读取了数据,并且一下就停止了,然后将数据刷新回了
-
这个时候主存中的数据已经刷新了,但是工作内存中并没有得到最新的数据
所以volatile可以解决的,他可以让主存的数据变成可见性,也就是主存的数据如果发生了改变,就可以将数据直接刷新回去
上述就是保证了可见性
不保证原子性
-
原子性操作是像原子一样,要么一起成功,要么一起失败
-
但是volatile在变量上时,是不保证的。
-
如何理解这句话
-
举例
-
class X { private static int num; public static void add(){ num++; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ add(); }).start(); } System.out.println(num); } }
-
上述代码一看就知道是存在数据不安全的情况
-
那么在这里底层上num++,并不是一个操作,而是由多个步骤操作实现的num++;
-
所以当出现了多个线程操作同一个变量时,就存在了不安全的行为,这几个步骤不能保证原子性
-
而此时,添加了volatile也是没有办法保证原子性的
-
注意:如果你把sout写到了里面,那么就会保证原子性,因为sout是加锁的
-
如何解决这个问题
使用
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这个包下的内容
public static void add(){
num.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(()->{
add();
}).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(num);
}
- 为什么要使用线程礼让
- 这个线程如果都跑完了,就剩下main线程与gcc垃圾线程
- 所以没有跑完就一直礼让
- 保证最后执行sout
- 也可以使用sleep休眠
- 不然上面的线程还没有执行完,就输出了
- 注意上面的问题哦
禁止指令重排
什么是指令重排
int x = 1;
int y = 2;
x = x + 2;
y = y + 2;
- 这里,使用 第一个指令与第二个指令重新排队,对程序是没有影响的
会出现的问题
x , y ,a ,b = 0
线程A | 线程B |
---|---|
x = a | y = b |
b = 1 | a = 2 |
正常线程输出是
x = 0 b = 1 ; y = 1 a = 2
但是如果执行顺序不同,使得指令重排,
结果可能是
x = 2 b = 1 ; y = 1 a = 2;
那么volatile会禁止重新排列
为什么会出现禁止重排,因为内存屏障会
内存屏障禁止重排
保证数据可见性
- volatile会增加数据禁止重排