volatile关键字

并发编程三要素

  • 原子性

    一个或多个操作要么全部执行成功要么全部执行失败,期间不中断,不存在上下文切换,线程切换会带来原子性问题

    int a=1;是原子操作

    而num++;不是原子操作

    解决:

    • 使用synchronize或Lock(ReentrantLock)

      public class LockTest {
          private int a=0;
          
          Lock lock=new ReentrantLock();
          
          //使用lock,每个对象都是有锁,只有获得锁才能进行对应操作
          public void add(){
              lock.lock();
              try {
                  a++;
              }finally {
                  lock.unlock();
              }
          }
          //锁的是整个方法
          public synchronized void  add2(){
              a++;
          }
      }
      
      

      思想:把一个方法或代码看做一个整体,保证是一个不可分割的整体。

  • 可见性

    见下面volatile的分析

  • 有序性

    程序的执行按代码顺序执行,jvm或cpu可能为了优化运行效率对代码进行重排序,但不会影响效果。但在多线程环境下就会出现问题。

volatile

解释

volatile关键字和synchronize的区别

  • volatile是轻量级的synchronize,保证了共享变量的可见性,如果值发生了变化,其他线程会立刻感知到,避免了脏读的现象,还能保证有序性,但不能保证原子性

    能够禁止指令重排

  • synchronize保证了可见性,也保证原子性(优化越来越多)。

    其有序性是由“一个变量同一时刻只允许一条线程对其进行lock操作”这条规则获取

可见性

指一个线程修改了共享变量后,其他线程可以立马感知到,这也就可以避免脏读的发生。
在这里插入图片描述

如线程A将x修改为1后,没有立即写回到主内存,而线程B仍然使用旧值0,但当x被volatile修饰后,修改的变量值会被立即写回到主内存,这样线程B就可以读取到该变量修改后的值,即共享变量的可见性。

public class Test {
    private static volatile   int x=1;
    
    public static void modify(){
        x=0;
    }
    public static void main(String[] args) {
        new Thread(()->{
           while (x==1){

       	 }
       	 System.out.println("x不为1了");
        },"A").start();
        try {
            Thread.sleep(3000);
            modify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main over");
    }
}

如上,睡眠3s后调用modify修改x的值,如果不使用volatile,x的修改对线程A不可见,程序永远不会结束;而如果用volatile,变量x的修改对A可见,修改后会立即执行完毕A线程。

重排序

重排序是从其他线程的角度看的。

分为编译期重排和运行时重排。

JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)

如对于:

int a=1;
int b=2;
int c=a*b;

jvm执行时不一定是按照a=1然后b=1的顺序,也可能是先给b赋值,再给a赋值,但不会影响最终c的结果,而这在多线程环境下出现一些问题。

比如我们一个线程在修改一些用户信息,修改完毕后给另一个线程发一个信号,告诉它去用最新的用户信息做统计,如果不对信号标志使用volatile,则可能在修改完信息前,该标志被置为true,也就是说信号标志被重排到了修改中或修改前,这样另一个线程收到信号后,未必得到真正修改完毕的用户信息。

内存屏障

volatile基于内存屏障解决可见性和重排序。https://www.jianshu.com/p/ef8de88b1343

内存屏障是屏障指令,使CPU对屏障指令之前和之后的内存操作执行结果的一种约束,其实就是强制性的将最新的值刷新到主存

happens-before

先行发生原则,volatile的内存可见性就体现了该原则。

线程A:

int k=1

线程B:

int j=k;

线程C:

k=2;

八大原则:

  1. 程序次序规则

    在同一个线程中,书写在前面的操作happen-before后面的操作。

  2. 锁规则

同一个锁的unlock发生在此锁的lock操作前

  1. volatile变量规则

  2. 线程启动规则

    同一个线程的start()在该线程的其他操作前执行

  3. 线程中断规则

    对线程的intrerrupt的调用在被中断线程检测到中断发送前

  4. 线程终止规则

    线程所有其他操作在线程终止前结束

  5. 对象创建规则

    一个对象的初始化先于其finalize方法

  6. 传递性

    a在b前happen-before,b在c前happen-before,那么a 就happen-before C。

场景:
  1. 不能修饰写入操作依赖当前值的变量,如:num++、num=num+1,JVM字节码层面不止一部,要求常经理本身是原子的

  2. 由于禁止了指令重排,所以JVM相关的优化没了,效率会偏弱

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值