JUC——volatile的三个特性

volatile概念:轻量级的同步机制

三个特性

  1. 保证可见效(其实 就是 及时通知)
  2. 不保证原子性
  3. 禁止指令重排

可见性

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的时候,主内存丢失某个线程写过来的数据。

如何解决原子性?

  1. 加synchronized (太重)
  2. 使用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关键字,禁止指令重排
 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值