JAVA并发编程volatile核心原理

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
简介: volatile是轻量级的并发解决方案,volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性,具体是如何实现可见性和有序性。以及volatile缺点是什么?

      上文说到synchronized,《JAVA并发编程synchronized全能王的原理》,虽然被评为并发全能王,不过用起来也是格外注意,不能搞大力出奇迹那一套,容易出现性能问题。比如synchronized是无法控制阻塞时长,阻塞不可中断问题;以及锁范围,修饰方法或代码块,要精细,仅修饰需要并发控制部分,降低锁粒度。文末再总结一下,synchronized和volatile的区别,先进入正题,聊聊正主:volatile。

     volatile是轻量级的并发解决方案,不会阻塞线程,是一种简单的同步机制。JAVA对volatile的定义是:volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性。

1.如何保证有序性

     有序性:禁止指令重排优化。具体就是要求volatile修饰的变量操作(读写该变量的语句)后面的语句放到前面执行,也不能让将volatile变量操作之前的的语句延迟到后面执行。要求volatile变量前后语句按序执行,不许指令重排优化。比如如图,count是volatile修饰的int变量。在多线程并发到这一段代码语句123,CPU处理器就是按顺序执行。不好出现132顺序。

2.如何保证可见性

      保证此变量的修改,能被所有线程及时看到。具体是,线程改volatile修饰变量时,先改本地缓存变量副本,然后将本地变量副本值刷到主内存中。其他线程都volatile变量时,强制从主存也就是JVM堆内存读取变量最新值到线程本地缓存。

3.volatile实现可见性源码分析

     volatile实现可见性原理,其实就是:内存屏障(memory barrier)。内存屏障是一条CPU指令,用来控制在特定条件下的重排序和可见性问题。java编译器会根据内存屏障的规则禁止重排序。

     在对volatile变量写操作前,编译器会在写操作之后-》增加一个store屏障指令,让线程本地内存变量值能刷新到主内存中。

     在对volatile变量读操作前,编译器会在读操作之前《--增加一个load屏障指令,保证及时读到主内存的最新值。

    总结起来,通俗的讲,就是CPU指令在对volatile修饰的变量修改后,会马上写入刷新到主内存中。CPU指令读volatile变量之前,强行要求cpu执行先读主内存该变量的最新值过来。这样就能读到其他线程修改后的最新值。

     看volatile的源码些微有点麻烦(需要对java代码进行javac编译,然后对.class文件进行javap处理),最后发现代码是hpp,汇编语言写的。不同操作系统实现不一样,比如jdk 8 linux x86是这个

        往细的讲,volatile为了保证变量的可见性,在java编译器编译代码指令时,对volatile修饰变量的读和写操作,都会在这个操作的前后插入屏障指令。

修改前,插入storestore屏障指令。

修改后,再次插入一个storeload屏障指令。

读前,插入一个loadStore屏障指令。

读后,插入一个loadload屏障指令。

4.volatile的缺点-原子性问题

     比如两个线程对一个volatile修饰的count字段,进行2w次++,由于原子性问题,导致结果并不是20000.


package lading.java.mutithread;
/**
 * volatile关键字存在原子性问题
 * 对volatile修饰的count进行20000次并发+1,预期结果是20000,但由于原子性问题,与预期不符
 */
public class Demo003VolatileBug {
    public static volatile int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            //线程1对count进行10000次+1
            for (int i = 0; i < 10000; i++) {
                count++;
            }
            System.out.println(Thread.currentThread().getName()+"完成了对count10000次+1");
        });
        Thread thread2 = new Thread(() -> {
            //线程2,也对count进行100次+1
            for (int i = 0; i < 10000; i++) {
                count++;
            }
            System.out.println(Thread.currentThread().getName()+"完成了对count10000次+1");
        });
        thread1.start();
        thread2.start();
        Thread.sleep(1000);
        System.out.println("两个子线程执行完毕后,count的值是:"+count);
    }
}

结果,果然不是2w,只有12977。

5.volatile怎么用更科学

     像4的示例,volatile修饰的count并发++2w次,结果出现原子性问题。可以通过使用CAS类来解决,比如两个都是轻量级方案,无锁,效率很高。

public static volatile AtomicInteger count = new AtomicInteger(0);
   //使用getAndIncrement 替换++
   count.getAndIncrement();

以及搭配ReentrantLock可重入锁进行加锁处理,确保解决原子性问题。


相关文章
|
4天前
|
Kubernetes 负载均衡 Java
k8s的出现解决了java并发编程胡问题了
Kubernetes通过提供自动化管理、资源管理、服务发现和负载均衡、持续交付等功能,有效地解决了Java并发编程中的许多复杂问题。它不仅简化了线程管理和资源共享,还提供了强大的负载均衡和故障恢复机制,确保应用程序在高并发环境下的高效运行和稳定性。通过合理配置和使用Kubernetes,开发者可以显著提高Java应用程序的性能和可靠性。
48 31
|
5天前
|
存储 缓存 安全
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是写出高端的CRUD应用。2025年,我正在沉淀自己,博客更新速度也在加快。在这里,我会分享关于Java并发编程的深入理解,尤其是volatile关键字的底层原理。 本文将带你深入了解Java内存模型(JMM),解释volatile如何通过内存屏障和缓存一致性协议确保可见性和有序性,同时探讨其局限性及优化方案。欢迎订阅专栏《在2B工作中寻求并发是否搞错了什么》,一起探索并发编程的奥秘! 关注我,点赞、收藏、评论,跟上更新节奏,让我们共同进步!
70 8
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
|
5天前
|
Java 编译器 开发者
注解的艺术:Java编程的高级定制
注解是Java编程中的高级特性,通过内置注解、自定义注解及注解处理器,可以实现代码的高度定制和扩展。通过理解和掌握注解的使用方法,开发者可以提高代码的可读性、可维护性和开发效率。在实际应用中,注解广泛用于框架开发、代码生成和配置管理等方面,展示了其强大的功能和灵活性。
52 25
|
6天前
|
Java 开发工具
课时6:Java编程起步
课时6:Java编程起步,主讲人李兴华。课程摘要:介绍Java编程的第一个程序“Hello World”,讲解如何使用记事本或EditPlus编写、保存和编译Java源代码(*.java文件),并解释类定义、主方法(public static void main)及屏幕打印(System.out.println)。强调类名与文件名一致的重要性,以及Java程序的编译和执行过程。通过实例演示,帮助初学者掌握Java编程的基本步骤和常见问题。
|
7天前
|
设计模式 存储 SQL
【Java并发】【volatile】适合初学者体质的volatile
当你阅读dalao的框架源码的时候,你是否会见到这样一个关键字 - - - volatie,诶,你是否会好奇,为什么要加它?加了它有什么作用?
46 14
【Java并发】【volatile】适合初学者体质的volatile
|
16天前
|
缓存 安全 Java
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
39 21
|
1月前
|
存储 算法 Java
【JAVA】生成accessToken原理
在Java中,生成accessToken用于身份验证和授权,确保合法用户访问受保护资源。流程包括:1. 身份验证(如用户名密码、OAuth 2.0);2. 生成唯一且安全的令牌;3. 设置令牌有效期并存储;4. 客户端传递令牌,服务器验证其有效性。常见场景为OAuth 2.0协议,涉及客户端注册、用户授权、获取授权码和换取accessToken。示例代码展示了使用Apache HttpClient库模拟OAuth 2.0获取accessToken的过程。
|
1月前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
3月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
3月前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析