volatile

JAVA多线程(四)--线程同步-Volatile

好久不见. 提交于 2020-03-01 07:36:10
1.cpu cache模型 cpu - 计算机的主内存(RAM), cpu速度变快,主内存没有跟上cpu的步伐,最终导致 cpu的处理速度和主内存的访问速度差距越来越大。 而cpu cache的出现解决了以上问题,同时引入了缓存不一致的问题。 比如:i++ 1)首先读取内存到cpu cache 2)进行i+1 3)将结果刷新到主内存中 在单线程中这个例子不会出现任何问题,但是在多线程的情况下就会出现问题。原因就是:每个线程都有自己的工作内存(本地内存,对应cpu cache模型中的cache),i在多个线程的本地内存中都会存在一个副本。 例:i 初始化为0;有两个操作A和B,都执行i++操作,按道理执行完之后i的值为2,但是就会有可能出现最后的结果i为1的情况,这就是缓存不一致的问题,要解决缓存不一致,我们有以下解决方式: 1)总线加锁 cpu和其他组建通信是通过总线来进行,采用总线加锁,会阻塞其他cpu对其他组件的访问,导致只有一个cpu抢到总线锁能够访问当前数据的内存。 2)通过缓存一致性协议(MESI)保证能够保证每一个缓存中使用到的共享变量副本是一致的,大致思想就是说当cpu操作cache中 的数据时,发现该数据是共享的,会进行如下操作: a. 读取操作,不用作任何处理 b. 写入操作,发出信号通知其他cpu将该变量的cache line置为无效状态

volatile

断了今生、忘了曾经 提交于 2020-02-29 16:33:22
很多人对Volatile都不太了解,其实Volatile是由于编译器优化所造成的一个Bug而引入的关键字。 int a = 10; int b = a; int c = a; 理论上来讲每次使用a的时候都应该从a的地址来读取变量值,但是这存在一个效率问题,就是每次使用a都要去内存中取变量值,然后再通过系统总线传到CPU处理,这样开销会很大。所以那些编译器优化者故作聪明,把a读进CPU的cache里,像上面的代码,假如a在赋值期间没有被改变,就直接从CPU的cache里取a的副本来进行赋值。但是bug也显而易见,当a在赋给b之后,可能a已经被另一个线程改变而重新写回了内存,但这个线程并不知道,依旧按照原来的计划从CPU的cache里读a的副本进来赋值给c,结果不幸发生了。 于是编译器的开发者为了补救这一bug,提供了一个Volatile让开发人员为他们的过失埋单,或者说提供给开发人员了一个选择效率的权利。当变量加上了Volatile时,编译器就老老实实的每次都从内存中读取这个变量值,否则就还按照优化的方案从cache里读。 volatile的本意是一般有两种说法--1.“暂态的”;2.“易变的”。 这两种说法都有可行。但是究竟volatile是什么意思,现举例说明(以Keil-c与a51为例 例子来自Keil FQA),看完例子后你应该明白volatile的意思了,如果还不明白

volatile的原理分析

安稳与你 提交于 2020-02-29 16:16:49
前言:Volatile作为一个多线程开发中的强有力的轻量级的线程协助工具,在实际编程中随处可见,它比synchronized更加轻量和方便,消耗的资源更少,了解Volatile对后面了解多线程有很重要的意义,本篇博客我们就来探究如果在一个字段上加上Volatile,那么它实际上到底起了什么作用?以及是怎么工作的? 本篇博客的目录: 一:工作内存和主内存 二:volatile的两大作用 三:volatile一定是线程安全的吗? 四:Volatile的局限性和适用场景 五:总结 正文开始 一:工作内存和主内存 1.1:它们具有的特点 ①主内存是所有变量的存储地方,这包括所有你看到的变量包括实例变量、静态字段、数组对象的元素 ②工作内存是线程私有的,所有的线程在操作变量(读取或者赋值)的时候都必须在工作内存中完成,而不能在主内存中进行 ③不同的线程之间无法访问对方的工作内存中的变量,线程之间传递值需要在工作内存中进 行 1.2: 图示 该图主要模拟了5个线程的工作内存和主内存之间的交互,可以看出不同线程之间是不可以进行变量交换的,它们公用一个主内存,所有的变量传递都在主内存中进行完成 二:volatile的两大作用 2.1:线程可见性 这里的可见性是指若一个变量被Volatile修饰,那么假如A线程对其进行了修改操作,那么其他线程都会立刻拿到修改后的值

[高并发Java 三] Java内存模型和线程安全

不想你离开。 提交于 2020-02-29 06:33:01
网上很多资料在描述Java内存模型的时候,都会介绍有一个主存,然后每个工作线程有自己的工作内存。数据在主存中会有一份,在工作内存中也有一份。工作内存和主存之间会有各种原子操作去进行同步。 下图来源于 这篇Blog 但是由于Java版本的不断演变,内存模型也进行了改变。本文只讲述Java内存模型的一些特性,无论是新的内存模型还是旧的内存模型,在明白了这些特性以后,看起来也会更加清晰。 1. 原子性 原子性是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。 一般认为cpu的指令都是原子操作,但是我们写的代码就不一定是原子操作了。 比如说i++。这个操作不是原子操作,基本分为3个操作,读取i,进行+1,赋值给i。 假设有两个线程,当第一个线程读取i=1时,还没进行+1操作,切换到第二个线程,此时第二个线程也读取的是i=1。随后两个线程进行后续+1操作,再赋值回去以后,i不是3,而是2。显然数据出现了不一致性。 再比如在32位的JVM上面去读取64位的long型数值,也不是一个原子操作。当然32位JVM读取32位整数是一个原子操作。 2. 有序性 在并发时,程序的执行可能就会出现乱序。 计算机在执行代码时,不一定会按照程序的顺序来执行。 class OrderExample { int a = 0; boolean flag = false;

设计模式之单例模式

五迷三道 提交于 2020-02-29 02:49:41
单例模式是各场景下常用的一种模式。 安全的double check的实现 注意点 构造函数为private修饰,不让外部直接调用创建类 两次if判断instance是否为空。第一层是为了判断如果instance存在直接返回,第二层在synchronized内判断是为了解决当一个线程进入,另外排队等待锁的线程在第一个线程释放锁之后重复创建instance对象。 volatile为了解决禁止指令重排序。instance = new instance 在jvm实际上分为三步,1分配空间,2初始化对象,3分配引用。 在jvm实际执行会存在优化的情况,执行顺序可能有123,132。volatile可以禁止指令重排序。 最后volatile的作用, 禁止指令重排序 内存可见性 来源: oschina 链接: https://my.oschina.net/u/2250599/blog/3176221

单例模式的3种实现方式, 及其性能对比

余生长醉 提交于 2020-02-29 01:26:38
1. 懒汉模式(double check), 线程安全, 效率不高, 可以延迟加载 public class Singleton1 implements Serializable { // 用volatile修饰instance, 禁止指令重排序, 为保证多线程安全 private volatile static Singleton1 instance = null; private Singleton1() { // 防止反射调用私有构造方法(跳过安全检查), 重复实例化实例 if (instance != null) { throw new RuntimeException("error:instance=" + instance); } } public static Singleton1 getInstance() { if (instance == null) { synchronized (Singleton1.class) { if (instance == null) { instance = new Singleton1(); } } } return instance; } private Object readResolve() { return instance; // 防止反序列化重复实例化实例 } } 2. 饿汉模式, 线程安全, 效率高,

Java并发

青春壹個敷衍的年華 提交于 2020-02-28 21:21:01
1、谈谈对volatile的理解 volatile是Java虚拟机提供的轻量级的同步机制。有三个特性:保证可见性;不保证原子性;禁止指令重排。 2、volatile可见性代码验证 代码第5行没有加volatile的执行结果 main线程感知不到number的变化,一直在while循环中。线程不会结束。 代码第5行加上volatile的执行结果 只要有一个线程修改了主物理内存的值,刷新回去的时候,只要是加了volatile关键字的变量,其他线程迅速收到通知,马上拿到主物理内存的最新值。 来源: https://www.cnblogs.com/yangyanbo/p/12379895.html

java锁的内存语义

荒凉一梦 提交于 2020-02-28 18:41:25
锁的获取和释放 线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。 线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。 在共享变量的可见性方面,锁的释放和获取和volatile是类似的。 对volatile变量进行写操作,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。 对volatile变量进行读操作,JMM会把该线程对应的本地内存置为无效。再从主内存中读取共享变量。 锁获取和释放内存处理流程: 主要理解以下几句话: 1.线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。 2.线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。 3.线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。 参考博客:https://www.cnblogs.com/yuanfy008/p/9346925.html 来源: CSDN 作者: qq_43382364 链接: https://blog.csdn.net/qq_43382364/article/details/104558557

多线程之混合锁-SpinWait(聪明的办法:你们一趟趟跑来跑去,不容易,我也没时间。不如等我有空了,我通知你们再来)

僤鯓⒐⒋嵵緔 提交于 2020-02-28 11:16:27
  ConcurrentQueue<T> 源码发现里面没有用到lock,ConcurrentDictionary里面是有lock的,lock的是字典里面每一个key,但是ConcurrentQueue<T> 的线程安全确是用SpinWait对象和volatile关键字来实现。      //用户模式-同步机制(轮训CPU,不用上下文切换,合适等待时间短的操作)==============不切换上下文=====消耗CPU //内核模式构造(需要上下文切换、消耗操作系统资源)===============切换上下文======消耗系统资源 static void Main(string[] args) { var t1 = new Thread(UserModeWait); var t2 = new Thread(HybridSpinWait); Console.WriteLine("运行用户模式 waiting"); t1.Start(); Thread.Sleep(20); //线程1一直循环,直到这里(_isCompleted为True退出循环) _isCompleted = true;//(加了这个关键字volatile,UserModeWait方法才能感知到这里进行了修改) Thread.Sleep(TimeSpan.FromSeconds(1)); _isCompleted

诡异的并发之可见性

自古美人都是妖i 提交于 2020-02-28 09:26:45
我们都知道,随着祖国越来越繁荣昌盛,随着科技的进步,设备的更新换代,计算机体系结构、操作系统、编译程序都在不断地改革创新,但始终有一点是不变的(我对鸭血粉丝的热爱忠贞不渝):那就是下面三者的性能耗时:CPU < 内存 < I/O 但也正因为这些改变,也就在并发程序中出现了一些诡异的问题,而其中最昭著的三大问题就是:可见性、有序性、原子性。 今天我们就主要来学习一下三者中的可见性。 零、可见性的阐述 可见性 的定义是:一个线程对共享变量的修改,另外一个线程能够立刻看到。 在单核时代,所有线程都在一个CPU上执行,所以一个线程的写,一定是对其它线程可见的。就好比,一个总经理下面就一个项目负责人。 此时,项目经理查看到任务G后,分配给员工A和员工B,那么这个任务的进度就能随时掌握在项目经理手中了;每个员工都能从项目经理处得知最新的项目进度。 而在多核时代后,每个CPU都有自己的缓存,这就出现了可见性问题。 此时,两个项目经理同时查看到任务G后,各自分配给自己下属员工,那么这个任务的进度就只能掌握在各自项目经理手中了,因为所有员工的工作进度并不是汇报给同一个项目经理;那么,每个员工只能得知自己项目组员工的工作进度,并不能得知其他项目组的工作进度。所以,当多个项目经理在做同一个任务时,就可能出现任务配比不均、任务进度拖延、任务重复进行等多种问题。 总结上面的例子来讲,就是因为进度的不及时更新