ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

volatile

2019-11-08 14:55:10  阅读:247  来源: 互联网

标签:Thread num 线程 内存 volatile public


volatile是什么?

volatile是java中的关键字,也是java虚拟机提供的轻量级的同步机制(乞丐版的synchronize)。

 

volatile的三大特性

1.可见性

2.不保证原子性

3.禁止指令重排序

 

为什么说volatile是轻量级的同步机制?

因为大多数多线程开发都需要遵守JMM的三大特性:

1.可见性

2.原子性

3.有序性

而volatile只保证可见性和禁止指令重排序(有序性)所以说是轻量级的同步机制。

可见性

由于JVM运行程序的实体是线程,而每个线程创建是JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程私有数据区域,而java内存模型中规定所有变量都存在主内存,主内存是共享区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,如何对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存完成。

例:

现在这里有三个线程:线程1,线程2,线程3想要去改主内存的age它不是直接去主内存修改,而是先把主内存的age拷贝到直接的工作内存,然后在自己的工作内存进行修改,再写会主内存,比如这里线程1把25改成37再写回去,但是线程2,和线程3并不知道主内存的值从25改成37,所以我们必须要有一种机制,只要有一个线程修改了自己工作内存的值并写回给主内存以后,要及时通知其他线程,这样及时通知这种情况JMM内存模型中第一个重要特性:可见性。

代码验证:

没有用volatile修饰

class Demo {
int num = 0;

public void add()
{
this.num = 60;
}
}

/**
* 验证valatile的可见性
*/
public class Test {
public static void main(String[] args) {
Demo d=new Demo();// 线程操作资源类
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in:"+d.num);
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e) {
e.printStackTrace();
}
d.add();
System.out.println(Thread.currentThread().getName()+"\t update num value:"+d.num);
},"线程1").start();
while(d.num == 0)
{
// main线程一直等待循环,直到num值不再等于0.
}
System.out.println(Thread.currentThread().getName()+"\t over:"+d.num);
}
}

运行结果:

我们可以看到线程1把num的值已经改成60了,也就是说线程1已经把60写回主内存了,但是程序没有停止,那就是说main线程不知道值已经改为60了,但没有人通知main线程,main线程还在那做一个安静的美男子。

加上volatile:

class Demo {
volatile int num = 0;

public void add()
{
this.num = 60;
}
}

/**
* 验证valatile的可见性
*/
public class Test {
public static void main(String[] args) {
Demo d=new Demo();// 线程操作资源类
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t come in:"+d.num);
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e) {
e.printStackTrace();
}
d.add();
System.out.println(Thread.currentThread().getName()+"\t update num value:"+d.num);
},"线程1").start();
while(d.num == 0)
{
// main线程一直等待循环,直到num值不再等于0.
}
System.out.println(Thread.currentThread().getName()+"\t over:"+d.num);
}
}

运行结果:

只要有一个线程修改了主内存的值,马上写回去的时候,只要加了volatile关键字的变量,其他线程迅速受到通知,拿到主内存最新的值。

不保证原子性

原子性值的是什么?

不可分割,完整性,也就是说某个线程正在做某个具体业务时,中间不可以被加塞或被分割,需要整体完整,要么同时成功,要么同时失败。

代码验证:

class Demo {
volatile int num = 0;
// 此时num前面是加了volatile关键字修饰的,volatile不保证原子性
public void add()
{
num++;
}
}
/**
* 验证valatile不保证原子性
*/
public class Test {
public static void main(String[] args) {
Demo d=new Demo();// 线程操作资源类
for(int i = 0;i <20; i++){
new Thread(() -> {
for (int j = 0; j <1000 ; j++) {
d.add();
}
},String.valueOf(i)).start();
}
while(Thread.activeCount() > 2)
{
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"\t num value:"+d.num);
}
}

运行结果:

这里有20个线程,每个线程执行1000次,按理来说最终运行结果应该是20000才对,但是每次的运行结果都不一样。

volatile不保证原子性的解释

假设这里有三个线程操作主内存数据,但不能直接操作主内存,要把它拷贝回自己的工作空间,所谓的变量的副本拷贝,这里每个线程工作内存都为0,这时候都在自己工作内存进行累加线程1工作内存值为1,线程2工作内存也为1,线程3工作内存也是1,只要线程的工作内存操作完成后会写会主内存。假设说线程1和线程2同时读到了这时候它们的工作内存值都为0,正常情况下线程1先写回去,主内存值为1了,线程2再拿到这个值进行累加再写回来,这个时候主内存值应该为2了,但是由于多线程竞争和调度的关系,某一个时间段线程1和线程2的值都为0,各自在自己的工作内存进行累加,准备把这个1写回去,将会出现在某一时间段,线程1将要把1写回去的时候突然被挂起了,线程2再刷的一下把它工作内存的1写回去,然后线程2再通知其他线程,其他线程还没有反应过来的情况下,被挂起的线程1马上也写回去,本来线程1和线程2应该加两次,但是会出现线程1的值1把线程2写回去的值1覆盖了,出现了丢失数据的一次。这就是数值为什么达不到20000的原因,出现丢失写值的情况。

 禁止指令重排

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分以下3中:

源代码——》编译器优化的重排——》指令并行的重排——》内存系统的重排——》最终执行的指令

在单线程环境里确保程序最终执行结果和代码顺序执行的结果一致。

处理器在进行重排序时要考虑指令之间的数据依赖性。

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

代码:

int a = 0;
boolean flag = false;
public void method01()
{
a = 1;
flag = true;
}
// 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。
public void method02()
{
if(flag)
{
a = a + 5;
System.out.println("return value:"+a);
}
}

volatile禁止指令重排总结

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:

1.保证特定操作的执行顺序;

2.保证某些变量的内存可见性(利用改特性实现volatile的内存可见性)。

由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

 

标签:Thread,num,线程,内存,volatile,public
来源: https://www.cnblogs.com/chengcanhua/p/11820171.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有