一. Volatile
1. volatile是Java虚拟机提供的轻量级的同步机制
其特性:
1.保证可见性
2.不保证原子性
3.禁止指令重排
2. 保证可见性
当多个线程访问主内存同一个变量时,一个线程修改了这个变量的值,
其他线程能够立即看到修改的值,并重新从主内存中取出修改后的变量。
代码演示:
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
//Lamda表达式
//第1个线程
new Thread(() -> {
//模拟线程进入主内存拷贝变量副本
System.out.println(Thread.currentThread().getName() + "\t come in");
//休眠3秒,给其他线程去读主内存的变量
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改变量值
myData.updateNum();
System.out.println(Thread.currentThread().getName() + "\t update num value: " + myData.num);
}, "AAA").start();
//第2个线程 main线程
while (myData.num == 0) {
//如果num不变,这个循环会一值执行,死循环,不会打印下面的那句话
//如果没有人来告诉这个线程num已经被改了,他会一值执行下去
//只有在num上加了Volatile线程才会有可见性,才会通知他去取新的num值,才退出循环
//可见性也可以理解成是一种即时通信机制
}
System.out.println(Thread.currentThread().getName() + "\t mission is over num: " + myData.num);
}
}
class MyData {
//volatile修饰主内存变量num
volatile int num = 0; //模拟主内存变量
//模拟线程的工作内存修改变量
public void updateNum() {
this.num = 60;
}
}
加了Volatile关键字的运行效果:
没加Volatile关键字的运行效果(死循环):
3. 不保证原子性
原子性:简称完整性,即某个线程正在做某个任务时,
中间不可以被影响,要么同时成功,要么同时失败。
代码演示:
public class VolatileDemo02 {
public static void main(String[] args) {
MyData01 myData = new MyData01();
//Lamda表达式
for (int i = 1; i <= 20; i++) {
//20个线程
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
//每个线程执行加1000次
myData.addNum();
}
}, String.valueOf(i)).start();
}
//这个循环的作用就是等待上面线程全部执行完毕后主线程在动手
while (Thread.activeCount() > 2) {
Thread.yield();
}
//执行完毕,使用main线程来输出结果
System.out.println(Thread.currentThread().getName() + "\t finally num: " + myData.num);
}
}
class MyData01 {
//volatile修饰主内存变量num
volatile int num = 0;
public void addNum() {
num++;
}
}
运行结果:
数值随机,不能保证原子性。
4. 禁止指令重排
指令重排:在计算机执行程序时,为了提高性能,
编译器和处理器常常会对指令做重排,一般分以下三种:
处理器在进行重排顺序是必须要考虑指令之间的数据依赖性
Volatile是怎么禁止指令重排的呢?
内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:
1. 保证特定操作的执行顺序
2. 保证某些变量的内存可见性(利用该特性实现Volatile的内存可见性)
3. 由于编译器和处理器都能执行指令重排优化,
如果在其中插入Memory Barrier则会告诉编译器和CPU,
不管什么指令都不能和这条Memory Barrier指令重排顺序,
内存屏障另外一个作用是强制刷出各种CPU的缓存数据,
因此任何CPU上的线程都能读取到这些数据的最新版本。