Java基础-关键字:volatile

简介: 观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现;加入volatile关键字时,会多出一个lock前缀指令。lock前缀指令实际上相当于一个内存屏障。内存屏障会提供3个功能:确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。强制将对缓存的修改操作立即写入主存。如果是写操作,它会导致其他CPU中对应的缓存行无效。

基础性问题

1、缓存一致性问题
程序运行,临时数据存放主存(物理内存)当中
CPU执行速率 远大于 数据在内存中读写速度
因此CPU里面就有了高速缓存
多个线程访问的变量为共享变量,存在缓存一致性问题

2、硬件层面解决方法:总线加LOCK#锁的方式、缓存一致性协议
总线加LOCK#锁的方式
对总线加锁,阻塞其他CPU对内存的访问,只能又一个CPU访问这个内存变量。
缺点:锁总线期间,其他CPU无法访问内存,导致效率地下。

缓存一致性协议
Intel的MESI协议:保证每个缓存中使用的共享变量的副本是一致的。

3、并发编程的三个基本概念:原子性,可见性,有序性
原子性:一个操作或者多个操作 要么全部执行,要么就都不执行。
可见性:多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:程序执行的顺序按照代码的先后顺序执行。

指令重排序:不会影响单个线程的执行,但是会影响到线程并发执行的正确性。
并发程序要正确地执行,必须要保证原子性、可见性以及有序性。任一条件没有被保证,就有可能会导致程序运行不正确。

Java内存模型

Java内存模型存在:缓存一致性问题 和 指令重排序问题。

所有的变量都是存在主存当中(类似于物理内存)
每个线程都有自己的工作内存(类似于高速缓存)
线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。
并且每个线程不能访问其他线程的工作内存。

原子性:
对基本数据类型的变量的读取和赋值操作是原子性操作
要实现更大范围操作的原子性,可以通过synchronized和Lock来实现

可见性:
Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存;
当有其他线程需要读取时,它会去内存中读取新值。
普通的共享变量不能保证可见性。通过synchronized和Lock也能够保证可见性。

有序性:
在Java内存模型中,允许编译器和处理器对指令进行重排序;
重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在Java里面,可以通过volatile关键字来保证一定的“有序性”。
可以通过synchronized和Lock来保证有序性,他保证每个时刻是有一个线程执行同步代码。

关键字:volatile : 保证可见性,不保证原子性,一定程度上保证有序性

1、保证可见性
示例代码如下:

public volatile static int inc = 1;
public static void main(String[] args) {
    new Thread() {
        public void run() {
            while(inc == 1) { }
        }
    }.start();
    
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) { }
    inc = 0;
    System.err.println("main is Over ... " + inc);
}

不加关键字volatile的时候:主线程修改变量,不会更新到子线程;子线程不会退出。
加入关键字,主线程更新到主存,子线程也去主存读取;子线程会退出。

2、不保证原子性
示例代码如下:

public static volatile int inc = 0;
public static void increase() { inc++; }
public static void main(String[] args) {
    for(int i = 0; i < 10; i++) {
        new Thread() {
            public void run() {
                for(int j = 0; j < 1000; j++) {
                    increase();
                }
            }
        }.start();
    }
    while(Thread.activeCount() > 1) Thread.yield();
    System.err.println(inc);
}

10个线程,每个线程对变量自加1,自加1000次;最终结果应该是:10000。
但是最终结果总是小于10000 。

如何保证原子性 ?
答:
(1)对方法 increase 同步锁:synchronized , 输出结果为: 10000 。
(2)定义锁 Lock ,更新方法 increase,输出结果为: 10000 。
如下代码:

public static Lock lock = new ReentrantLock();
public static void increase() { 
    lock.lock();
    try {
        inc++; 
    } finally {
        lock.unlock();
    }
}

(3)采用原子性的包装对象: AtomicInteger。 如下代码:

public static volatile AtomicInteger inc = new AtomicInteger();
public static void increase() {
    inc.getAndIncrement();
}

在java 1.5的java.util.concurrent.atomic包下提供了一些原子操作类
对基本数据类型的 自增,自减、以及加法操作,减法操作 进行了封装
atomic是利用CAS来实现原子性操作的(Compare And Swap)
CAS实际上是利用处理器提供的CMPXCHG指令实现的

3、一定程度上保证有序性
volatile关键字能禁止指令重排序,所以volatile能在一定程度上保证有序性

观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现;
加入volatile关键字时,会多出一个lock前缀指令
lock前缀指令实际上相当于一个内存屏障
内存屏障会提供3个功能:
确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。
强制将对缓存的修改操作立即写入主存。
如果是写操作,它会导致其他CPU中对应的缓存行无效。

相关文章
|
7月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
248 0
|
9月前
|
设计模式 存储 SQL
【Java并发】【volatile】适合初学者体质的volatile
当你阅读dalao的框架源码的时候,你是否会见到这样一个关键字 - - - volatie,诶,你是否会好奇,为什么要加它?加了它有什么作用?
244 14
【Java并发】【volatile】适合初学者体质的volatile
|
9月前
|
存储 缓存 安全
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是写出高端的CRUD应用。2025年,我正在沉淀自己,博客更新速度也在加快。在这里,我会分享关于Java并发编程的深入理解,尤其是volatile关键字的底层原理。 本文将带你深入了解Java内存模型(JMM),解释volatile如何通过内存屏障和缓存一致性协议确保可见性和有序性,同时探讨其局限性及优化方案。欢迎订阅专栏《在2B工作中寻求并发是否搞错了什么》,一起探索并发编程的奥秘! 关注我,点赞、收藏、评论,跟上更新节奏,让我们共同进步!
391 8
【原理】【Java并发】【volatile】适合初学者体质的volatile原理
|
10月前
|
缓存 安全 Java
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
271 21
|
9月前
|
Java C语言
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
193 4
|
8月前
|
存储 安全 Java
深入理解 Java 中的 instanceof 关键字
本文深入解析了 Java 中的 `instanceof` 关键字,探讨其在类型判断中的作用。作为二元操作符,`instanceof` 可用于检查对象是否为某类实例或实现特定接口,避免类型转换异常 (`ClassCastException`)。文章通过多态性下的类型判断、安全类型转换、接口实现检测及集合元素类型判定等实际应用场景,展示了 `instanceof` 的强大功能。掌握该关键字可提高代码健壮性,确保运行时类型安全。
591 0
|
12月前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
337 5
Java 并发编程——volatile 关键字解析
|
11月前
|
Java 编译器 开发者
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
1739 9
|
11月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
12月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
296 7