原子操作

从三个特性理解多线程开发

岁酱吖の 提交于 2020-01-01 17:37:10
工作中许多地方需要涉及到多线程的设计与开发,java多线程开发当中我们为了线程安全所做的任何操作其实都是围绕多线程的三个特性:原子性、可见性、有序性展开的。针对这三个特性的资料网上已经很多了,在这里我希望在站在便于理解的角度,用相对直观的方式阐述这三大特性,以及为什么要实现和满足三大特性。 一、原子性 原子性是指一个操作或者一系列操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。其实这句话就是在告诉你,如果有多个线程执行相同一段代码时,而你又能够预见到这多个线程相互之间会影响对方的执行结果,那么这段代码是不满足原子性的。结合到实际开发当中,如果代码中出现这种情况,大概率是你操作了共享变量。 针对这个情况网上有个很经典的例子,银行转账问题: 比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。 如果A和B两个转账操作是在不同的线程中执行,而C的账户就是你要操作的共享变量,那么不保证执行操作原子性的后果是十分严重的。 OK,上面的状况我们理清楚了,由此可以引申出下列三个问题 1

Java中的13个原子操作类

空扰寡人 提交于 2020-01-01 15:34:06
目录 原子更新基本类型类 原子更新数组 原子更新引用类型 原子更新字段类 当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变 量i=1, A线程更新i+1, B线程也更新i+ 1, 经过两个线程橾作之后可能i不等于3, 而是等于2。因 为A和B线程在更新变量i的时候拿到的i都是1, 这就是线程不安全的更新操作,通常我们会使 用synchronized来解决这个问题,synchronized会保证多线程不会同时更新变量i。 而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。 因为变量的类型有很多种,所以 在Atomic包里一共提供了13个类,属于4种类型的原子更 新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。 Atomic包里的类基本都是使用Unsafe实现的包装类。 原子更新基本类型类 使用原子的方式更新基本类型,Atomic包提供了以下3个类。 AtomicBoolean 原子更新布尔类型。 Atomiclnteger 原子更新整型。 AtomicLong 原子更新长整型。 以上3个类提供的方法几乎一模一样,所以本节仅以Atomiclnteger为例进行讲解,

NoSQL那些事--Redis

为君一笑 提交于 2019-12-30 01:22:50
Redis是个流行的内存数据库(in-momery)。接口好用,性能也很强,还支持多种数据结构,加上各种高可用性集群方案,实在是太太太好用了。 但是就是因为太好用了,好用到让很多人都晕了脑子: 用Redis性能就大大提高了 用Redis可以保证原子性 用Redis可以实现事务 用Redis可以当队列 …… 这就好像一个股民,在手机上操作买卖几笔股票,赚了一些,然后感叹道"股市就是为我发财而存在的啊"!!他的下场可想而知。 Redis的种种优势源自于他的设计——简单直接的单线程内存操作。但这些优势是有前提的。 Redis的性能高,吗? Redis的性能非常高。有些评测说用Redis可以达到几十万QPS(比如这里 http://skipperkongen.dk/2013/08/27/how-many-requests-per-second-can-i-get-out-of-redis/ )。大家可能在网文上记住了这个NB的数字,却很少关心这个数值怎么来的。这就像是你买手机评测光看跑分一样不靠谱。 Redis要达到高性能需要做到: Value尽可能的小。一般的测评都会用比较小的value,比如一个整数或者不长的字符串。但是如果用Redis做缓存,那么缓存的大小的可能偏离这个数字。比如一个页面几十KB;再比如,一个5年的市场价格序列数据可能高达几MB

Java面试官最爱问的volatile关键字

血红的双手。 提交于 2019-12-29 23:11:16
在Java的面试当中,面试官最爱问的就是volatile关键字相关的问题。经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用volatile关键字作为切入点呢? 为什么爱问volatile关键字 爱问volatile关键字的面试官,大多数情况下都是有一定功底的,因为volatile作为切入点,往底层走可以切入Java内存模型(JMM),往并发方向走又可接切入Java并发编程,当然,再深入追究,JVM的底层操作、字节码的操作、单例都可以牵扯出来。 所以说懂的人提问题都是有门道的。那么,先整体来看看volatile关键字都设计到哪些点:内存可见性(JMM特性)、原子性(JMM特性)、禁止指令重排、线程并发、与synchronized的区别……再往深层次挖,可能就涉及到字节码、JVM等。 不过值得庆幸的是,如果你已经学习了微信公众号“程序新视界”JVM系列的文章,上面的知识点已经不是什么问题了,权当是复习了。那么,下面就以面试官提问的形式,在不看答案的情况下,尝试回答,看看学习效果如何。夺命连环问,开始…… 面试官:说说volatile关键字的特性 被volatile修饰的共享变量,就具有了以下两点特性: 保证了不同线程对该变量操作的内存可见性; 禁止指令重排序; 回答的很好,点出了volatile关键字两大特性

对volatile不具有原子性的理解

随声附和 提交于 2019-12-29 20:34:56
在阅读多线程书籍的时候,对volatile的原子性产生了疑问,问题类似于这篇 文章 所阐述的那样。经过一番思考给出自己的理解。 我们知道对于可见性,Java提供了volatile关键字来保证 可见性 、 有序性 。 但不保证原子性 。 普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。   背景:为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。 如果对声明了volatile的变量进行写操作 ,JVM就会向处理器发送一条指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。 在多处理器下,为了保证各个处理器的缓存是一致的,就会 实现缓存一致性协议 ,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 总结下来 : 第一:使用volatile关键字会强制将修改的值立即写入主存; 第二:使用volatile关键字的话,当线程2进行修改时

java内存模型-锁

▼魔方 西西 提交于 2019-12-29 03:13:52
锁的释放-获取建立的 happens before 关系 锁是 java 并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。下面是锁释放-获取的示例代码: class MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 …… } //6 } 假设线程 A 执行 writer() 方法,随后线程 B 执行 reader() 方法。根据 happens before 规则,这个过程包含的 happens before 关系可以分为两类: 根据程序次序规则,1 happens before 2, 2 happens before 3; 4 happens before 5, 5 happens before 6。 根据监视器锁规则,3 happens before 4。 根据 happens before 的传递性,2 happens before 5。 上述 happens before 关系的图形化表现形式如下: 在上图中,每一个箭头链接的两个节点,代表了一个 happens before 关系

JAVA内存模型5-锁

只谈情不闲聊 提交于 2019-12-29 03:12:52
锁的释放-获取建立的happens before关系 锁是java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。下面是锁释放-获取的示例代码: class MonitorExample { int a = 0; public synchronized void writer() { //1 a++; //2 } //3 public synchronized void reader() { //4 int i = a; //5 …… } //6 } View Code   假设线程A执行writer()方法,随后线程B执行reader()方法。根据happens -before规则,这个过程包含的happens -before关系可以分为两类: 根据监视器锁规则,3happens before4。 根据程序次序规则,1happens -before2, 2 happens- before 3; 4 happens- before 5, 5 happens -before 6。 根据happens before 的传递性,2 happens before 5。    上述happens -before关系的图形化表现形式如下:   在上图中,每一个箭头链接的两个节点,代表了一个happens before关系

原子操作和volatile关键字

跟風遠走 提交于 2019-12-29 02:35:57
原子操作 :不可被中断的操作。要么全执行,要么全不执行。 现代CPU读取内存,通过读取缓存再写入主存。先去主存读--->写入缓存---->运行线程--->写入缓存---->写入主存 多cpu时会出现缓存一致性和总线锁的问题。 只有简单的读取,赋值操作,即一步完成的操作才是原子操作。 volatile,synchronized,lock 能保证可见性, volatile保证修改的值立即更新到主存,synchronized和lock保证同一时刻只有一个线程操作变量,在锁被释放前会将新值写入内存。 线程的有序性: java内存模型具备一些先天的有序性,即 happensbefore(先行发生)原则 : 1,程序次序规则,一个线程内,按照代码书写顺序执行 2,锁定规则,一个解锁操作ounlock先行发生于后面对同一个锁的lock操作 3,volatile变量规则,对一个变量的写操作先行发生于后面对这个变量的读操作 4,传递规则,A先发生于B,B先发生于C,则A先发生于C 5,线程启动规则,Thread对象的start()方法先行发生于此线程的每一个操作 6,线程中断规则,对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,即中断的发生先于中断被检测到 7,线程终结规则,线程中所有的操作都先行发生于线程的中止检测,可以通过Thread.join()方法结束

单核,多核CPU的原子操作

不羁岁月 提交于 2019-12-29 02:35:30
一. 何谓"原子操作": 原子操作就是: 不可中断的一个或者一系列操作, 也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换(context switch). 二. 为什么关注原子操作? 1. 如果确定某个操作是原子的, 就不用为了去保护这个操作而加上会耗费昂贵性能开销的锁. - (巧妙的利用原子操作和实现无锁编程) 2. 借助原子操作可以实现互斥锁(mutex). (linux中的mutex_lock_t) 3. 借助互斥锁, 可以实现让更多的操作变成原子操作. 三. 单核CPU的原子操作: 在单核CPU中, 能够在一个指令中完成的操作都可以看作为原子操作, 因为中断只发生在指令间. 四. 多核CPU的原子操作: 在多核CPU的时代(确实moore定律有些过时了,我们需要更多的CPU,而不是更快的CPU,无法处理快速CPU中的热量散发问题), 体系中运行着多个独立的CPU, 即使是可以在单个指令中完成的操作也可能会被干扰. 典型的例子就是decl指令(递减指令), 它细分为三个过程: "读->改->写", 涉及两次内存操作. 如果多个CPU运行的多个进程在同时对同一块内存执行这个指令, 那情况是无法预测的. 五. 硬件支持 & 多核原子操作: 软件级别的原子操作是依赖于硬件支持的. 在x86体系中, CPU提供了HLOCK pin引线,

原子操作

天涯浪子 提交于 2019-12-29 02:35:14
   "原子操作(atomic operation)是不需要synchronized",这是Java多线程编程的老生常谈了。   所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (cpu上下文切换)。   定义:一个操作是原子的(atomic),如果这个操作所处的层(layer)的更高层不能发现其内部实现与结构。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体式原子性的核心。   首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存当中读取或者写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址,也就是lock住这块内存,在我关于信号量的随笔中有提到《windows核心编程》,JVM同样也是。   我们以decl (递减指令)为例,这是一个典型的"读-改-写"过程,涉及两次内存访问。设想在不同CPU运行的两个进程都在递减某个计数值,可能发生的情况是:   ⒈ CPU A(CPU A上所运行的进程,以下同)从内存单元把当前计数值⑵装载进它的寄存器中;   ⒉ CPU B从内存单元把当前计数值⑵装载进它的寄存器中。   ⒊ CPU A在它的寄存器中将计数值递减为1;   ⒋ CPU