volatile理解

1.什么是volatile?

volatile是JAVA虚拟机提供的轻量级的同步机制。

2.volatile的特性

## 2.1 可见性
volatile的可见性是多线程同步之间的一种通讯机制,在JMM中
规定共享变量(实例域、静态域、数组元素)放在主内存中,非共享
变量(局部变量、方法定义参数和异常处理器参数)放在每个线程
自己的工作内存,每个线程在使用共享变量时会将其拷贝到自己的
工作内存进行操作,但操作完成后不知道何时将其写回主内存,此
时主内存的值未发生改变,其他线程嗅探到主内存的值未发生改变,
导致其他线程操作了脏数据”。
class MyThread extends Thread{
    int number =0;
    @Override
    public void run() {
        System.out.println("我的线程开始执行");
        while (true) {
            /**
            *当嗅探到number的值被改变时跳出循环
            *未使用volatile修饰number,
            *此时main线程修改了number的值,
            *但是对于我的线程并不可见,所以会死循环
            */
            if (number != 0) {
                break;
            }
        }
        System.out.println("我的线程执行结束");
    }
}
/**
 *  volatile实例类
 *   number未使用volatile修饰,多线程之间不具有可见性
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//创建我的线程
        myThread.start();
        /**
         *  为防止主线程跑在 我的线程 之前,
         *  即防止我的线程还未开始时主线程已经执行完毕,将number的值该为100后我的线程才跑
         */
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在程序执行1秒后这段话会被执行,给 我的线程 足够的启动时间
        myThread.number=100;
        System.out.println(Thread.currentThread().getName()+" number="+myThread.number);
    }
}

不可见运行代码

但对于加了volatile关键字的变量进行操作,JVM会向处理器发
送一条Lock前缀的指令,将这个变量所在的缓存行的数据写回主
内存,其他线程会嗅探在总线上的数据来检查自己的数据是否过期
当发现自己的缓存行对应的内存地址被改变就会将当前线程中的
变量置为无效,当线程需要对该变量进行操作时,会重新从主内存
中读取变量最新的值,在进行操作,这样就实现了多线程之间的通
信。
class MyThread extends Thread{
    volatile int number =0;
    @Override
    public void run() {
        System.out.println("我的线程开始执行");
        while (true) {
            /**
             * 当嗅探到number的值被改变时跳出循环
             * number被volatile修饰,
             * 所以当main线程修改了number的值时
             * 会及时通知 我的线程,我的线程从主内存
             * 中获取最新的值后跳出循环
             */
            if (number != 0) {
                break;
            }
        }
        System.out.println("我的线程执行结束");
    }
}
/**
 *  volatile实例类
 *   number使用volatile修饰,多线程之间具有可见性,实现多线程间的通信
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//创建我的线程
        myThread.start();
        /**
         *  为防止主线程跑在 我的线程 之前,
         *  即防止我的线程还未开始时主线程已经执行完毕,将number的值该为100后我的线程才跑
         */
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在程序执行1秒后这段话会被执行,给 我的线程 足够的启动时间
        myThread.number=100;
        System.out.println(Thread.currentThread().getName()+" number="+myThread.number);
    }
}

可见运行结果

## 2.2 禁止指令重排序
指令重排序是指JAVA语言规定JVM线程内部维持顺序化语义(满足
As-If-Serial语义和Happens-Before原则),即只要程序执
行的最终结果与其顺序化执行的结果相同,JVM允许指令执行的顺
序与代码逻辑书写的顺序可以不一致,这样的过程叫做指令重排序。
指令重排序的意义是指令更加符合CPU执行的特性,提高执行效率。

指令重排序

但是在多线程中某些时候指令重排序会让程序出现预期之外的结
果(如DCL中会造成线程不安全),所以我们有时候需要禁止JVM做
这种指令重排序的优化。
volatile通过加入内存屏障防止这种指令重排,用volatile修
饰的变量在写操作时在volatile写的前后分别加入StoreStore
屏障和StoreLoad屏障防止写操作时发生指令重排,在volatile
读操作之后加入LoadLoad屏障和LoadStore屏障防止读操作时
发生指令重排序。



## 2.3 不保证原子性
原子性是指该操作是一个完整的整体,是不可被分割的,同一时
刻只能有一个线程对它进行操作。
被volatile修饰的对象操作不能保证原子性,即该操作会因为
线程调度器而被中断操作。
class MyData{
     volatile int number=0;
    //该方法会使number+1
    public void numberPlusPlus(){ 
        number++;
    }
}
public class VolatileByAtomicDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();
        /**
         * 循环创建10000个线程,都调用numberPlusPlus使number+1
         */
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                //双重for为了增加线程被抢占的概率
                for (int j = 1; j <= 1000; j++) {
                    myData.numberPlusPlus();
                }
            },String.valueOf(i)).start();

        }
        /**
         * java运行时会自动调起main线程和GC线程
         * Thread.activeCount()>2说明自己创建的线程还有未执行结束的,
         * 等待所有的自创建线程执行结束
         */
        while(Thread.activeCount() > 2){
            Thread.yield();
        }
        /**
         *  预期在循环结束后numberPlusPlus被调用10000次,该语句打印10000
         *  实际结果小于10000(有可能等于10000),原因时number++在执行时被拆分为3步
         *  1.读取number的值    2.执行number+1    3.将操作结果写回
         *  因为无法保证原子性,a线程在执行number++的第二步后,b线程抢占了资源
         *  此时a线程已经进行了+1操作但是因为b此时加塞抢占,a线程挂起,并未将值写回
         *  b线程读取到的值与a线程相同,b线程执行完number++操作后释放资源,
         *  a线程从挂起状态变为执行,但因为挂起前a线程只剩下写回操作,执行后直接写回,
         *  导致b线程的操作被覆盖,丢失操作数据
         */
        System.out.println(myData.number);
    }

}


不保证原子性
i++字节码

如果想要保证原子性则需要在number++时加上synchronized
或者int类型的number换为能够保证原子性的AtomicInteger
类型变量。
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值