NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 设计模式

  • 开发规范

  • 经验分享

  • 记录

  • 快速开始

  • 笔记

    • 多线程与并发

      • 前言
      • 理论基础
      • Java中各种锁的概念
      • 关键字:synchronized
      • 关键字:volatile
        • 概念
        • 特点
          • 防重排序
          • 实现可见性
          • 不保证原子性
        • 原子变量与CAS算法
          • i++原子性问题
          • 原子变量
        • 参考文章
      • 关键字:final
      • Java多线程 - 线程池
      • Java多线程 - Block Queue阻塞队列
      • Java多线程 - 辅助类
      • Java多线程 - CompletableFuture
      • Java多线程 - 线程变量传递ThreadLocal
    • JDK

    • Java集合

    • Spring

    • JVM

    • Other

  • 面试题

  • 微服务

  • 踩过的坑

  • Java
  • 笔记
  • 多线程与并发
NipGeihou
2022-02-10
目录

关键字:volatile

# 概念

一种解决可见性和有序性问题的方案

# 特点

# 防重排序

懒汉式 - 单例模式 - 双重检查加锁:


 

















public class Singleton {
    public static volatile Singleton singleton;
    /**
     * 构造函数私有,禁止外部实例化
     */
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

在上述单例模式代码中,使用了 volatile 关键字在单例对象上,以防止在极端情况下因操作系统 对指令进行重排序 导致不可预料的结果。

正常实例化对象步骤

  1. 分配内存空间。
  2. 初始化对象。
  3. 将内存空间的地址赋值给对应的引用。

被对指令进行重排序可能发生的步骤

  1. 分配内存空间。
  2. 将内存空间的地址赋值给对应的引用。
  3. 初始化对象。

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为 volatile 类型的变量。

# 实现可见性











 
 
 
 
 
 







 









package study;

public class VolatileStudy implements Runnable {

    private boolean flag = false;

    public static void main(String[] args) {
        VolatileStudy vs = new VolatileStudy();
        new Thread(vs).start();

        while (true) {
            if (vs.isFlag()) {
                System.out.println("---------------");
                break;
            }
        }
    }

    @Override
    public void run() {

        Thread.sleep(200);

        this.flag = true;
        System.out.println("flag = " + flag);
    }

    public boolean isFlag() { return flag; }

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

运行结果

flag = true

在上述代码中,由于 main 线程中的 while(true) 速度极快 (opens new window),以至于无法从主存中获取最新的值,因此 main 线程一直不能执行到 System.out.println("---------------"); 。


解决方法一:打破 while(true) 的 “速度极快”

在 while 循环末尾添加一个休眠代码,则能读取主存更新后的值。


解决方法二:synchronized 关键字

synchronized (vs) {
    if (vs.isFlag()) {
        System.out.println("---------------");
        break;
    }
}

解决方法三:volatile 关键字

private volatile boolean flag = false;

总结

上述场景中,由于 (主) 线程没看到其他线程对共享变量值的修改,导致 BUG 的出现。使用 volatile 关键字实现可见性,从而避免上述 BUG 的产生。

# 不保证原子性

volatile 不能保证完全的原子性,只能保证单次的读 / 写操作具有原子性。

似乎有没有 volatile 关键字都可以保证单次读 / 写的原子性,难道这还可以细分吗?@NipGeihou

如何解决i++原子性问题

仅仅依靠 volatile 关键字无法解决 “i++ 原子性” 问题,原因是 i++ 实际上分为读、写两步操作,可通过使用 原子类 或 Synchronized关键字 解决此问题。

# 原子变量与 CAS 算法

# i++ 原子性问题

package study;

public class TestAtomicDemo {

    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }

}

class AtomicDemo implements Runnable {

    private int serialNumber = 0;

    @Override
    public void run() {

        Thread.sleep(200);

        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());

    }

    public int getSerialNumber() { return serialNumber++; }
}

输出结果

Thread-5:0
Thread-9:2
Thread-0:1
Thread-2:2
Thread-7:5
Thread-3:4
Thread-4:3
Thread-8:1
Thread-1:0
Thread-6:6

进程已结束,退出代码0

上述代码出现原子性线程安全问题

解决方法:使用原子变量

...
    // private int serialNumber = 0;
    private AtomicInteger serialNumber = new AtomicInteger();
...
    public int getSerialNumber() {
        // return serialNumber++;
        return serialNumber.getAndIncrement();
    }
...

# 原子变量

java.util.concurrent.atomic

[标量类]
AtomicBoolean
AtomicInteger
AtomicLong
AtomicReference

[数组类]
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray

[更新器类]
AtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater

[复合变量类]
AtomicMarkableReference
AtomicStampedReference

DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder

上述原子变量使用了:

  1. volatile 保证内存可见性

  2. CAS(CompareAndSwap)算法保证数据的原子性

    CAS 包括了三个操作数:内存值 V 、预估值 A、更新值 B;当且仅当 V == A 时, V = B。

# 参考文章

  • 关键字: volatile 详解 | Java 全栈知识体系 (opens new window)
上次更新: 2022/12/31, 03:04:26
关键字:synchronized
关键字:final

← 关键字:synchronized 关键字:final→

最近更新
01
Docker Swarm
04-18
02
安全隧道 - gost
04-17
03
Solana最佳实践
04-16
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式