volatile是什么

volatile是java虚拟机提供的轻量级同步机制,它包含三种特性:

  • 保证可见性:只要主内存中变量做出修改,其余线程马上会感知到变量的修改。
package com.chinda.java.audition;

import java.util.concurrent.TimeUnit;

class MyData {
volatile int number = 0;
public void addTo60 (){
this.number = 60;
}
} /**
* 0. 先不添加volatile关键字, 执行是否会阻塞; 之后加上volatile关键字, 最后打印会不会执行。
* 1.验证volatile可见性
* 1.1 假如int number = 0; number变量没有添加volatile关键词修饰没有可见性
* 1.2 添加volatile,可以解决可见性问题。
*
* @author Wang Chinda
* @date 2020/5/2
* @see
* @since 1.0
*/
public class VolatileDemo {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t come in!");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
myData.addTo60();
System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
}, "T1").start(); while (myData.number == 0) {
// 主线程跳出此循环, 说明线程时可见, 若阻塞在此循环, 说明线程不可见。
}
System.out.println(Thread.currentThread().getName() + "\t mission is over! numer value: " + myData.number);
}
}
  • 不保证原子性:
package com.chinda.java.audition;

import java.util.concurrent.atomic.AtomicInteger;

class MyData1 {
volatile int number = 0;
AtomicInteger atomic = new AtomicInteger(); public void addPlus() {
this.number++;
} public void addAtomic() {
atomic.getAndIncrement();
} } /**
* 1.验证volatile不保证原子性
* 1.1 原子性: 即某个线程正在做某个具体业务, 中间不可以被加塞或者被分割。需要整体完整,要么同时成功,要么同时失败。
* 1.2 一个线程将属性值写回主内存,没有来得及通知其他线程时, 有其他线程也要将属性值写回主内存, 属性值在主内存中出现覆盖现象。所以volatile不保证原子性。
* 2. 解决非原子性问题。
* 2.1 添加synchronized
* 2.2 添加锁lock
* 2.3 用原子类AtomicInteger
*
* @author Wang Chinda
* @date 2020/5/2
* @see
* @since 1.0
*/
public class VolatileDemo1 {
public static void main(String[] args) {
MyData1 myData = new MyData1(); for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
myData.addPlus();
myData.addAtomic();
}
}, String.valueOf(i)).start();
} // 等待上面20个线程全部计算完成后, 再用main线程取得最终的计算结果。
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "\t finally! numer value: " + myData.number);
System.out.println(Thread.currentThread().getName() + "\t finally! atomic value: " + myData.atomic.get());
}
}
  • 禁止指令重排

JMM概念

​ JMM本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段、静态字段、构成数组对象的元素)的访问方式。包含三种特性:

  • 可见性
  • 原子性
  • 有序性

JMM同步规定

  1. 线程解锁前,必须把共享变量的值刷回主内存。

  2. 线程加锁前,必须读取主内存内最新值到线程的工作内存中。

  3. 加锁与解锁是同一把锁。

    由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,工作内存时内阁线程私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存时共享内存区域,所有线程都可以访问,但线程对变量的操作必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成将变量写回主内存。

volatile修饰JMM

  

volatile修饰时,主内存中属性修改,各线程中是不可见的。各个线程中工作内存只以运算前在主内存中拷贝的属性值为准。

volatile修饰JMM

  

volatile修饰时,主内存中的属性修改,各个线程中是可见的,其中一个线程将工作内存中的属性写回主内存中会通知各个线程去同步主内存中的属性值。但是若在通知之前已经将工作内存中的属性修改,再收到通知同步,这时候就会出现属性值覆盖情况。例如上图中,你品,你细品。即volatile可保证线程间的可见性,但是不保证线程中数据的原子性。

单例模式双端检锁线程安全

DCL(双端检锁)机制不一定线程安全,原因是指令重排序的存在,加入volatile可以禁止指令指令重排。

package com.chinda.java.audition;

/**
* @author Wang Chinda
* @date 2020-02-16 21:27
* @see
* @since 1.0
*/
public class Singleton5 {
private static volatile Singleton5 INSTANCE; private Singleton5() {
System.out.println("线程: " + Thread.currentThread().getName() + " --> 此行代码被执行多次, 说明线程不安全!!!");
} public static Singleton5 getInstance() throws InterruptedException { // 为提升效率
if (INSTANCE == null) {
// 类锁, 线程每次都会抢锁
synchronized (Singleton5.class) {
if (INSTANCE == null) {
Thread.sleep(100);
INSTANCE = new Singleton5();
}
}
}
return INSTANCE;
} public static void main(String[] args) {
for (int i = 1; i <= 20000; i++) {
new Thread(() -> {
try {
Singleton5.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}

原因在于某一个线程执行到第一次检测,读取到INSTANCE不为null时,INSTANCE的引用对象可能没有完成初始化。

INSTANCE = new Singleton5();可以分为一下三步完成。

  1. memory = allocate(); 1. 分配对象内存空间。
  2. INSTANCE -> memory ; 2. 初始化对象。
  3. INSTANCE = ; 3. 设置INSTANCE指向刚分配的内存地址,此时INSTANCE != null。

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果再单线程中并没有变化,因此这种重排优化时允许的。

  1. memory = allocate(); 1. 分配对象内存空间。
  2. INSTANCE = ; 3. 设置INSTANCE指向刚分配的内存地址,此时INSTANCE != null,但对象还没有初始化完成。
  3. INSTANCE -> memory ; 2. 初始化对象。

指令重排只会保证串行语义执行的一致性(单线程),但是并不会关心多线程间的语义一致性。所有当一个线程访问INSTANCE不为null时,由于INSTANCE实例未必已经初始化完成,也就造成了线程安全问题。

JUC(一):volatile关键字的更多相关文章

  1. volatile关键字与内存可见性&原子变量与CAS算法

    1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...

  2. 【JUC系列第一篇】-Volatile关键字及内存可见性

    作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...

  3. JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁

    问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...

  4. Volatile关键字以及线程的内存可见性问题

    一.Volatile关键字 作用: 当多个线程进行操作共享数据时,可以保证内存中的数据可见,即为一个线程对数据的修改对另外一个线程来说是可见的.相较于 synchronized 是一种较为轻量级的同步 ...

  5. volatile关键字与内存可见性

    前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开 ...

  6. volatile关键字及内存可见性

    先看一段代码: package com.java.juc; public class TestVolatile { public static void main(String[] args) { T ...

  7. Java volatile关键字小结

    public class Test { public static void main(String[] args){ } } /* 12.3 Java内存模型 Java内存模型定义了线程与主内存之间 ...

  8. 1.volatile关键字 内存可见性

    Java JUC 简介 在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括 ...

  9. 最后一面挂在volatile关键字上,面试官:重新学学Java吧!

    最后一面挂在volatile关键字上,面试官:重新学学Java吧! 为什么会有volatile关键字? volatile: 易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; ...

随机推荐

  1. MathType如何输入微分上的点

    作为被老师们青睐的公式编辑器,MathType可以帮助插入各种数学符号和编辑数学公式,从而提高数学试卷的编写效率.但是作为新手,在编辑公式的时候难免有困难,比如就有人问:如何输入微分上的点?其实也是有 ...

  2. 考研党其实可以用思维导图MindManager做考研复习计划

    近年由于就业压力,个人学历提升等各种原因,考研的人数越来越多了,相对难度也越来越大了,尽管今年研究生招生规模同比去年增加18.9万,但也无法掩盖考研的竞争逐年激烈. 身为考研大军中的预备选手之一,小编 ...

  3. Java蓝桥杯练习题——求小数n位后3个数

    求整数除法小数点后第n位开始的3位数 位数不足的补0,如0.125小数第3位后三位:0.12500→500 输入格式:a b n,空格分开,a是被除数,b是除数,n是小数后的位置 输出格式:3位数字, ...

  4. 小米死磕硬核技术,将扩招5000名工程师,多个领域会使用到C++

    小米MIDC大会2020在北京小米科技园召开,小米集团创始人.集团董事长兼CEO雷军宣布:"重视人才队伍的建设.人才是创新之源,提升技术实力的第一步,就是聚拢最顶尖的人才.小米2021年将在 ...

  5. MySQL的中的全局锁、表级锁、行锁

    MySQL的中的全局锁.表级锁.行锁 学习极客时间-林晓彬老师-MySQL实战45讲 学习整理 全局锁 对整个数据库实例加锁.通过使用Flush tables with read lock (FTWR ...

  6. 使用github actions 完成一些自动化工作

    github actions 是什么? github actions是github的持续集成及自动化工作流服务,使用起来都比较方便.大部分github actions都可以在https://githu ...

  7. 现代富文本编辑器Quill的内容渲染机制

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师.官方网站:devui.designNg组件库:ng-devui(欢迎S ...

  8. PHP后台基本语法使用笔记

    1. PHP中报500错误时如何查看错误信息 //将如下的代码放入PHP的文件中ini_set("display_errors","On"); error_re ...

  9. rest-framework 响应器(渲染器)

    一 作用: 根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件. 用户请求URL:    http://127.0.0.1:8000/test/?format=json    http ...

  10. 20190712_mysql执行sql脚本语句_Failed to open file_error

    要说执行sql脚本语句不难 就下面这条语句: 进入mysql安装目录下的bin目录 然后执行: \mysql –u用户名 –p密码 –D数据库<[sql脚本文件路径全名] 比如: C:\MySQ ...