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可重入锁进行加锁处理,确保解决原子性问题。


相关文章
|
10天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
14天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
49 12
|
10天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
11天前
|
监控 Java API
探索Java NIO:究竟在哪些领域能大显身手?揭秘原理、应用场景与官方示例代码
Java NIO(New IO)自Java SE 1.4引入,提供比传统IO更高效、灵活的操作,支持非阻塞IO和选择器特性,适用于高并发、高吞吐量场景。NIO的核心概念包括通道(Channel)、缓冲区(Buffer)和选择器(Selector),能实现多路复用和异步操作。其应用场景涵盖网络通信、文件操作、进程间通信及数据库操作等。NIO的优势在于提高并发性和性能,简化编程;但学习成本较高,且与传统IO存在不兼容性。尽管如此,NIO在构建高性能框架如Netty、Mina和Jetty中仍广泛应用。
26 3
|
11天前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2100 3
|
11天前
|
安全 算法 Java
Java CAS原理和应用场景大揭秘:你掌握了吗?
CAS(Compare and Swap)是一种乐观锁机制,通过硬件指令实现原子操作,确保多线程环境下对共享变量的安全访问。它避免了传统互斥锁的性能开销和线程阻塞问题。CAS操作包含三个步骤:获取期望值、比较当前值与期望值是否相等、若相等则更新为新值。CAS广泛应用于高并发场景,如数据库事务、分布式锁、无锁数据结构等,但需注意ABA问题。Java中常用`java.util.concurrent.atomic`包下的类支持CAS操作。
43 2
|
11天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
92 2
|
27天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
28天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
50 3
|
2月前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin