Volatile
并发编程中的三个概念
谈一谈你对Volatile的理解:
https://blog.csdn.net/qq_44739500/article/details/108711929
https://www.cnblogs.com/dolphin0520/p/3920373.html
1:保持可见性
通过前面对JMM的介绍,我们知道各个线程对主内存中共享变量的操作都是各个线程各自拷贝变量到自己的工作内存操作后,再写回主内存中的。这就可能存在一个线程AAA修改了共享变量X的值还未写回主内存中时,另外一个线程BBB又对内存中的一个共享变量X进行操作,但此时A线程工作内存中的共享变量的值,对线程B来说是不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。
既然出现了可见性的问题那么如何解决?
1)加锁
那么为什么加锁可以解决可见性的问题?
源自于jmm模型对于同步的规定:
- 线程加锁前,必须读取主内存的最新值到自己的工作内存。
- 线程解锁前,必须把共享变量的值刷新回主内存。
- 加锁和解锁是同一把锁。
这样就导致获取不到锁的线程就会阻塞等待,所以变量的值永远就是最新的。
2)volatile修饰共享变量
每个线程操作共享数据的时候会重新去主内存拉取共享变量的值到自己的工作内存,如果他操作了数据并且写回到主内存,其他已经读取了该变量的线程的变量副本就会失效了,需要从新去主内存去读取共享变量的值。
package com.baidu.tVolatile;
import org.omg.CORBA.TIMEOUT;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//不加volatile 会发生死循环
//加volatile 可以保持可见性
private static volatile int num = 0;
public static void main(String[] args) { //main 线程
new Thread(()->{ // 线程 1;对主内存的变化时不知道的
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num =1;
System.out.println(num);
}
}
2:不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割。要么成功,要么失败。
volatile 不能保证原子性
number++在多线程下是非线程安全的,如何保证原子性?
- 加锁:ReentrainLock或者Synchronized
- 原子类:AtomicInteger等原子类进行操作。
JMMDemo02 :发现volatile无法保证原子性
package com.baidu.tVolatile;
//不保证原子性
public class JMMDemo02 {
// volatile 不能保证原子性
private volatile static int num = 0;
public static void add(){
num++; // 不是一个原子性操作;
}
public static void main(String[] args) {
for (int i = 0; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //main GC
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加Lock锁,和Synchronized,怎样保证原子性;
使用原子类,解决 保证原子性
package com.baidu.tVolatile;
import java.util.concurrent.atomic.AtomicInteger;
//不保证原子性
public class JMMDemo02 {
//原子类的int
private static AtomicInteger num = new AtomicInteger();
public static void add(){
//num++; // 不是一个原子性操作;
num.getAndIncrement();// AtomicInteger 的加一方法;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //main GC
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
这些类的底层,都适合操作系统挂钩的!在内存中修改值,Unsafe类是一个特殊的存在;
3:指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那样进行执行。
源代码—>编译器优化的重排—>指令并行也可能会重拍—>内存系统和也会重排—>执行。
处理器在进行指令重排的时候,考虑数据之间的依赖性。
Int x = 1; //1
Int y = 2; //2
X = x +5; // 3
Y = x*x; //4
我们所期望的执行顺序:1234 ,但是可能执行的时候会变成2134 1324
可不可能是:4123! 不行
volatile可以避免指令重排
内存屏障:CPU指令。作用:
1:保证特定的操作执行顺序;
2:可以保证某些变量的内存可见性。(利用这些特征保证了Volatile的可见性
Volatile可以保持可见性 ,不能保证原子性,由于内存屏障,可以保证指重排的现象产生!
问题:Volatile在哪个地方使用的最多;
答:单例模式;