博客
关于我
Java学习笔记(多线程):volatile关键字
阅读量:549 次
发布时间:2019-03-09

本文共 3079 字,大约阅读时间需要 10 分钟。

《Java并发编程实践:深入解析volatile关键字》

1. 导言

Java中的volatile关键字用于多线程编程,用于修饰一变量,使其在简单实现synchronized的效果。虽然初始解释较为简单,但volatile与synchronized的区别及使用细节对多线程开发至关重要。本文将详细解析volatile的实现原理及其在多线程环境中的应用。

2. CPU缓存机制与多线程下的数据不一致

在单线程环境中,CPU高速缓存与内存的数据传递过程是无缝的。然而,在多线程环境中,每个线程都有其自己的CPU和高速缓存。这种情况下,多个线程对同一变量进行操作时可能导致数据不一致。

以一条简单的语句x = x + 1为例,数据流转过程如下:

  • CPU从内存中读取x到高速缓存。
  • CPU对高速缓存中的x执行加法运算,并存储结果。
  • 可能尚未将计算结果写回内存。
  • 在多线程环境下,若线程1在执行完第二步而线程2立即读取内存中的x,得到的可能仍为旧值。最终结果无法满足预期,可能仅增加一次,使得x的值小于预期。

    3. 多线程编程中的三个核心概念

    在并发编程中,系统需要满足以下三大性质:

  • 原子性:多个操作必须视为一个整体执行。
  • 有序性:代码的执行顺序需遵循原文顺序。
  • 可见性:被多个线程共享的变量修改后,所有线程需立刻知晓。
  • 3.1 原子性

    原子性要求所有操作要么全部执行完,要么全部打住。某些操作(如基本数据类型的加法)在Java中是原子操作,这意味着不会因线程调度而被打断。但复杂操作可能需要额外保护,如synchronized或Lock。

    3.2 有序性与指令重排序

    代码执行的顺序决定了结果的有效性。为了优化性能,系统可能会对代码进行重排序。然而,重排序可能导致结果不依赖依赖关系的改变,破坏有序性。例如,以下代码可能因为重排序而导致错误:

    a = a + 1; // 依赖于a的初始值b = a + a;  // 取决于a的值b = b + a;  // 再次利用a的值

    在多线程环境中,重排序问题更为严重。如下代码:

    innited = false; // 线程1操作 doSomething(); // 线程2马上执行

    因为initeddoSomething()无关,线程2可能直接执行,而忽略线程1的准备工作。

    3.3 可见性

    可见性要求所有线程在某一时刻知晓shared variable的变化。

    占用解决方案

  • volatile关键字:确保变量修改后立即写入内存。其他线程只能从主存读取。
  • synchronized关键字:同时保证可见性和原子性。
  • Lock机制:提供更细粒度的同步控制。
  • 4. Java内存模型

    Java内存模型规定主存是唯一管理中存储的介质,每个线程拥有一自己的工作内存。变量只能在工作内存中进行计算后,再写回主存。线程不可直接访问其他线程的工作内存,主存是所有线程的唯一共享资源。

    下图展示了Java内存的结构:

    主存 <-线程1-> 工作内存 <-	cpu <- 寄存器 <-cache <- CPU     |                               |   |               |                     |                               |   |               |                     |                               |   |               |                     |                               |   |               |                     |                               |                               |                     --------------------------读写 occurs here -------------------------

    4.1 原子性支持

    Java内存模型支持对基本数据类型变量的原子操作。这是指一个线程读取并修改变量时,其他线程无法读取或修改同一变量的中间值。例如:

    x = x + 1;x++; // 这两步不是原子操作

    上述代码并非原子操作,因为在完成x = x + 1后,x可能已经被另一个线程读取到新的值。

    4.2 可见性支持

    volatile变量的修改会立即同步至主存,其他线程的读取操作只能从主存获取数据。因此,volatile保证了可见性。但是,这种同步给性能带来了影响。

    5. volatile关键字的作用与限制

    5.1 volatile的可见性

    volatile变量的修改会立即写入主存,使得其它线程能够及时获取最新值。这避免了多线程环境下的缓存一致性问题。

    5.2 volatile不保证原子性

    例如,对int i = 0;进行自增操作,可能执行以下步骤:

  • 从内存读取i的值。
  • 将i加1。
  • 将结果写入内存。
  • 如果在第2步后另一个线程读取内存,此时i可能已被修改为1,但读取到的值可能还是原来的。这样,同一语句可能只执行一次,导致结果错误。

    5.3 综合分析

    为了确保原子性和可见性,可以采用以下方式:

  • 使用synchronized关键字。
  • 使用Lock机制。
  • 利用AtomicInteger等原子类型。
  • 下面的示例显示了volatile和线程安全的区别:

    public class VolatileTest {    public volatile int inc = 0;     public void increase() {        inc++;    }    public static void main(String[] args) {        final VolatileTest test = new VolatileTest();        Lock lock = new ReentrantLock();        for(int i = 0; i < 100; i++){            new Thread(() -> {                for(int j = 0; j < 10000; j++) {                    try {                        lock.lock();                        test.increase();                        System.out.println(test.inc);                    } finally {                        lock.unlock();                    }                }            }, "线程" + i).start();        }    }}

    与使用synchronizedLock结合使用的版本相比,volatile只能确保可见性,而不能保证原子性。

    这篇文章实际上深入探讨了volatile的实现原理、与多线程编程的关系,并结合实际案例分析了其设计决策和局限性。

    转载地址:http://ueupz.baihongyu.com/

    你可能感兴趣的文章
    小程序form表单里面buton点击事件失效
    查看>>
    微信小程序placeholder设置自定义样式
    查看>>
    安装npm install --save vue-scroller失败
    查看>>
    es6 引用数组,数组发生改变 (es6 引用类型的数据引用的时候怎么不改变原始数据)
    查看>>
    后端返回图片地址,图片不显示的问题
    查看>>
    fatal: Not a git repository Git报错
    查看>>
    spring-day01
    查看>>
    spring的值注入与组件扫描
    查看>>
    【leetcode】349. 两个数组的交集(intersection-of-two-arrays)(哈希)[简单]
    查看>>
    ftp上传不成功,提示 200 227 501 错误
    查看>>
    C#跨窗体程序调用方法的具体操作
    查看>>
    C#中创建Android项目
    查看>>
    C#使用OpenCV(OpenCVSharp)
    查看>>
    ANC主动降噪技术的原理
    查看>>
    伦茨科技最新32脚蓝牙芯片-ST17H65
    查看>>
    如何让一个TWS连接两个手机
    查看>>
    统计学之变异系数与是非标志
    查看>>
    统计学之偏度系数和峰度系数
    查看>>
    第5讲 间接调用
    查看>>
    MOOC运筹学不挂科笔记 对偶问题
    查看>>