JAVA并发编程之Volatile关键字及内存可见性

4次阅读

共计 2745 个字符,预计需要花费 7 分钟才能阅读完成。

作者:毕来生
微信:878799579


1. 什么是 JUC?

JUC 全称 java.util.concurrent 是在并发编程中很常用的实用工具类

2.Volatile 关键字

1、如果一个变量被 volatile 关键字修饰,那么这个变量对所有线程都是可见的。
2、如果某条线程修改了被 Volatile 修饰的这个变量值,修改后的值对于其他线程来时是立即可见的。
3、并不是经过 Volatile 修饰过的变量在多线程下就是安全的
4、多线程间可以使用 SynchronousQueue 或者 Exchanger 进行数据之间传递

3. 内存可见性

内存可见性 (Memory Visibility) 是指当某个线程正在使用对象状态 而另一个线程在同时修改该状态, 需要确保当一个线程修改了对象 状态后, 其他线程能够看到发生的状态变化。
可见性错误是指当读操作与写操作在不同的线程中执行时, 我们无法确保执行读操作的线程能适时地看到其他线程写入的值, 有时甚至是根本不可能的事情。
原理同 CAS 原理相同,不懂的同学可以自行百度,附上一张 CAS 演示图供大家参考

4. 实战举例

通过线程来修改变量 count 的值,使用 Volatile 关键字修饰和不使用 Volatile 修饰 count 变量结果对比。

首先我们来看一下通过内部类实现 Runnable,变量 <font color=”red”> 使用 Volatile 关键字 </font> 修饰演示以及结果

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:29
 * @Todo: 通过内部类实现 Runnable,变量使用 Volatile 关键字修饰演示
 * @Version : JDK11 , IDEA2018
 */
public class NoVolatileTest{public static void main(String[] args) {NoVolatileThread noVolatileThread = new NoVolatileThread();
        new Thread(noVolatileThread).start();

        while (true){if(noVolatileThread.isFlag()){System.out.println("flag 此时为 true!");
                break;
            }
        }
    }
}

class NoVolatileThread implements Runnable{

    private boolean flag = false;
    
    @Override
    public void run() {
        try {Thread.sleep(500);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        
        flag = true;

        System.out.println(Thread.currentThread().getName() + "flag =" + flag);
    }

    public boolean isFlag() {return flag;}

    public void setFlag(boolean flag) {this.flag = flag;}
}

运行结果如下图所示:


接下来我们来看一下通过内部类实现 Runnable,变量 <font color=”red”> 不使用 Volatile 关键字 </font> 修饰演示以及结果

package org.bilaisheng.juc;

/**
 * @Author: bilaisheng
 * @Wechat: 878799579
 * @Date: 2019/1/1 16:53
 * @Todo: 通过内部类实现 Runnable,变量使用 Volatile 关键字修饰演示
 * @Version : JDK11 , IDEA2018
 */
public class VolatileTest{public static void main(String[] args) {VolatileThread volatileThread = new VolatileThread();
        new Thread(volatileThread).start();

        while (true){
            // if 的判断 volatile 保证当时确实正确,然后线程 a 可能处于休眠状态,// 线程 b 也判断不存在,b 线程就 new 了一个。// 然后 a 线程 wake up,据需执行 new volatile 获取最新值。if(volatileThread.isFlag()){System.out.println("flag 此时为 true!");
                break;
            }
        }
    }
}

class VolatileThread implements Runnable{

    private volatile boolean flag = false;

    @Override
    public void run() {
        try {Thread.sleep(500);
        } catch (InterruptedException e) {e.printStackTrace();
        }
        flag = true;

        System.out.println(Thread.currentThread().getName() + "flag =" + flag);
    }

    public boolean isFlag() {return flag;}

    public void setFlag(boolean flag) {this.flag = flag;}
}

运行结果如下图所示:

通过对比我们发现在通过 Volatile 修饰和不通过 Volatile 修饰的变量,输出结果竟然会有些偏差。到底是为什么呢?

我们逐步拆解上面代码执行步骤:

1、针对于不使用 Volatile 关键字修饰变量:

  • 步骤一:默认 flag = false;
  • 步骤二 main 线程的缓存区域没有刷新 flag 的值。所以 flag 还是 false。故没有输出 <flag 此时为 true!>
  • 步骤三:子线程输出 Thread-0 flag = true

2、针对于使用 Volatile 关键字修饰变量:

  • 步骤一:默认 flag = false;
  • 步骤二:主线程看到 flag 是被 Volatile 关键字修饰的变量。则获取最新的 flag 变量值,此时 flag = true。故输出 <flag 此时为 true!>
  • 步骤三:子线程输出 Thread-0 flag = true

5. Volatile 的优点

可见性:被 Volatile 修饰的变量可以马上刷新主内存中的值,保证其他线程在获取时可以获取最新值,所有线程看到该变量的值均相同。

轻量级的 synchronized,高并发下保证变量的可见性。


6.Volatile 的缺点

1、频繁刷新主内存中变量,可能会造成性能瓶颈

2、不具备操作的原子性,不适合在对该变量的写操作依赖于变量本身自己。例如 i ++,并不能通过 volatile 来保证原子性

7. 喜欢就关注我吧

正文完
 0