关于volatile:深入理解关键字volatile

volatile 关键字的作用是什么?相比于 synchronized 关键字(重量级锁)对性能影响较大,Java提供了一种较为轻量级的可见性和有序性问题的解决方案,那就是应用 volatile 关键字。因为应用 volatile 不会引起上下文的切换和调度,所以 volatile 对性能的影响较小,开销较低。 从并发三要素的角度看,volatile 能够保障其润饰的变量的可见性和有序性,无奈保障原子性(不能保障齐全的原子性,只能保障单次读/写操作具备原子性,即无奈保障复合操作的原子性)。 上面将从并发三要素的角度介绍 volatile 如何做到可见和有序的。 1. volatile 如何实现可见性?什么是可见性?可见性指当多个线程同时访问共享变量时,一个线程对共享变量的批改,其余线程能够立刻看到(即任意线程对共享变量操作时,变量一旦扭转所有线程立刻能够看到)。 1.1 可见性例子/** * volatile 可见性例子 * @author 单程车票 */public class VisibilityDemo { // 结构共享变量 public static boolean flag = true;// public static volatile boolean flag = true; // 如果应用volatile润饰则能够终止循环 public static void main(String[] args) { // 线程1更改flag new Thread(() -> { // 睡眠3秒确保线程2启动 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} // 批改共享变量 flag = false; System.out.println("批改胜利,以后flag为true"); }, "one").start(); // 线程2获取更新后的flag终止循环 new Thread(() -> { while (flag) { } System.out.println("获取到批改后的flag,终止循环"); }, "two").start(); }}不应用 volatile 润饰 flag 变量时,运行程序会进入死循环,也就是说线程1对 flag 的批改并没有被线程2读到,也就是说这里的flag并不具备可见性。应用 volatile 润饰 flag 变量时,运行程序会终止循环,打印提醒语句,阐明线程2读到了线程1批改后的数据,也就是说被 volatile 润饰的变量具备可见性。*1.2 volatile 如何保障可见性?被 volatile 润饰的共享变量 flag 被一个线程批改后,JMM(Java内存模型)会把该线程的CPU内存中的共享变量 flag 立刻强制刷新回主存中,并且让其余线程的CPU内存中的共享变量 flag 缓存生效,这样当其余线程须要拜访该共享变量 flag 时,就会从主存获取最新的数据。 ...

March 20, 2023 · 3 min · jiezi

关于volatile:Java中不可或缺的关键字volatile

什么是volatile关键字volatile是Java中用于润饰变量的关键字,其能够保障该变量的可见性以及程序性,然而无奈保障原子性。更精确地说是volatile关键字只能保障单操作的原子性, 比方x=1 ,然而无奈保障复合操作的原子性,比方x++ 其为Java提供了一种轻量级的同步机制:保障被volatile润饰的共享变量对所有线程总是可见的,也就是当一个线程批改了一个被volatile润饰共享变量的值,新值总是能够被其余线程立刻得悉。相比于synchronized关键字(synchronized通常称为重量级锁),volatile更轻量级,开销低,因为它不会引起线程上下文的切换和调度。 保障可见性可见性:是指当多个线程拜访同一个变量时,一个线程批改了这个变量的值,其余线程可能立刻看到批改的值。咱们一起来看一个例子: public class VisibilityTest { private boolean flag = true; public void change() { flag = false; System.out.println(Thread.currentThread().getName() + ",已批改flag=false"); } public void load() { System.out.println(Thread.currentThread().getName() + ",开始执行....."); int i = 0; while (flag) { i++; } System.out.println(Thread.currentThread().getName() + ",完结循环"); } public static void main(String[] args) throws InterruptedException { VisibilityTest test = new VisibilityTest(); // 线程threadA模仿数据加载场景 Thread threadA = new Thread(() -> test.load(), "threadA"); threadA.start(); // 让threadA执行一会儿 Thread.sleep(1000); // 线程threadB 批改 共享变量flag Thread threadB = new Thread(() -> test.change(), "threadB"); threadB.start(); }}其中:threadA 负责循环,threadB负责批改 共享变量flag,如果flag=false时,threadA 会完结循环,然而下面的例子会死循环! 起因是threadA无奈立刻读取到共享变量flag批改后的值。 咱们只需private volatile boolean flag = true;,加上volatile关键字threadA就能够立刻退出循环了。 ...

January 6, 2023 · 3 min · jiezi

关于volatile:volatilesynchronized可见性有序性原子性代码证明基础硬核

0.简介前一篇文章《Synchronized用法原理和锁优化降级过程》从面试角度详细分析了synchronized关键字原理,本篇文章次要围绕volatile关键字用代码剖析下可见性,原子性,有序性,synchronized也辅助证实一下,来加深对锁的了解。** 1.可见性 1.1 不可见性A线程操作共享变量后,该共享变量对线程B是不可见的。咱们来看上面的代码。 package com.duyang.thread.basic.volatiletest;/** * @author :jiaolian * @date :Created in 2020-12-22 10:10 * @description:不可见性测试 * @modified By: * 公众号:叫练 */public class VolatileTest { private static boolean flag = true; public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(() -> { while (flag){ //留神在这里不能有输入 }; System.out.println("threadA over"); }); threadA.start(); //休眠100毫秒,让线程A先执行 Thread.sleep(100); //主线程设置共享变量flag等于false flag = false; }}上述代码中,在主线程中启动了线程A,主线程休眠100毫秒,目标是让线程A先执行,主线程最初设置共享变量flag等于false,控制台没有输入后果,程序死循环没有完结不了。如下图所示主线程执行完后flag = false后Java内存模型(JMM),主线程把本人工作内存的flag值设置成false后同步到主内存,此时主内存flag=false,线程A并没有读取到主内存最新的flag值(false),主线程执行结束,线程A工作内存始终占着cpu工夫片不会从主内存更新最新的flag值,线程A看不到主内存最新值,A线程应用的值和主线程应用值不统一,导致程序凌乱,这就是线程之间的不可见性,这么说你应该能明确了。线程间的不可见性是该程序死循环的根本原因。 1.2 volatile可见性上述案例中,咱们用代码证实了线程间的共享变量是不可见的,其实你能够从上图得出结论:只有线程A的工作内存可能感知主内存中共享变量flag的值发生变化就好了,这样就能把最新的值更新到A线程的工作内存了,你只有能想到这里,问题就曾经完结了,没错,volatile关键字就实现了这个性能,线程A能感知到主内存共享变量flag产生了变动,于是强制从主内存读取到flag最新值设置到本人工作内存,所以想要VolatileTest代码程序失常完结,用volatile关键字润饰共享变量flag,private volatile static boolean flag = true;就功败垂成。volatile底层实现的硬件根底是基于硬件架构和缓存一致性协定。如果想深刻下,能够翻看上一篇文章《可见性是什么?(通俗易懂)》。肯定要试试才会有播种哦! ...

December 22, 2020 · 5 min · jiezi

关于volatile:面经手册-第14篇volatile-怎么实现的内存可见没有-volatile-一定不可见吗

作者:小傅哥博客:https://bugstack.cn 积淀、分享、成长,让本人和别人都能有所播种!????一、码场心得 你是个能享乐的人吗? 从前的能享乐大多指的体力劳动的苦,但当初的能享乐曾经包含太多维度,包含:读书学习&寂寞的苦、深度思考&脑力的苦、自律习惯&修行的苦、自控能力&放弃的苦、抬头做人&尊严的苦。 尽管这些苦摆在眼前,但大多数人还是喜爱吃简略的苦。熬夜加班、日复一日、反复昨天、CRUD,最初身材发胖、体质降落、能力有余、自抱自泣!所以有些苦能不吃就不吃,要吃就吃那些有成长价值的苦。 明天你写博客了吗? 如果一件小事能保持5年以上,那你肯定是很了不起的人。是的,很了不起。人最难的就是想分明了但做不到,或者偶然做到长期做不到。 其实大多数走在研发路上的搭档们,都晓得本人该致力,但明明下好了的信心就是保持不了多久。就像你是否也想过要写技术博客,做技术积攒。直到有一天被瓶颈限度在困局中才会焦急,但这时候在想破局就真的很难了! 二、面试题谢飞机,小记,飞机趁着周末,吃完火锅。又去约面试官喝茶了! 谢飞机:嗨,我在这,这边,这边。 面试官:你怎么又来了,最近学的不错了? 谢飞机:还是想来大厂,别害羞,面我吧! 面试官:我如同是你补课老师... 既然来了,就问问你吧!volatile 是干啥的? 谢飞机:啊,volatile 是保障变量对所有线程的可见性的。 面试官:那 volatile 能够解决原子性问题吗? 谢飞机:不能够! 面试官:那 volatile 的底层原理是如何实现的呢? 谢飞机:...,这!面试官,刚问两个题就甩雷,你是不家里有事要忙? 面试官:你管我! 三、volatile 解说1. 可见性案例public class ApiTest { public static void main(String[] args) { final VT vt = new VT(); Thread Thread01 = new Thread(vt); Thread Thread02 = new Thread(new Runnable() { public void run() { try { Thread.sleep(3000); } catch (InterruptedException ignore) { } vt.sign = true; System.out.println("vt.sign = true 告诉 while (!sign) 完结!"); } }); Thread01.start(); Thread02.start(); }}class VT implements Runnable { public boolean sign = false; public void run() { while (!sign) { } System.out.println("你坏"); }}这段代码,是两个线程操作一个变量,程序冀望当 sign 在线程 Thread01 被操作 vt.sign = true 时,Thread02 输入 你坏。 ...

October 22, 2020 · 3 min · jiezi

关于volatile:理解Volatile关键字其实看这一篇就够了写的非常细致

前言volatile是Java虚拟机提供的轻量级的同步机制。 volatile关键字作用是什么?两个作用: 1.保障被volatile润饰的共享变量对所有线程总数可见的,也就是当一个线程批改了一个被volatile润饰共享变量的值,新值总是能够被其余线程立刻得悉。 2.禁止指令重排序优化。 volatile的可见性对于volatile的可见性作用,咱们必须意识到被volatile润饰的变量对所有线程总数立刻可见的,对volatile变量的所有写操作总是能立即反馈到其余线程中; 上面来测试一下,此时的还未initFlag被volatile润饰。 private boolean initFlag = false; public void test() throws InterruptedException{ Thread threadA = new Thread(() -> { while (!initFlag) { } String threadName = Thread.currentThread().getName(); System.out.println("线程" + threadName+"获取到了initFlag扭转后的值"); }, "threadA"); //线程B更新全局变量initFlag的值 Thread threadB = new Thread(() -> { initFlag = true; }, "threadB"); //确保线程A先执行 threadA.start(); Thread.sleep(2000); threadB.start();}执行后果:控制台只打印了 "线程threadB扭转了initFlag的值",且程序并未终止。 此时initFlag曾经被volatile关键字润饰了 private volatile boolean initFlag = false; public void test() throws InterruptedException{ Thread threadA = new Thread(() -> { while (!initFlag) { } String threadName = Thread.currentThread().getName(); System.out.println("线程" + threadName+"获取到了initFlag扭转后的值"); }, "threadA"); Thread threadB = new Thread(() -> { initFlag = true; String threadName = Thread.currentThread().getName(); System.out.println("线程" + threadName+"扭转了initFlag的值"); }, "threadB"); //确保线程A先执行 threadA.start(); Thread.sleep(2000); threadB.start();}执行后果: ...

August 27, 2020 · 2 min · jiezi

关于volatile:volatile域的语义及其实现

0.背景-缓存一致性依据维基百科的定义:在一个共享内存多处理器零碎中,每个处理器都有一个独自的缓存,能够有很多共享数据正本:一个在主内存中,一个在每个申请它的处理器的本地缓存中。 当一个数据正本被更改时,其余正本必须反映该更改。 缓存一致性是确保共享操作数(数据)值的更改及时在整个零碎中流传的学科。上面图1是缓存不统一的示例图,图2是缓存统一的示例图其实Java的volatile某种意义上也是来解决这种缓存不统一的状况。 更多缓存一致性的常识,能够参看维基百科的词条,也能够看medium上的这篇文章 1.JMM提供的volatile域的语义1.1 可见性依据JSR-133 FAQ中的阐明,volatile字段是用于在线程之间传递状态的非凡字段。 每次读取volatile时,都会看到任意一个线程对该volatile的最初一次写入。 实际上,程序员将volatile字段指定为不能承受因为缓存或重排序而导致的“过期”值的字段。 禁止编译器和运行时在寄存器中调配它们。 它们还必须确保在写入后将其从高速缓存(cache)中刷新到主存(memory),以便它们能够立刻对其余线程可见。 同样,在读取volatile字段之前,必须使高速缓存有效,以便能够看到主内存中的值而不是本地处理器高速缓存中的值。 也就是说每次读取volatile都是从主存读取,写入也会刷新到主存,因此保障了不同线程拿到的都是最新值,即保障了共享资源对各个CPU上的线程的可见性,这其实就是保障了缓存一致性。 1.2. 重排序限度在旧的内存模型下(Java1.5之前),对volatile变量的访问不能互相重排序,但能够与nonvolatile变量拜访一起重排序。 这毁坏了volatile字段作为从一个线程到另一线程发信号告诉状态的一种伎俩。 在新的内存模型下(Java1.5及之后),volatile变量不能互相从新排序依然为true。区别在于,当初对它们四周的失常字段拜访进行重排序不再那么容易了。 写入一个volatile 字段具备与monitor开释雷同的存储成果,而从一个volatile 字段中读取具备与monitor获取雷同的存储成果。 实际上,因为新的内存模型对volatile 字段拜访与其余字段拜访(无论是否为易失性)的从新排序施加了更严格的束缚,因而当线程A写入volatile 字段f时,对线程A可见的任何内容,这些内容在线程B读取f时都可见。 这是一个如何应用易失性字段的简略示例: class VolatileExample { int x = 0; volatile boolean v = false; public void writer() { x = 42; v = true; } public void reader() { if (v == true) { //uses x - guaranteed to see 42. } }}假设一个线程在调用writer办法,而另一个在调用reader办法。在writer办法中对v的写操作,会将对x的写操作也更新到主存中,而对v的读操作则从主存中获取该值。 因而,如果reader办法看到v的值为true,那么也保障能够看到在它之前产生的对42的写入。 ...

August 8, 2020 · 3 min · jiezi