Java并发包Atomic 和Unsafe续:Lockfree And Waitfree算法
在上篇文章中,Unsafe的操作都是native操作,这个我们在上一节中已经分析过了,那么这些native操作有几个比较特殊:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
这几个函数都有个compareAndSwap操作,这个是什么操作?
这个是典型的CAS的操作,这个操作能够保证操作的原子性;这个操作在并行化编程中是个很重要的技术,链接请戳这里。现在几乎所有的CPU都至此这个操作,在X86系统结构中,compare-and-exchange(CMPXCHG)指令能够实现这个原子操作,有了这个院子操作,就可以使用这个操作实现各种无锁(Lock free)的数据结构。
想象一下这个CMPXCHG操作是什么样子的?使用wikipedia上的一个例子来表示这个过程:
int compare_and_swap (int* reg, int oldval, int newval) { int old_reg_val = *reg; if (old_reg_val == oldval) *reg = newval; return old_reg_val; }
这个操作还是比较易懂的,先看*reg的内存值是不是等于oldval,如果是的话,将其赋值newval。
这个操作其实并不能看出来赋值是否成功,只是赋值而已;因为这个操作始终会返回oldval。我们将这个返回值设定为bool型,可以确定使用者的调用是不是成功。
bool compare_and_swap (int *accum, int *dest, int newval) { if ( *accum == *dest ) { *dest = newval; return true; } else { *accum = *dest; return false; } }
好了,现在大家理解了这个操作,与CAS类似的原子操作还有:
CAS的ABA问题:
问题描述:
1:进程T读取共存变量值A
2:T进程被抢占,进程K执行
3:进程K把共享变量值从A改为B,使用完毕后,再改回A,此时进程T抢占。
4:进程T读取变量值A,发现没有变化,继续执行后续流程。
问题:虽然T认为变量值没有变化,继续执行;但是这回引发潜在的问题。CAS是最容易受到影响的实现,因为CAS判断的是指针的地址。如果地址被重用,那就麻烦了。
解决方案:
在wikipedia上的Compare-and-set后面,提供了一个DoubleCompare-And-Set的解法,戳这里。例如在32位系统上,需要检查64位的内容。
1:一次CAS操作检查双倍长度的值,前半部分是指针,后半部分是计数器值。
2:必须这两个值全部通过,才能进行Double-CAS赋值操作。
这样的话,ABA问题发生时,值同计数器值不同,就能顺利解决ABA问题。依然的问题是,DCAS并不被所有的CPU支持(up to 2006);所以基于DCAS的解决方案都是当成理想方案来考虑的。大家考虑的解决方案依然是用CAS解决。
上面提到了Lock-Free、Wait-Free、Lock-Based,那么这几种算法究竟是什么意思呢?
Lock-Free:锁无关,一个锁无关的程序能够确保它所有线程中至少有一个能够继续往下执行。这意味着有些线程可能会被任意的延迟,然而在每一个步骤中至少有一个线程能够执行下去。因此这个系统作为一个整体总是在前进的,尽管有些线程的进度可能没有其它线程走的快。
Wait-Free:等待无关,一个等待无关的程序可以在有限步之内结束,而不管其它线程的相对执行速度如何。
Lock-Based:基于锁,基于锁的程序无法提供上面的任何保证,任一线程持有了某互斥体并处于等待状态,那么其它想要获取同意互斥体的线程只有等待,所有基于锁的算法无法摆脱死锁的阴影。
那么根据上述的描述,Wait-Free和Lock-Free算法意味着它们有更多的有点:
1:线程中止免疫-杀掉系统中的任何线程都不会倒置其它线程被延迟。
2:信号免疫-线程可以自由穿插执行,不会导致死锁。
3:优先级倒置免疫-这两种算法对优先级倒置免疫。
可见在高并发系统中,基于lock-free、wait-free的算法对性能的关键性。
最后返回来,我们再看下AtomicLong的getAndIncrement实现:
public final long getAndIncrement() { while (true) { long current = get(); long next = current + 1; if (compareAndSet(current, next)) return current; } }
有感觉到这个实现熟悉嘛?再回一下compareAndSet的C++实现,你还有什么想问的呢。
大家在学习时,要多看源码,考虑下开源代码的实现思路,认真学习,总结,再学习,形成自己的学习方法。下面提供几个算法的系统实现链接:
Java:
LockfreeStack: https://github.com/mthssdrbrg/LockFreeStack
Disruptor: http://www.searchtb.com/2012/10/introduction_to_disruptor.html
C:
Nobel: http://www.cse.chalmers.se/research/group/noble/
Lock-free-lib: http://www.cl.cam.ac.uk/research/srg/netos/lock-free/
更多项目实现: