1、保证可见性
volatile
通过JMM
实现数据的可见性。
JMM(Java内存模型): 线程将变量从主内存中拷贝到工作内存,修改完成后将值写到主内存中,并且会被其他线程感知到变量被修改,其他线程将重新从主内存中获取最新值。
主内存中的变量所有线程都能访问。
总结:被volatile修饰的变量能够保证每个线程获取该变量的最新值,从而避免出现数据脏读的现象。
使用说明:
在main中开启5个线程,并且处于while循环状态,如果加上valotile
修饰词,2秒后main线程修改num值,其他5个线程则会执行while之后的语句,表示每个线程都感知到了valotile
修饰变量值的变化。
代码说明:
public class Test {
volatile private static int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (num == 0) {
}
System.out.println("线程:" + Thread.currentThread().getName() + "获取最新值");
}, "test-" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
num = 1;
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果:
2、不保证原子性
volatile
不能保证变量的原子性。
比如:n++在字节码中会分为多步执行,因为在并发情况下,多个线程会同时拿到相同的初始化值,并且同时往主内存写入相同的值,所以再累加时会缺失某个数值。
使用说明:
通过运行结果可以看出,最后的value值小于20000,则说明volatile
不保证原子性。
代码说明:
public class Test {
volatile private static int num = 0;
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
num++;
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(2);
System.out.println("value:" + num);
} catch (Exception e) {
e.printStackTrace();
}
}
}
效果:
可能只执行一次value不会小于小于20000,需要多执行几次,比如结果如下:
3、禁止指令重排
执行程序时,为了提高性能,编译器和处理器常常会对指令(字节码)做重排序,分3种类型:
实例代码如下:
int a = 1; // 1
int b = 2; // 2
int c = a + b; // 3
编译器和处理器不会对存在数据依赖关系的操作做重排序。
a和b不存在依赖关系,1、2可以进行重排序;c依赖 a和b,所以3必须在1、2的后面。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/18093.html