从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包,而Queue,不少状况下使用到了Atomic操做,所以首先从这里开始)。不少状况下咱们只是须要一个简单的、高效的、线程安全的递增递减方案。注意,这里有三个条件:简单,意味着程序员尽量少的操做底层或者实现起来要比较容易;高效意味着耗用资源要少,程序处理速度要快;线程安全也很是重要,这个在多线程下能保证数据的正确性。这三个条件看起来比较简单,可是实现起来却难以使人满意。java
一般状况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操做:或者变量当前值,为该值+1/-1,而后写回新的值。在没有额外资源能够利用的状况下,只能使用加锁才能保证读-改-写这三个操做时“原子性”的。程序员
Doug Lea在未将backport-util-concurrent 合并到JSR 166 里面来以前,是采用纯Java实现的,因而不可避免的采用了synchronized关键字。算法
public final synchronized void set(int newValue);数组
public final synchronized int getAndSet(int newValue);缓存
public final synchronized int incrementAndGet();安全
同时在变量上使用了volatile (后面会具体来说volatile究竟是个什么东东)来保证get()的时候不用加锁。尽管synchronized的代价仍是很高的,可是在没有JNI的手段下纯Java语言仍是不能实现此操做的。数据结构
JSR 166提上日程后,backport-util-concurrent就合并到JDK 5.0里面了,在这里面重复使用了现代CPU的特性来下降锁的消耗。后本章的最后小结中会谈到这些原理和特性。在此以前先看看API的使用。多线程
一切从java.util.concurrent.atomic.AtomicInteger开始。架构
int addAndGet(int delta)
以原子方式将给定值与当前值相加。 实际上就是等于线程安全版本的i =i+delta操做。
boolean compareAndSet(int expect, int update)
若是当前值 == 预期值,则以原子方式将该值设置为给定的更新值。 若是成功就返回true,不然返回false,而且不修改原值。
int decrementAndGet()
以原子方式将当前值减 1。 至关于线程安全版本的--i操做。
int get()
获取当前值。
int getAndAdd(int delta)
以原子方式将给定值与当前值相加。 至关于线程安全版本的t=i;i+=delta;return t;操做。
int getAndDecrement()
以原子方式将当前值减 1。 至关于线程安全版本的i--操做。
int getAndIncrement()
以原子方式将当前值加 1。 至关于线程安全版本的i++操做。
int getAndSet(int newValue)
以原子方式设置为给定值,并返回旧值。 至关于线程安全版本的t=i;i=newValue;return t;操做。
int incrementAndGet()
以原子方式将当前值加 1。 至关于线程安全版本的++i操做。
void lazySet(int newValue)
最后设置为给定值。 延时设置变量值,这个等价于set()方法,可是因为字段是volatile类型的,所以次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管能够忽略),因此若是不是想当即读取设置的新值,容许在“后台”修改值,那么此方法就颇有用。若是仍是难以理解,这里就相似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果当即返回(这种解释实际上是不正确的,可是能够这么理解)。
void set(int newValue)
设置为给定值。 直接修改原始值,也就是i=newValue操做。
boolean weakCompareAndSet(int expect, int update)
若是当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 建立任何 happen-before 排序,所以不提供与除 weakCompareAndSet 目标外任何变量之前或后续读取或写入操做有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序致使此操做失败)。可是从Java源码来看,其实此方法并无实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操做。
因为这里例子比较简单,这里就不作过多介绍了。
AtomicInteger和AtomicLong、AtomicBoolean、AtomicReference差很少,这里就不介绍了。在下一篇中就介绍下数组、字段等其余方面的原子操做。
参考资料:
(1)http://stackoverflow.com/questions/2443239/java-atomicinteger-what-are-the-differences-between-compareandset-and-weakcompar
(2)http://stackoverflow.com/questions/1468007/atomicinteger-lazyset-and-set
在这一部分开始讨论数组原子操做和一些其余的原子操做。
AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray 的API相似,选择有表明性的AtomicIntegerArray来描述这些问题。
int get(int i)
获取位置 i
的当前值。很显然,因为这个是数组操做,就有索引越界的问题(IndexOutOfBoundsException异常)。
对于下面的API起始和AtomicInteger是相似的,这种经过方法、参数的名称就可以获得函数意义的写法是很是值得称赞的。在《重构:改善既有代码的设计》 和《代码整洁之道》 中都很是推崇这种作法。
void set(int i, int newValue)
void lazySet(int i, int newValue)
int getAndSet(int i, int newValue)
boolean compareAndSet(int i, int expect, int update)
boolean weakCompareAndSet(int i, int expect, int update)
int getAndIncrement(int i)
int getAndDecrement(int i)
int getAndAdd(int i, int delta)
int incrementAndGet(int i)
int decrementAndGet(int i)
int addAndGet(int i, int delta)
总体来讲,数组的原子操做在理解上仍是相对比较容易的,这些API就是有多使用才能体会到它们的好处,而不只仅是停留在理论阶段。
如今关注字段的原子更新。
AtomicIntegerFieldUpdater<T>/AtomicLongFieldUpdater<T>/AtomicReferenceFieldUpdater<T,V> 是基于反射的原子更新字段的值。
相应的API也是很是简单的,可是也是有一些约束的。
(1)字段必须是volatile类型的!在后面的章节中会详细说明为何必须是volatile,volatile究竟是个什么东西。
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操做对象字段的关系一致。也就是说调用者可以直接操做对象字段,那么就能够反射进行原子操做。可是对于父类的字段,子类是不能直接操做的,尽管子类能够访问父类的字段。
(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。
(4)只能是可修改变量,不能使final变量,由于final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。若是要修改包装类型就须要使用AtomicReferenceFieldUpdater 。
在下面的例子中描述了操做的方法。
在上面的例子中DemoData的字段value3/value4对于AtomicIntegerFieldUpdaterDemo类是不可见的,所以经过反射是不能直接修改其值的。
AtomicMarkableReference 类描述的一个<Object,Boolean>的对,能够原子的修改Object或者Boolean的值,这种数据结构在一些缓存或者状态描述中比较有用。这种结构在单个或者同时修改Object/Boolean的时候可以有效的提升吞吐量。
AtomicStampedReference 类维护带有整数“标志”的对象引用,能够用原子方式对其进行更新。对比AtomicMarkableReference 类的<Object,Boolean>,AtomicStampedReference 维护的是一种相似<Object,int>的数据结构,其实就是对对象(引用)的一个并发计数。可是与AtomicInteger 不一样的是,此数据结构能够携带一个对象引用(Object),而且可以对此对象和计数同时进行原子操做。
在后面的章节中会提到“ABA问题”,而AtomicMarkableReference/ AtomicStampedReference 在解决“ABA问题”上颇有用。
在这个小结里面重点讨论原子操做的原理和设计思想。
因为在下一个章节中会谈到锁机制,所以此小节中会适当引入锁的概念。
在Java Concurrency in Practice 中是这样定义线程安全的:
当多个线程访问一个类时,若是不用考虑这些线程在运行时 环境下的调度和交替运行,而且不须要额外的同步及在调用方代码没必要作其余的协调 ,这个类的行为仍然是正确的 ,那么这个类就是线程安全的。
显然只有资源竞争时才会致使线程不安全,所以无状态对象永远是线程安全的 。
原子操做的描述是: 多个线程执行一个操做时,其中任何一个线程要么彻底执行完此操做,要么没有执行此操做的任何步骤 ,那么这个操做就是原子的。
枯燥的定义介绍完了,下面说更枯燥的理论知识。
指令重排序
Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程经过叫作指令的重排序。指令重排序存在的意义在于:JVM可以根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的从新排序机器指令,使机器指令更符合CPU的执行特色,最大限度的发挥机器的性能。
程序执行最简单的模型是按照指令出现的顺序执行,这样就与执行指令的CPU无关,最大限度的保证了指令的可移植性。这个模型的专业术语叫作顺序化一致性模型。可是现代计算机体系和处理器架构都不保证这一点(由于人为的指定并不能老是保证符合CPU处理的特性)。
咱们来看最经典的一个案例。
在两个线程交替执行的状况下数据的结果就不肯定了,在机器压力大,多核CPU并发执行的状况下,数据的结果就更加不肯定了。
Happens-before法则
Java存储模型有一个happens-before原则,就是若是动做B要看到动做A的执行结果(不管A/B是否在同一个线程里面执行),那么A/B就须要知足happens-before关系。
在介绍happens-before法则以前介绍一个概念:JMM动做(Java Memeory Model Action),Java存储模型动做。一个动做(Action)包括:变量的读写、监视器加锁和释放锁、线程的start()和join()。后面还会提到锁的的。
happens-before完整规则:
(1)同一个线程中的每一个Action都happens-before于出如今其后的任何一个Action。
(2)对一个监视器的解锁happens-before于每个后续对同一个监视器的加锁。
(3)对volatile字段的写入操做happens-before于每个后续的同一个字段的读操做。
(4)Thread.start()的调用会happens-before于启动线程里面的动做。
(5)Thread中的全部动做都happens-before于其余线程检查到此线程结束或者Thread.join()中返回或者Thread.isAlive()==false。
(6)一个线程A调用另外一个另外一个线程B的interrupt()都happens-before于线程A发现B被A中断(B抛出异常或者A检测到B的isInterrupted()或者interrupted())。
(7)一个对象构造函数的结束happens-before与该对象的finalizer的开始
(8)若是A动做happens-before于B动做,而B动做happens-before与C动做,那么A动做happens-before于C动做。
volatile语义
到目前为止,咱们屡次提到volatile,可是却仍然没有理解volatile的语义。
volatile至关于synchronized的弱实现,也就是说volatile实现了相似synchronized的语义,却又没有锁机制。它确保对volatile字段的更新以可预见的方式告知其余的线程。
volatile包含如下语义:
(1)Java 存储模型不会对valatile指令的操做进行重排序:这个保证对volatile变量的操做时按照指令的出现顺序执行的。
(2)volatile变量不会被缓存在寄存器中(只有拥有线程可见)或者其余对CPU不可见的地方,每次老是从主存中读取volatile变量的结果。也就是说对于volatile变量的修改,其它线程老是可见的,而且不是使用本身线程栈内部的变量。也就是在happens-before法则中,对一个valatile变量的写操做后,其后的任何读操做理解可见此写操做的结果。
尽管volatile变量的特性不错,可是volatile并不能保证线程安全的,也就是说volatile字段的操做不是原子性的,volatile变量只能保证可见性(一个线程修改后其它线程可以理解看到此变化后的结果),要想保证原子性,目前为止只能加锁!
volatile一般在下面的场景:
应用volatile变量的三个原则:
(1)写入变量不依赖此变量的值,或者只有一个线程修改此变量
(2)变量的状态不须要与其它变量共同参与不变约束
(3)访问变量不须要加锁
这一节理论知识比较多,可是这是很面不少章节的基础,在后面的章节中会屡次提到这些特性。
本小节中仍是没有谈到原子操做的原理和思想,在下一节中将根据上面的一些知识来介绍原子操做。
参考资料:
(1)Java Concurrency in Practice
在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操做成功,可是不表明这个过程就是没有问题的。若是链表的头在变化了两次后恢复了原值,可是不表明链表就没有变化。所以前面提到的原子操做AtomicStampedReference/AtomicMarkableReference就颇有用了。这容许一对变化的元素进行原子操做。
参考资料:
(1)非阻塞算法简介
(2)流行的原子