广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >JUC并发编程之volatile详解
  • 791
分享到

JUC并发编程之volatile详解

javajvm开发语言 2023-09-01 11:09:37 791人浏览 独家记忆
摘要

目录   1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1.

目录

 

1. volatile

1.1 volatile关键字的作用

1.1.1 变量可见性

1.1.2 禁止指令重排序

1.2 volatile可见性案例

1.3 volatile非原子性案例

1.4 volatile 禁止重排序

1.5 volatile 日常使用场景

送书活动


 

1. volatile

并发编程中,多线程操作共享的变量时,可能会导致线程安全问题,如数据竞争、可见性问题等。为了解决这些问题,Java提供了JUC(java.util.concurrent)工具包,其中包含了很多用于处理并发编程的工具类和接口。在JUC中,volatile是一个关键字,它可以用于修饰变量,用来确保变量的可见性和禁止指令重排序,从而在一定程度上解决线程安全问题。

1.1 volatile关键字的作用

1.1.1 变量可见性

在多线程环境下,如果一个线程修改了共享变量的值,其他线程可能由于线程间的数据不一致性而看不到该变量的最新值。这种问题称为“变量不可见性”或“可见性问题”。

volatile关键字可以确保被修饰的变量对所有线程可见。当一个线程修改了volatile变量的值,其他线程立即能够看到修改后的最新值,而不会使用缓存中的旧值。

1.1.2 禁止指令重排序

JVM(Java虚拟机)中,为了优化性能,编译器和处理器可能会对指令进行重排序。在单线程环境下,这种重排序不会影响程序的执行结果。然而,在多线程环境下,指令重排序可能会导致线程安全问题。

volatile关键字可以防止指令重排序,确保被修饰的变量按照代码中的顺序执行。

1.2 volatile可见性案例

public class VolatileExample {    private static volatile boolean flag = false;    public static void main(String[] args) {        new Thread(() -> {            while (!flag) {                System.out.println("Waiting for the flag to be true...");            }            System.out.println("Flag is now true. Exiting the thread.");        }).start();        try {            Thread.sleep(1000); // 确保主线程在子线程之前执行        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("Setting the flag to true...");        flag = true;    }}

在上面的示例中,我们创建了一个VolatileExample类,并声明了一个volatile类型的flag变量。在主线程中,我们启动一个新的子线程,该子线程会不断地检查flag变量的值,直到flag变为true时,子线程退出。

在主线程中,我们将flag变量设置为true。由于flag变量被声明为volatile类型,子线程能够及时看到flag的最新值,从而退出循环,输出“Flag is now true. Exiting the thread.”。

这个示例演示了volatile关键字的作用,确保了flag变量的可见性。如果我们没有使用volatile关键字,子线程可能会一直循环下去,因为它看不到主线程对flag的修改。

38b2da1a26484e70b96d28c9a0f348a3.png

 

1.3 volatile非原子性案例

public class Test {    public static void main(String[] args) throws InterruptedException {        VolatileAtomicityExample example = new VolatileAtomicityExample();        for(int i=1;i<=100;i++){            new Thread(()->{                for(int j=1;j<=1000;j++)                    example.increment();            },String.valueOf(i)).start();        }        TimeUnit.SECONDS.sleep(2);        System.out.println(example.getCount());    }}class VolatileAtomicityExample {    volatile int count = 0;    public  void increment() {        count++;    }    public int getCount() {        return count;    }}

我们创建了一个VolatileAtomicityExample类,其中的成员变量count被声明为volatile类型。然后,我们创建了100个线程,每个线程分别执行1000次increment()操作,对count进行自增。最后,我们在主线程中打印count的最终值。以上示例中的输出结果可能会因为运行时的不确定性而有所不同。每次运行时可能得到不同的结果,但通常结果都小于100000。为了解决这个问题,我们需要使用synchronized关键字或其他线程安全机制来确保increment()方法的原子性。

这是什么原因呢?

4afd2ac10749476b910af197364ee512.png

 对于volatile变量具备可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的,也仅是数据加载时是最新的。但是多线程环境下,"数据计算"和"数据赋值"操作可能多次出现,若数据在加载之后,若主内存volatile修饰变量发生修改之后,线程工作内存中的操作将会作废去读主内存最新值,操作出现写丢失问题。即各线程私有内存和主内存公共内存中变量不同步,进而导致数据不一致。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加同步。

1.4 volatile 禁止重排序

内存屏障是一种硬件指令或者编译器指令,用于控制内存操作的顺序,以确保多线程环境下的内存可见性和正确的执行顺序。

内存屏障分为两种类型:

  1. 内存读屏障(Load Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存读取操作之前,所有的先行写操作都已经完成,并且其结果对当前线程可见。也就是说,读屏障可以防止后续读取指令重排序到读屏障之前的位置。

  2. 内存写屏障(Store Barrier):它是一个特殊的硬件或者编译器指令,用于保证在内存写入操作之前,所有的先行写操作和写屏障之前的写操作都已经完成,并且其结果对其他线程可见。也就是说,写屏障可以防止前面的写入指令重排序到写屏障之后的位置。

volatile关键字通过内存屏障来保证变量的读写操作不会被重排序。具体来说,对于volatile变量的写操作,在写入变量之后会插入写屏障,这样可以防止其他指令重排序到写屏障之前。类似地,对于volatile变量的读操作,在读取变量之前会插入读屏障,这样可以防止其他指令重排序到读屏障之前。

通过这种方式,volatile关键字确保了对变量的读写操作具有一定的有序性,从而保证了多线程环境下的内存可见性和正确的执行顺序。

1.5 volatile 日常使用场景

状态标志:当一个线程修改了某个状态标志,其他线程需要立即看到最新的状态。这时可以使用volatile关键字修饰状态标志,保证其在多线程之间的可见性。例如:

public class Task implements Runnable {    private volatile boolean isRunning = true;    @Override    public void run() {        while (isRunning) {            // 执行任务逻辑        }    }    public void stop() {        isRunning = false;    }}

双重检查锁定(Double-Checked Locking):在多线程环境下,当需要延迟初始化一个对象时,为了避免重复初始化,常常使用双重检查锁定。在这种情况下,需要使用volatile关键字来确保对象在多线程环境中的可见性。例如:

public class Singleton {    private volatile static Singleton instance;    private Singleton() {}    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}

在上述代码中,我们实现了一个简单的单例模式。在getInstance()方法中,我们使用双重检查锁定来实现延迟初始化,确保只有在instance为空时才创建新的Singleton实例。

然而,在多线程环境下,由于指令重排序的存在,可能会导致以下问题:

  1. 对象引用不为空但尚未初始化:在线程A执行完instance = new Singleton();这一行之前,可能发生指令重排序,导致instance的引用不为空,但是Singleton实例的初始化还未完成。这样,线程B在执行return instance;时就会得到一个尚未初始化的对象,导致错误。

  2. 可见性问题:指令重排序也可能导致线程B无法及时看到线程A的初始化操作。例如,线程A对instance的赋值可能被重排到线程A的后面执行,从而线程B在读取instance时得到一个旧的引用,无法感知线程A的初始化操作。

为了解决这个问题,需要在创建Singleton实例时使用volatile关键字来保证对象的可见性和禁止指令重排序。

 

送书活动

《硅基物语·我是灵魂画手》

 

当AI遇上绘画,会打开怎样的奇妙世界?

用ChatGPT+Midjourney西出人类的灵魂与梦想

用StableDiffusion+D-ID画出青春绚丽的渴望

激活每个人隐藏的绘画天赋

人人都能成为顶尖绘画大师

 

ChatGPT+Midjourncy+StableDiffusion+D-ID+RunwayGen-l

爆火软件全流程协作

掌据AI绘商技巧

解锁超全绘画关键司

讲解创作底层逻辑

一本书讲透AI绘画全流程

 

内容简介

一本将AI绘画讲透的探秘指南,通过丰富的实践案例操作,通俗易懂地讲述AI绘画的生成步骤生动展现了AI绘画的魔法魅力。从历史到未来,跨越百年时空,从理论到实践,讲述案例操作:从技术到哲学,穿越多个维度,从语言到绘画,落地实战演练。AI绘画的诞生,引发了奇点降临,点亮了AGI(通用人工智能),并涉及 Prompt、风格,技术细节、多模态交互,AIGC等一系列详细讲解。让您轻松掌握生图技巧,创造出独特的艺术作品,书写属于自己的艺术时代。

 

ce601e4efef840d5a5733a2116498ffb.jpeg

 当当网链接:http://product.dangdang.com/29601870.html

 

 关注博主、点赞、收藏、

评论区评论

  即可参与送书活动! 

 

来源地址:https://blog.csdn.net/qq_43649937/article/details/132042338

--结束END--

本文标题: JUC并发编程之volatile详解

本文链接: https://www.lsjlt.com/news/388167.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JUC并发编程之volatile详解
    目录   1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1....
    99+
    2023-09-01
    java jvm 开发语言
  • 详解JUC并发编程之锁
    目录1、自旋锁和自适应锁2、轻量级锁和重量级锁轻量级锁加锁过程轻量级锁解锁过程3、偏向锁4、可重入锁和不可重入锁5、悲观锁和乐观锁6、公平锁和非公平锁7、共享锁和独占锁8、可中断锁和...
    99+
    2022-11-12
  • 详解Java并发编程基础之volatile
    目录一、volatile的定义和实现原理1、Java并发模型采用的方式2、volatile的定义3、volatile的底层实现原理二、volatile的内存语义1、volatile的...
    99+
    2022-11-12
  • 详解Java并发编程之volatile关键字
    目录1、volatile是什么?2、并发编程的三大特性3、什么是指令重排序?4、volatile有什么作用?5、volatile可以保证原子性?6、volatile 和 sy...
    99+
    2022-11-12
  • Java并发编程之Volatile变量详解分析
    目录一、volatile变量的特性1.1、保证可见性,不保证原子性1.2、禁止指令重排二、内存屏障三、happens-beforeVolatile关键字是Java提供的一种轻量级的同...
    99+
    2022-11-12
  • java并发编程工具类JUC之ArrayBlockingQueue
    Java BlockingQueue接口java.util.concurrent.BlockingQueue表示一个可以存取元素,并且线程安全的队列。换句话说,当多线程同时从 Jav...
    99+
    2022-11-12
  • java 多线程与并发之volatile详解分析
    目录CPU、内存、缓存的关系CPU缓存什么是CPU缓存为什么要有多级CPU CacheJava内存模型(Java Memory Model,JMM)JMM导致的并发安全问题可见性原子...
    99+
    2022-11-12
  • 详解JUC并发编程中的进程与线程学习
    目录进程与线程进程线程同步异步串行并行执行时间创建和运行线程Thread 与 Runnable 的关系原理分析查看进程线程运行原理线程上下文切换start与run方法sleep方法s...
    99+
    2022-11-13
  • Java并发编程之关键字volatile的深入解析
    目录前言一、可见性二、有序性总结前言 volatile是研究Java并发编程绕不过去的一个关键字,先说结论: volatile的作用:       &n...
    99+
    2022-11-12
  • Java并发编程之ThreadLocal详解
    目录一、什么是ThreadLocal?二、ThreadLocal的使用场景三、如何使用ThreadLocal四、数据库连接时的使用五、ThreadLocal工作原理六、小结七、注意点...
    99+
    2022-11-12
  • Golang并发编程之Channel详解
    目录0. 简介1. channel数据结构2. channel创建3. 数据发送3.1 空通道的数据发送3.2 直接发送3.3 缓存区3.4 阻塞发送4. 接收数据4.1 空通道的数...
    99+
    2023-05-19
    Golang并发编程Channel Golang并发编程 Golang Channel
  • java并发编程工具类JUC之LinkedBlockingQueue链表队列
    java.util.concurrent.LinkedBlockingQueue 是一个基于单向链表的、范围任意的(其实是有界的)、FIFO阻塞队列。访问与移除操作是在队头进行,添...
    99+
    2022-11-12
  • JUC 并发编程学习笔记(总)
    文章目录 1. 什么是JUC2. 进程和线程2.1 进程2.2 线程2.3 并发2.4 并行2.5 线程的状态2.6 wait 和 sleep 的区别 3. Lock锁(重点)3.1 传统Synchronized3.2 Lock...
    99+
    2023-08-18
    java JUC
  • java并发编程JUC CountDownLatch线程同步
    目录java并发编程JUC CountDownLatch线程同步1、CountDownLatch是什么?2、CountDownLatch 如何工作3、CountDownLat...
    99+
    2022-11-12
  • Java并发编程之关键字volatile知识总结
    目录一、作用二、可见性三、有序性四、happens-before五、与 Synchronized 对比一、作用 被 volatile 修饰的变量 1.保证了不同线程对该变量操作的内存...
    99+
    2022-11-12
  • Java并发编程之JUC并发核心AQS同步队列原理剖析
    目录一、AQS介绍二、AQS中的队列1、同步等待队列2、条件等待队列3、AQS队列节点Node三、同步队列源码分析1、同步队列分析2、同步队列——独占模式源码分析3、同步队列——共享...
    99+
    2022-11-12
  • Java并发编程之Executors类详解
    一、Executors的理解 Executors类属于java.util.concurrent包; 线程池的创建分为两种方式:ThreadPoolExecutor ...
    99+
    2022-11-12
  • Java并发编程之LockSupport类详解
    目录一、LockSupport类的属性二、LockSupport类的构造函数三、park(Object blocker)方法 和 park()方法分析四、parkNanos(Obje...
    99+
    2022-11-12
  • 详解Java高并发编程之AtomicReference
    目录一、AtomicReference 基本使用1.1、使用 synchronized 保证线程安全性二、了解 AtomicReference2.1、使用 AtomicReferen...
    99+
    2022-11-12
  • Java并发编程之详解ConcurrentHashMap类
    前言 由于Java程序员常用的HashMap的操作方法不是同步的,所以在多线程环境下会导致存取操作数据不一致的问题,Map接口的另一个实现类Hashtable 虽然是线程安全的,但是...
    99+
    2022-11-12
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作