基础
Java
Java
  • 基础知识
    • Java 语言的特点
    • Java 基础
      • 语法基础
      • 类型
      • 泛型
      • 注解
      • 异常
      • 反射机制
      • Java 容器
    • Java IO
      • 基础IO
      • NIO
    • Java 并发
      • Java 内存模型
        • 主内存与工作内存
        • 对于 volatile 型变量的特殊规则
        • long 和 double 的非原子性协定
        • 原子性、可见性与有序性
        • 先行发生(Happens-Before)原则
      • 线程
        • 状态转换
        • 线程安全性
          • 对象的共享
            • 可见性
            • 线程封闭
            • 不可变性
            • 安全发布
          • 在现有的线程安全类中添加功能
        • 线程池
          • Executor 框架
          • ExecutorService
          • Executors
          • Future
          • CompletionService
          • 设置线程池的大小
          • ThreadPoolExecutor
      • 线程安全的容器
        • 同步容器类
        • 并发容器
          • ConcurrentHashMap
          • CopyOnWriteArrayList
          • BlockingQueue
            • 串行线程封闭
            • 双端队列与工作密取
      • 任务取消
        • 自定义的取消标志
        • 线程中断
        • 通过 Future 来实现取消
      • 条件队列
        • 内置条件队列
        • 显式的 Condition 对象
      • JUC 中的 AQS
        • AbstractQueuedSynchronizer
        • ReentrantLock
        • ReentrantReadWriteLock
        • Semaphore
        • CountDownLatch
      • 原子变量
        • CAS
        • 原子变量类
        • ABA 问题
        • 非阻塞算法
          • 非阻塞的栈
          • 非阻塞的链表(X)
    • Java 虚拟机
      • JVM 的运行机制
      • 类加载器
      • 运行时数据区
        • JVM 的内存区域
        • 永久代与元空间
        • OutOfMemoryError
      • Java 中的 4 种引用类型
      • 垃圾收集(GC)
        • 如何确定垃圾
        • 垃圾回收算法
        • 垃圾收集器
          • Serial 收集器
          • ParNew 收集器
          • Parallel Scavenge 收集器
          • Serial Old 收集器
          • Parallel Old 收集器
          • CMS 收集器
          • Garbage First 收集器
  • Group 1
    • JDK 与 JRE
    • JVM默认配置
    • java与HTTPS
    • 构建高效且可伸缩的结果缓存
    • 基础补充
      • 在 Switch 中使用 String
      • 为什么 Java 语言不支持多重继承?
      • 为什么在重写 equals 方法的时候需要重写 hashCode 方法
      • 为什么 String 要设计为不可变的?
      • 移位运算符
      • SPI 机制
      • 为何 HashMap 不是线程安全的
      • Class.forName() 和ClassLoader.loadClass() 区别
      • synchronized 关键字
    • 零拷贝
    • Java中的锁优化技术
      • 自旋锁与自适应自旋
      • 锁消除
      • 锁粗化
      • 轻量级锁
      • 偏向锁
    • Arthas
    • Thread.sleep()、Object.wait()、Condition.await()、LockSupport.park()
由 GitBook 提供支持
在本页
  • 加锁与可见性
  • volatile 变量
  • 非原子的 64 位操作
  1. 基础知识
  2. Java 并发
  3. 线程
  4. 线程安全性
  5. 对象的共享

可见性

上一页对象的共享下一页线程封闭

最后更新于9个月前

可见性是一个复杂的属性,因为可见性中的错误总是会违背我们的直觉。

在单线程环境中,如果向某个变量先写入值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。然而,当读操作和写操作在不同的线程中执行时,情况却并非如此。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是不可能的事情。

为了确保多线程之间对内存写入操作的可见性,必须使用同步机制。

加锁与可见性

加锁的含义不仅局限于互斥行为,还包括内存可见性。

volatile 变量

当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者其他处理器不可见的地址,因此在读取 volatile 类型的变量时总会返回最新写入的值。

当数组引用被声明为 volatile 时,只有引用而不是数组本身具有 volatile 语义。

当线程 A 首先写入一个 volatile 变量,并且线程 B 随后读取该变量时,在写入 volatile 变量之前对 A 可见的所有变量的值,在 B 读取了 volatile 变量后,对 B 也是可见的。

如果在代码中依赖 volatile 变量来控制状态的可见性,通常比使用锁的代码更脆弱,也更难以理解。

当且仅当满足以下所有条件时,才应该使用 volatile 变量:

非原子的 64 位操作

最低安全性

当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全性保证被称为最低安全性(out-of-the-thin-air safety)

最低安全性适用于绝大多数变量,但存在一个例外:非 volatile 类型的 64 位数值变量(double 和 long)。Java 内存模型要求,变量的读取操作和写入操作都必须是原子操作,但对于非 volatile 类型的 long 和 double 变量,JVM 允许将 64 位的读操作和写操作分解为两个 32 位的操作。

long 和 double 的非原子性协定