Java 中什么是无锁编程?

633 total views, 3 views today

多线程环境下,为了保证数据不受到并发操作的影响,通常会采用加锁的策略保证一致性。除了加锁之外,还有一种方式就是采用无锁编程。

Compare-and-Swap

Java 中的无锁编程本质上就是一个 CAS(compare-and-swap)机制。CAS 是一个原子性操作,目前大部分的 CPU 都支持 CAS 指令, 能够使其在硬件层面上提供原子性操作。在  Intel  处理器中,CAS 通过指令 cmpxchg 实现,该机制在修改某个内存值的时候,会先比较内存值是否和给定的数值一致,如果一致则修改,不一致则不修改。由于这几步动作是原子操作,所以不必担心并发问题。

原子操作

原子操作是指这个操作不会被打断,一旦开始,不会有任何线程去修改相关的内存,原子操作会独占这段资源。这个特性是由 CPU 硬件通过相应的指令所保证的,处理器可以通过总线锁,或者是缓存锁来实现原子操作。所以说原子操作在修改一个内存对象时,是不会被干扰的,所以不会有并发的问题。

Java 中的无锁类

Java.util.concurrent 中提供了一些实现的原子操作的类,包括:AtomicBoolean、AtomicInteger、AtomicIntegerArray、AtomicLong、AtomicReference、AtomicReferenceArray。

以 AtomicLong 为例,AtomicLong 中有一个原子自增方法 incrementAndGet。
在 jdk1.7 中,getAndIncrement 方法实现方式如下:

代码中可以看到,getAndIncrement 方法中,死循环调用 compareAndSet 方法,如果 compareAndSet 返回失败就会一直重试,直到 compareAndSet 返回 true。其中 compareAndSet 方法用的 unsafe.compareAndSwapInt 方法,该方法就是调用 CPU 中的 CAS 指令。

在 jdk1.8 中,getAndIncrement 方法实现方式如下:

可以看到,直接使用了 Unsafe 类,Unsafe 类直接提供了硬件级别的原子操作。

CAS 的 ABA 问题

虽然 CAS 操作是原子性的,但是 CAS 操作时,需要提供某时刻内存中的数据用于比较,这个操作和 CAS 操作之间并不是原子的,有一段时间差,这中间可能导致 ABA 问题,即数据从 A 变成 B 又变成 A。

可能的事件序列:

线程 1 从内存位置 V 中取出 A。
线程 2 从位置 V 中取出 A。
线程 2 进行了一些操作,将 B 写入位置 V。
线程 2 将 A 再次写入位置 V。
线程 1 进行 CAS 操作,发现位置 V 中仍然是 A,操作成功。
尽管线程 1 的 CAS 操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

一般来说,这个问题没太多影响,但是在某些场合下,还是会导致数据问题。毕竟数据曾经变过了。比如对一个基于链表实现的栈做 pop 和 push 操作,一定几率下出现问题,可以自行搜索 ABA 问题场景的例子。ABA 问题的解决很简单,只需要在每次修改的时候,加上版本号即可。

原创文章,转载请注明出处!http://www.javathings.top/java中什么是无锁编程?/

About: wusq


发表评论

邮箱地址不会被公开。 必填项已用*标注