juc-3-volatile

目录

1 多线程下变量的不可见性及解决方案

 2 不可见性解决方案

2.1 加锁方式解决

2.2 使用volatile关键字

2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

3 volatile不保证原子性以及解决方案

3.1 案例 volatile 验证不是原子性

3.2  讲解count++和count-- 不是原子操作

3.3 解决volatile 原子性问题

4  volatile 禁止指令重排序

4  实战(懒汉式 双重检查+volatile )

 5  场景(只适合纯赋值 )


总结

  1. 线程间的通讯有不可见性。
  2. volatile能使线程通讯可见,但是不保证原子性。(解决不了库存超卖) 
  3. volatile 禁止指令重排序

1 多线程下变量的不可见性及解决方案

在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改共享变量后,另一个线程,不能直接看到线程修改后的变量的最新值。

 案例 (不可见性)

个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。

       2 开启线程,将 主内存中 flag = false; 读取到副本中去,并作修改 flag = true; 

      3 向主内存中刷新为 flag = true; 

      4  cup 去执行 main线程 ,将 主内存中 flag =true; 读取到main线程的 副本中去。打印日志,"主线程获取flag"

public class volatileDemo implements Runnable {
    private boolean flag = false;

    @Override
    public void run() {
        // 2子线程已经将flag变成true了
        flag = true;
        System.out.println(Thread.currentThread().getName()+"flag="+flag);
    }
    public boolean getFlag(){
        return flag;
    }
    public static void main(String[] args) {
        //同一个对象,同一变量 a 锁的是同一个对象,线程安全的
        volatileDemo thread = new volatileDemo();
        // 1 子线程开始执行
        new Thread(thread).start();
        while (true){
           // 3 获取flag时已经由子线程将它变成 true了
           if(thread.getFlag()){
               System.out.println("主线程获取flag");
           }
        }
    }
}

验证不可见性(仅仅加上一个延时)

个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。

       2 开启子线程,由于延时一秒(放弃CPU的时间片),主内存中还是  flag = false; 并且让cpu去执行main线程。

       3  main线程 将 主内存中 flag =false; 读取到main线程的 副本中去。

       4 子线程将 flag = true; 并刷新到主内存中。

       5  由于 while(true)速度比较快,只会读 main线程的副本,不会读主内存数据 。所以不会有打             印,"主线程获取flag"

    

JMM规定

  1. 所有的共享变量都存储于主内存中,这里说的变量不包含局部变量 ,因为局部变量是私有的,因此不存在竞争问题。
  2. 每一个线程还存在自己的工作内存,线程的工作内存,保留了共享变量的副本。
  3. 线程对变量的所有操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
  4. 不同线程间,也不能直接访问对方工作内存中的变量,线程间的变量的值的传递需要通过主内存中转完成。
  5. 总结   1.线程读取共享变量到自己的副本中
              2.更改副本的值
              3.刷新到主内存

 2 不可见性解决方案

 如何实现多线程间访问共享变量的可见性?

  1. 加锁
  2. 使用volatile关键字
  3. while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

2.1 加锁方式解决

讲解:某一个线程进入到synchronized代码块中,执行过程如下:

  1. 线程获取锁     
  2. 清空工作内存(本地内存)   
  3. 从主内存拷贝共享变量最新值到工作内存中称为副本。
  4. 执行代码,将修改后的副本刷新会主内存中
  5. 线程释放锁。

 

2.2 使用volatile关键字

 流程原理

 

2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量

 

volatile不保证原子性以及解决方案

3.1 案例 volatile 验证不是原子性

public class ThreadRunnable2 implements Runnable {
    private volatile int a = 100;
    @SneakyThrows @Override
    public void run() {
        while (true) {
            if (a > 1) {
                a--;
                System.out.println(Thread.currentThread().getName() + "==" + a);
                Thread.sleep(100);
            }
        }
    }
    public static void main(String[] args) {
        ThreadRunnable2 thread = new ThreadRunnable2();
        new Thread(thread).start();
        new Thread(thread).start();
    }
}

 

3.2  讲解count++和count-- 不是原子操作

 

 

3.3 解决volatile 原子性问题

加锁

 

使用原子类

AtomicBoolean可以用原子方式更新的 boolean 值。
AtomicInteger可以用原子方式更新的 int 值。
AtomicIntegerArray可以用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater<T>基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新
AtomicLong可以用原子方式更新的 long 值。
AtomicLongArray可以用原子方式更新其元素的 long 数组
AtomicLongFieldUpdater<T>基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference<V>AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
AtomicReference<V>可以用原子方式更新的对象引用。
AtomicReferenceArray<E>可以用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater<T,V>基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新
AtomicStampedReference<V>AtomicStampedReference 维护带有整数"标志"的对象引用,可以用原子方式对其进行更新

    public static void main(String[] args) {
        //无参构造初始值为0 ,有参构造可以指定初始值
        AtomicInteger atomicInteger = new AtomicInteger();
        //获取当前值
        int i = atomicInteger.get();
        // 加 1操作 并返回前一个值  
        //例如:初始值是0 执行完getAndIncrement(),返回结果是 0 ,但是当前值是 1。
        int andIncrement = atomicInteger.getAndIncrement();
        // 减1操作 并返回前一个值
        int andDecrement = atomicInteger.getAndDecrement();
        // 加 20 并返回最终结果
        //例如: 原值是5,返回结果将是25。
        int i1 = atomicInteger.addAndGet(20);
        // 设置新值 返回旧值
        int andSet = atomicInteger.getAndSet(30);
    }

4  volatile 禁止指令重排序

指令重排序:cpu为了提高我们的处理速度可能会对我们的代码指令重排序。但是在并发执行下,会有较小的概率出现和我们预期不符的结果。

案例(指令重排序)(执行了68万没有复现。就不打算执行了)

public class volatileDemo2 {

    public static int a = 0, b = 0;
    public static int i = 0, j = 0;

    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        while (true) {
            count++;
            Thread one = new Thread(() -> {
                a = 1;
                i = b;
            }, "线程A");
            Thread two = new Thread(() -> {
                b = 1;
                j = a;
            }, "线程B");

            one.start();
            two.start();
            one.join();
            two.join();
            String result = "第 " + count + "次" + " i= " + i + " , j= " + j;
            System.out.println(result);
            if (i == 0 && j == 0) {
                break;
            }
        }
    }
}

以上代码打印可的情况分析

解决办法

 

实战(懒汉式 双重检查+volatile

public class volatileDemo3 {
    //静态属性 ,volatile保证可见性和禁止指令重排
    private volatile static volatileDemo3 instance = null;

    //私有化构造器
    private volatileDemo3() {
    }
    public static volatileDemo3 getInstance() {
        //第一重检查
        if (instance == null) {
            synchronized (volatileDemo3.class) {
                //第二重检查
                if (instance == null) {
                    instance = new volatileDemo3();
                }
            }
        }
        return instance;
    }
}

 

 5  场景(只适合纯赋值 )

 

  //纯赋值不会设计到原子操作
  //  flag = true;
  // 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。
   flag = !flag;
public class volatileDemo4 implements Runnable {
    private volatile boolean flag = false;
    AtomicInteger atomicInteger = new AtomicInteger();
    @SneakyThrows @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
             //纯赋值不会设计到原子操作
           //  flag = true;
            // 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。
            flag = !flag;
            atomicInteger.getAndIncrement();
        }
    }
    public boolean getFlag(){

        return flag;
    }
    public static void main(String[] args) throws InterruptedException {
        volatileDemo4 thread = new volatileDemo4();
        // 1 子线程开始执行
        Thread thread1 = new Thread(thread);
        Thread thread2 = new Thread(thread);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(thread.getFlag()+"次数"+thread.atomicInteger);
    }
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值