1.Java线程简介.
在JDK 5之前,Java语言是靠synchronized关键字,保证同步的,这会导致有锁(后面的章节还会谈到锁).
锁机制存在以下问题:
(1)在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题.
(2)一个线程持有锁,会导致其它所有需要此锁的线程挂起.
(3)如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险.
volatile是不错的机制,但是volatile不能保证原子性.因此对于同步最终还是要回到锁机制上来.
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁.
而另一个更加有效的锁就是乐观锁.所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止.
1.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;
}
AtomicInteger的incrementAndGet的实现,看看++i是怎么做到的.
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
首先可以看到ta是通过一个无限循环(spin),直到increment成功为止.
循环的内容是
1.取得当前值.
2.计算+1后的值.
3.如果当前值还有效(没有被)的话,设置那个+1后的值.
4.如果设置没成功(当前值已经无效了,即被别的线程改过了), 再从1开始.
在这里采用了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的非阻塞算法.其它原子操作都是利用类似的特性完成的.其中,
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
unsafe.compareAndSwapInt(t