由于临界区访问的限制,所以我们需要设置并发的访问控制策略,这就是并发级别.按照并发级别的分类,大致上可以分为阻塞、无饥饿、无障碍、无锁、无等待。
Blocking(阻塞)
1. Blocking
2. Starvation-Free
Obstruction-Free(无障碍)
3. Obstruction-Free
Lock-Free(无锁)
4. Lock-Free (LF)
Wait-Free(无等待)
5. Wait-Free (WF)
6. Wait-Free Bounded (WFB)
7. Wait-Free Population Oblivious (WFPO)
1.阻塞(Blocking)
在基础概念篇(一)中,我们提到了阻塞,并提到了阻塞的形成原因。在多线程高并发的情况下,使用同步锁(synchronized)或者重入锁(reentrantLock),会造成临界区外的线程形成阻塞线程。
2.无饥饿(Starvation-Free)
在基础概念篇(一)中,我们也说到了饥饿现象。造成饥饿的原因是线程很长时间都得不到资源(CPU资源),无法继续执行。比如,优先级较高的线程很容易得到锁,而优先级较低的线程却可能长时间都得不到执行资源。这就造成了资源使用的不公平性(后续会介绍什么是公平锁,什么是非公平锁)。无饥饿就是每个线程都可以得到系统分配的时间片去执行自己的任务.
3.无障碍(Obstruction-Free)
无障碍,就是在执行过程中,不会出现阻碍,不会因为临界区而形成阻塞,是非阻塞的调度。
在并发访问控制中,阻塞的调度是一种悲观的策略,因为系统会担心由于多个线程的访问,会出现共享数据不一致(所谓共享数据,就是在主内存中存放的数据,JMM-Java内存模型中线程之间通信的一种方式)。
而非阻塞调度是一种乐观的策略,因为系统认为多线程的访问不会产生冲突,或者产生冲突的概率比较小,因此允许多线程同时访问临界区,但是这很容易形成数据不一致,无障碍要求在线程执行产生冲突时,要进行数据回滚。所以说,无障碍执行并不是可以很顺畅的执行,更严重的,可能没有一个线程能顺利执行下去。
4.无锁(Lock-Free)
无锁的并行就是无障碍实现的一种,它规定线程必须在有限步骤内完成。在无锁的情况下,所有线程可以同时访问临界区。在高并发多线程中,CAS(Compare And Swap,比较交换)技术就是一种无锁实现.在它的实现中,使用了一个无限循环,当要修改的内容和期望内容一致时,才去做修改.因此,CAS对死锁是免疫的.在java.util.concurrent.atomic包下(在jdk的rt.jar中)的各种原子类实现,都使用了CAS技术.例如在AtomicInteger中的getAndSet(int newValue)方法.
关于CAS技术,我会专门写一片文章阐述,这里不多说.
另外,使用无锁方式,省去了线程之间竞争临界区资源锁而产生的性能损耗,也没有线程之间频繁调度带来的开销.
5.无等待(Wait-Free)
无等待表示任何线程都可以在有限步骤内结束,而不必关心其他线程进度如何.进一步分类可以分为有界无等待Wait-Free Bounded (WFB)和集居数无关无等待Wait-Free Population Oblivious (WFPO,外国人起的这名字也是醉了).
有界无等待:按照英文愿意,是指方法的执行过程都可以在有界限的步骤内完成,但是这个过程可能是与线程数量相关的.
集居数无关无等待(也可以叫做线程数无关无等待):在英文文献中,是这么说的--一个无等待的方法,如果其性能和活动线程数目无关,那么被称为集居数无关无等待的。
Wait-free bounded(有界无等待):
如果所有的L个线程消耗C(N,L)或者更少的时间完成操作:OpsF() < C(N,L)
Wait-free population oblivious(集居数无关无等待,在并发变成实战中翻译成了线程数无关无等待,也准确):
如果所有的L个线程在有限操作内完成F,并且和L无关:OpsF() < C(N)
其中,设F为一个函数方法,设L为同时调用F的并发线程数目,设N为一个与L无关的变量,设OpsF()代表一个指定线程完成F需要进行的操作步骤,设C(N,L)为一个依赖N和L的函数.
一种典型的无等待结构就是Read-Copy-Update(RCU).它的基本思想是,对数据的读可以不加控制,因此,所有的读线程都是无等待的.但是在写数据的时候,需要先取得原始数据的副本,接着修改副本数据,修改完成后,然后在合适的时机回写数据.