J.U.C CAS

时间:2022-08-03 21:39:22

在JDK1.5之前,也就是J.U.C加入JDK之前,Java是依靠synchronized关键字(JVM底层提供)来维护协调对共享字段的访问,保证对这些变量的独占访问权,并且以后其他线程忽的该锁时,将可以看到对这些变量进行的更改(可见性,互斥性)。

锁机制的问题:

  • 锁问题不可回避的,就是上下文切换,加重系统线程调度,引起性能问题;
  • 不一致的获得多个锁的顺序,还可能引发死锁;
  • 如果一个线程试图获得其他线程已经具有的锁时,那么该线程将被阻塞,直到该锁可用,期间它无法进行其他任何操作,试想对一组操作序列加锁,也许在这些操作序列中就仅仅一步操作需要同步,而其余大部分操作都可以同时进行,不会发生竞争问题。一个粗粒度的上锁策略,将严重的导致系统的吞吐量。
  • 如果阻塞的线程是优先级高的任务,那么可能造成非常不好的结果(线程的优先级倒置);

前篇中所涉及的关键字volatile,在此处也并不能胜任,因为它仅仅提供可见性原语,它并不提供互斥访问,原子操作原语(对volatile修饰的int递增操作)。独占锁是一种悲观锁,synchronized就是一种独占锁。

比较并交换(CAS)

支持并发的第一个处理器提供原子的测试并设置操作。现在的处理器(Inter和Sparc)使用的最通用的方法是实现名为比较并转换或者CAS的原语。

CAS操作包含三个操作数-内存位置(V),预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。CAS有效的说明了"我认为位置V应该包含值A;如果包含该值,则将B放在这个位置;否则,就不要更改该位置的值,只告诉我这个位置现在的值既可"。

lock-free , wait-free

如果每个线程在其线程任意延迟(或者甚至失败)时都将持续进行操作,就可以说该算法是wait-free的。与此形成对比的是,lock-free算法要求仅某个线程总是执行操作。(wait-free的另一种定义是保证每个线程在器有限的步骤中正确计算自己的操作,而不管其他线程的操作,计时,交叉,速度)

无阻塞算法被广泛的用在操作系统和JVM级别,进行诸如线程和进程的调度任务。虽然他们的实现比较复杂,但相对与锁定的备选算法,他们有许多优点:可以避免优先级倒置和死锁的发生,竞争比较便宜,协调发生在更细粒度级别,提高吞吐量,其定义:

                                                  一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法

上面说了一大堆CAS的好,最后也该客观的评价一下CAS,当然它也有其缺点:

  • "ABA"问题。J.U.C中AtomicMarkableReference, AtomicStampedReference解决"ABA"可以排上用场。
  • 不停的循环重试知道成功,消耗大量CPU资源。Exponential Backoff。
  • 在高度竞争的环境下,CAS性能反而比直接加锁低,所以CAS有他的适用场景。