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中对应的缓存行无效。

相关文章
|
3月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
71 0
|
1月前
|
存储 缓存 Java
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
这篇文章详细介绍了Java中的IO流,包括字符与字节的概念、编码格式、File类的使用、IO流的分类和原理,以及通过代码示例展示了各种流的应用,如节点流、处理流、缓存流、转换流、对象流和随机访问文件流。同时,还探讨了IDEA中设置项目编码格式的方法,以及如何处理序列化和反序列化问题。
67 1
java基础:IO流 理论与代码示例(详解、idea设置统一utf-8编码问题)
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
27天前
|
SQL 缓存 安全
[Java]volatile关键字
本文介绍了Java中volatile关键字的原理与应用,涵盖JMM规范、并发编程的三大特性(可见性、原子性、有序性),并通过示例详细解析了volatile如何实现可见性和有序性,以及如何结合synchronized、Lock和AtomicInteger确保原子性,最后讨论了volatile在单例模式中的经典应用。
34 0
|
2月前
|
缓存 Java 编译器
JAVA并发编程volatile核心原理
volatile是轻量级的并发解决方案,volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性,具体是如何实现可见性和有序性。以及volatile缺点是什么?
|
3月前
|
安全 Java 编译器
Java 中的 volatile 变量
【8月更文挑战第22天】
27 4
|
3月前
|
缓存 安全 Java
Java里为什么单利一定要加volatile呢?
【8月更文挑战第11天】Java里为什么单利一定要加volatile呢?
30 3
|
3月前
|
缓存 安全 Java
Java里volatile底层是如何实现的?
【8月更文挑战第11天】Java里的volatile底层是如何实现的?
28 2
|
4月前
|
存储 SQL Java
Java实现关键字模糊查询的高效方法及实践
实现关键字模糊查询的方法有多种,每种方法都有其适用场景。在选择合适的方法时,应考虑实际需求、数据量大小、性能要求等因素。正则表达式适用于处理简单文本或小数据集;数据库模糊查询适用于存储在RDBMS中的数据;而第三方库,则适合需要进行复杂搜索的大型项目。选用合适的工具,可以有效提升搜索功能的性能和用户体验。
96 6
|
3月前
|
安全 Java
下一篇
无影云桌面