JUC(一):volatile关键字
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同步规定
线程解锁前,必须把共享变量的值刷回主内存。
线程加锁前,必须读取主内存内最新值到线程的工作内存中。
加锁与解锁是同一把锁。
由于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();可以分为一下三步完成。
- memory = allocate(); 1. 分配对象内存空间。
 - INSTANCE -> memory ; 2. 初始化对象。
 - INSTANCE = ; 3. 设置INSTANCE指向刚分配的内存地址,此时INSTANCE != null。
 
步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果再单线程中并没有变化,因此这种重排优化时允许的。
- memory = allocate(); 1. 分配对象内存空间。
 - INSTANCE = ; 3. 设置INSTANCE指向刚分配的内存地址,此时INSTANCE != null,但对象还没有初始化完成。
 - INSTANCE -> memory ; 2. 初始化对象。
 
指令重排只会保证串行语义执行的一致性(单线程),但是并不会关心多线程间的语义一致性。所有当一个线程访问INSTANCE不为null时,由于INSTANCE实例未必已经初始化完成,也就造成了线程安全问题。
JUC(一):volatile关键字的更多相关文章
- volatile关键字与内存可见性&原子变量与CAS算法
		
1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...
 - 【JUC系列第一篇】-Volatile关键字及内存可见性
		
作者:毕来生 微信:878799579 什么是JUC? JUC全称 java.util.concurrent 是在并发编程中很常用的实用工具类 2.Volatile关键字 1.如果一个变量被volat ...
 - JUC 并发编程--05, Volatile关键字特性: 可见性, 不保证原子性,禁止指令重排, 代码证明过程. CAS了解么 , ABA怎么解决, 手写自旋锁和死锁
		
问: 了解volatile关键字么? 答: 他是java 的关键字, 保证可见性, 不保证原子性, 禁止指令重排 问: 你说的这三个特性, 能写代码证明么? 答: .... 问: 听说过 CAS么 他 ...
 - Volatile关键字以及线程的内存可见性问题
		
一.Volatile关键字 作用: 当多个线程进行操作共享数据时,可以保证内存中的数据可见,即为一个线程对数据的修改对另外一个线程来说是可见的.相较于 synchronized 是一种较为轻量级的同步 ...
 - volatile关键字与内存可见性
		
前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开 ...
 - volatile关键字及内存可见性
		
先看一段代码: package com.java.juc; public class TestVolatile { public static void main(String[] args) { T ...
 - Java volatile关键字小结
		
public class Test { public static void main(String[] args){ } } /* 12.3 Java内存模型 Java内存模型定义了线程与主内存之间 ...
 - 1.volatile关键字 内存可见性
		
Java JUC 简介 在 Java 5.0 提供了 java.util.concurrent (简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括 ...
 - 最后一面挂在volatile关键字上,面试官:重新学学Java吧!
		
最后一面挂在volatile关键字上,面试官:重新学学Java吧! 为什么会有volatile关键字? volatile: 易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; ...
 
随机推荐
- FTP的PORT(主动模式)和PASV(被动模式)
			
最近做一个项目用到FTP和其它系统进行文件传输,结果在FTP网络连接的问题上花了很多时间,由于太久没搞多FTP,忘记了FTP不单单开放21端口,客户端采用不同连接模式对网络有不同.在此重温一下FTP的 ...
 - tp5配置引入使用redis
			
1.首先你的php得是已经安装了redis扩展的 2.在tp里找到config.php配置文件,找到cache,改成下面的样子 'cache' => [ // 选择模式 'type' => ...
 - cmd编译java代码为什么总是说找不到main方法;请园子里大神指点迷津!!!
			
编写源代码如下: cmd,编译路径:E: cd Notepad cd src javac Character.java jvav Character 运行结果: 实在是找不到问题点,请评论区给予指导啊 ...
 - 面试阿里,美团,京东都会被问到的Spring ,从基础到源码帮你全搞定
			
1 前言 Spring是一个轻量级开源框架,它是为了解决企业应用开发的复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框 ...
 - CDR征稿-CorelDRAW征文活动开始啦!
			
学习平面设计的小伙伴对CorelDRAW一定不陌生,CorelDRAW和Photoshop.illustrator以及 InDesign是做设计必备的四款工具.如果您是CorelDRAW专家.培训讲师 ...
 - wraps装饰器的使用
			
functools模块中的wraps装饰器 说明 使用functools模块提供的wraps装饰器可以避免被装饰的函数的特殊属性被更改,如函数名称__name__被更改.如果不使用该装饰器,则会导致函 ...
 - 4. Eclipse集成Git
			
4.1 Git插件 Eclipse中已经内置了Git插件 4.2 把工程初始化为本地库 初始化 设置本地库范围的签名 4.3 Git图标 4.4 Eclipse特定文件 Eclispe特定文件介绍 为 ...
 - Java基础教程——打印流
			
打印流 打印流可以把原本输出到控制台的信息输出到文件中.PrintStream是字节打印流(还有个对应的字符打印流是PrintWriter,这里不涉及) System类中有个变量: public fi ...
 - MySQL制作具有千万条测试数据的测试库
			
有时候需要制造一些测试的数据,以mysql官方给的测试库为基础,插入十万,百万或者千万条数据.利用一些函数和存储过程来完成. 官方给的测试库地址:https://github.com/datachar ...
 - fist-第五天冲刺随笔
			
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...