Java并发——原子变量和原子操作
很多情况下我们只是需要一个简单的、高效的、线程安全的递增递减方案。注意,这里有三个条件:简单,意味着程序员尽可能少的操作底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也非常重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较简单,但是实现起来却难以令人满意。
通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。
Java 5新增了AtomicInteger类,该类包含方法getAndIncrement()以及getAndDecrement(),这两个方法实现了原子加以及原子减操作,但是比较不同的是这两个操作没有使用任何加锁机制,属于无锁操作。
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁(后面的章节还会谈到锁)。
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
(2)一个线程持有锁会导致其它所有需要此锁的线程挂起。
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
CAS 操作
上面的乐观锁用到的机制就是CAS,Compare and Swap。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
非阻塞算法 (nonblocking algorithms)
一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。
拿出AtomicInteger来研究在没有锁的情况下是如何做到数据正确性的。
private volatile int value;
首先毫无疑问,在没有锁的机制下需要借助volatile原语,保证线程间的数据是可见的(共享的),这样获取变量值的时候才能直接读取。
public final int get() {
return value;
}
然后来看看++i是怎么做到的。
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。
而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。参考资料的文章中介绍了如果利用CAS构建非阻塞计数器、队列等数据结构。
CAS看起来很爽,但是会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,但是在这个时间差内任何变化都可能发生。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。要解决"ABA问题",我们需要增加一个版本号,在更新变量值的时候不应该只更新一个变量值,而应该更新两个值,分别是变量值和版本号,AtomicStampedReference支持在两个变量上进行原子的条件更新,可以使用该类进行更新操作。
参考资料:
(1)非阻塞算法简介
(2)流行的原子
转自:http://www.blogjava.net/xylz/archive/2010/07/04/325206.html
Java并发——原子变量和原子操作的更多相关文章
- Java并发编程系列-(3) 原子操作与CAS
3. 原子操作与CAS 3.1 原子操作 所谓原子操作是指不会被线程调度机制打断的操作:这种操作一旦开始,就一直运行到结束,中间不会有任何context switch,也就是切换到另一个线程. 为了实 ...
- java的原子变量
java的原子变量类似c++的InterlockedDecrement()操作.其实就是在进行算术时,把整个算式看为一个整体,并且保证同一时间只计算该式子一次. 它的用途比如,多个线程可能会调用某个函 ...
- Java多线程-----原子变量和CAS算法
原子变量 原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题 Java给我们提供了以下几种原子类型: AtomicInteger和Ato ...
- Java并发编程学习笔记(一)——线程安全性
主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...
- Java并发编程实战
代码中比较容易出现bug的场景: 不一致的同步,直接调用Thread.run,未被释放的锁,空的同步块,双重检查加锁,在构造函数中启动一个线程,notify或notifyAll通知错误,Object. ...
- 原子变量与CAS算法(二)
一.锁机制存在的问题 (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. (2)一个线程持有锁会导致其它所有需要此锁的线程挂起. (3)如果一个优先级高的线程等待一个 ...
- 29、Java并发性和多线程-非阻塞算法
以下内容转自http://ifeve.com/non-blocking-algorithms/: 在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法.在绝大多数项目中,在 ...
- Java并发编程系列-(8) JMM和底层实现原理
8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...
- Java并发编程笔记之LinkedBlockingQueue源码探究
JDK 中基于链表的阻塞队列 LinkedBlockingQueue 原理剖析,LinkedBlockingQueue 内部是如何使用两个独占锁 ReentrantLock 以及对应的条件变量保证多线 ...
随机推荐
- 修改docker下mysql配置
1.在/home/smile/docker/mysql/config/目录下增加一个文件 my.cnf # Copyright (c) , Oracle and/or its affiliates. ...
- Make jQuery throw error when it doesn't match an element
Make jQuery throw error when it doesn't match an element 解答1 You could make a plugin to use to ensur ...
- Python的datetime与Decimal数据进行json序列化的简单说明
我们在Python的json.JSONEncoder类中可以查看Python数据序列化为JSON格式的数据时数据类型的对应关系: class JSONEncoder(object): "&q ...
- docker部署Javaweb项目(jdk+tomcat+mysql)
步骤一:在主机下载安装docker,参照Centos7上安装docker 步骤二:下载Linux版本的JDK1.6和Tomcat6.0(其他项目若依赖其他版本的运行环境可选择另外版本下载),通过sec ...
- Selenium IDE安装及环境搭建教程
摘自https://blog.csdn.net/ywyxb/article/details/59103683 Selenium IDE环境部署- Firefox浏览器Firefox-ESR版本下载(推 ...
- application.events 识别组合键 参考。
https://blog.csdn.net/chinayu2007/article/details/43761277 在窗体上放上ApplicationEvents控件,在OnMessage消息中加入 ...
- AndroChef Java Decompile
With AndroChef Java Decompiler you can decompile apk., dex, jar and java class-files. It's simple an ...
- MariaDB增删改
1.MariaDB 数据类型 MariaDB数据类型可以分为数字,日期和时间以及字符串值. 使用数据类型的原则:够用就行, 尽量使用范围小的,而不用大的 常用的数据类型: 1.整数:int, bit( ...
- SSLPinning简介,使用Xposed+JustTrustMe来突破SSL Pinning
0x00 前面 如果你是一干Web安全的,当你在测试目前大多数的手机APP应用程序时,你一定遇到过burpsuite无法抓到数据包的情况,开始你以为只是https的问题,但是当你使用了burpsuit ...
- C语言Ⅰ博客作业01
1.你对计算机科学与技术专业了解是怎样? 本专业培养具有良好的科学素养,系统地.较好地掌握计算机科学与技术包括计算机硬件.软件与应用的基本理论.基本知识和基本技能与方法,能在科研部门.教育单位.企业. ...