volatile 相关理解
volatile概念:轻量级的同步机制
三个特性
- 保证可见效(其实 就是 及时通知)
- 不保证原子性
- 禁止指令重排
可见性
JMM:java内存模型
基于JVM运行程序的实体是线程,而jvm创建每个线程时候都会为其创建一块单独的工作内存,工作内存是每个线程私有的数据区域,而java内存模型中规定所有的变量都存储在主内存,主内存是共享内存区域,所有线程均可访问,但线程对变量的操作必须在工作内存中进行。 首先将变量从主内存中拷贝到自己的工作内存,然后对其进行修改,修改完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中存储的是主内存变量的一个副本拷贝,因此不同的线程间无法访问对方的工作内存。线程间的通讯必须靠主内存来完成。
JVMM规则要求代码保证:可见性、原子性、有序性。volatile能保证两个(除了原子性)
代码演示
class MyData{
volatile int number =0;
public void addData(){
this.number = 60;
}
}
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" come in" );
try {
TimeUnit.SECONDS.sleep( 3 );
} catch (InterruptedException e) {
e.printStackTrace( );
}
myData.addData();
System.out.println(Thread.currentThread().getName()+" update the data value:" +myData.number );
}
} ,"AA").start( );
while( myData.number == 0 ){
}
System.out.println(Thread.currentThread().getName()+"...mission is over,main get the number value: " +myData.number);
}
}
//打印结果
Connected to the target VM, address: '127.0.0.1:52751', transport: 'socket'
AA come in
AA update the data value:60
main...mission is over,main get the number value: 60
总结:当MyData的number变量没有加volatile时,不会打印出最后一句“main,。。。。”.AA线程修改number为60,通知到主内存,但主内存并不会通知main线程变量值一修改,所以main方法一直无法拿到number的新值,会在while方法中一直等待。而volatile 会“及时通知”到其他线程变量的修改,因而保证了可见性
原子性
不可分割、完整性,即某个线程在处理具体业务时候,中间不可被加塞或者分割,需要整体完整,要不同时成功,要么同时失败
class MyData2{
volatile int number =0;
public void addPlus(){
number++;
}
}
public class VolatileDemo2 {
public static void main(String[] args) {
MyData2 myData = new MyData2();
for (int i = 0; i < 20; i++) {
new Thread( () -> {
for (int j = 0; j <1000 ; j++) {
myData.addPlus();
}
},String.valueOf( i ) ).start();
}
/// 需要等待20个线程全部执行完成后,在用main线程取得最终的结果。为什么要判断大于2呢?因为后台有2个线程,一个main线程,一个gc线程,如果大于2,说明还有其他线程存在
while( Thread.activeCount()> 2 ){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"...mission is over,main get the number value: " +myData.number);
}
}
//打印结果:main...mission is over,main get the number value: 19210
结论:如果volatile能保证原子性,那么main拿到的结果应该是201000=20000,而实际值基础上都是小于20000的,说明线程在计算过程中有数据丢失的情况,没有保证原子性。*
number++在多线程下是非线程安全的,如何不加synchronized解决?
为什么number++不安全
因为number++需要依赖它之前的值,当多个线程出现同时修改number的时候,主内存丢失某个线程写过来的数据。
如何解决原子性?
- 加synchronized (太重)
- 使用juc下的原子类 AotimicInteger
package volilateTest;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by yangting on 2020/7/15
* 验证volatie的非原子性——解决方案,使用Atomic类
*/
class MyData3{
AtomicInteger atomicInteger = new AtomicInteger( );
public void addAtomic(){
atomicInteger.getAndIncrement();
}
}
public class VolatileDemo3 {
public static void main(String[] args) {
MyData3 myData = new MyData3();
for (int i = 0; i < 20; i++) {
new Thread( () -> {
for (int j = 0; j <1000 ; j++) {
myData.addAtomic();
}
},String.valueOf( i ) ).start();
}
/// 需要等待20个线程全部执行完成后,在用main线程取得最终的结果。为什么要判断大于2呢?因为后台有2个线程,一个main线程,一个gc线程,如果大于2,说明还有其他线程存在
while( Thread.activeCount()> 2 ){
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+"...mission is over,main get the number value: " +myData.atomicInteger);
}
}
//打印结果:main...mission is over,main get the number value: 20000
禁止指令重排
概念:多线程环境中线程交替执行,由于编译器优化重排存在,使得两个线程中使用的变量能否保证一致性是无法确定的,导致结果无法预测。
class myData{
int a =0;
boolean flag = false;
public void method1(){
a=1;
flag = true;
}
public void method2(){
if(flag){
a= a+5 ;
System.out.println("return value :" +a );
}
}
}
public class VolatileDemo4 {
}
/*
假如多个线程去执行myDate的两个方法,指令未出现重排的情况是 a=1 后 再执行flag =true,然后method2中 判断flag=true a =1+5 =6;
而当指令发生重排,导致flag=ture 在 a=1前面执行,那么最终结果 a=0+5=5.
这样导致的结果无法预测,无法保证结果一致性。
解决方案:加volatile关键字,禁止指令重排
*/