无锁策略

对于并发控制来说,锁是一种悲观策略,它总是假设每次的临界区操作都会产生冲突,于是对每次访问都加锁,如果有多个线程同时访问临界区资源,就会阻塞让线程等待。

无锁是一种乐观策略,它假设对资源的访问是没有冲突的,所有的线程都可以在不停顿的情况下持续运行。无锁策略是以比较交换技术来实现(CAS),一旦检测到线程冲突,就会重试当前操作直至没有冲突。

1、比较交换:CAS

CAS的优点:

是非阻塞的,不会产生死锁

线程间的影响远小于锁的方式

没有锁竞争带来的系统开销及线程间调度的开销

CAS的算法原理:

它包含三个参数CAS(V,E,N),V表示要更新的变量,E表示预期值,N表示新增。仅当V==E时,才会将V的值设为N,如果V != E,则说明已有其他线程做了更新,那么当前线程什么都不做。最后返回当前V的真实值。CAS操作一个变量时,只有一个线程会成功并更新数据。失败的线程不会被挂起,仅被告知失败,并允许再次尝试或放弃操作。

CAS需要你给出一个期望值,即变量应该是什么值,如果不是预想的那样,则说明它已被别人修改,此时需要重新读取并再次尝试修改。

2、无锁的线程安全整数:AtomicInteger

在java.util.concurrent.atomic包下实现了一些可以直接使用CAS操作的线程安全的类型,如AtomicInteger、AtomicLong、AtomicBoolean等。常用的是AtomicInteger,和Integer不同的是它是可变且线程安全的,对其进行修改等操作,都是使用CAS指令进行的。

定义方法

private volatile int value; //核心字段,表示AtomicInteger对象当前的实际值

private static final long valueOffset; //保存着value字段在AtomicInteger对象中的偏移量

AtomicInteger示例

3、Unsafe类

包路径:sun.misc.Unsafe,Unsafe类封装了一些类似指针的操作。

比较交换的方法定义

第一个参数表示给定的对象,第二个参数表示对象内的偏移量,第三个参数表示期望值,第四个参数表示要设置的值。

native底层实现方法
getUnsafe() 实现

获得Unsafe实例的方法是调用getUnsafe(),它会检查这个类的getClassLoader是否为系统加载器,如果不是则直接抛出异常。我们无法直接使用Unsafe类,因为它是JDK内部使用的专属类。

PS:根据Java类加载器的工作原理,应用程序的类由App Loader加载,而系统核心类由BootStrap类加载器加载,如rt.jar。

4、无锁的对象引用:AtomicReference

AtomicReference是普通对象的引用,它可以保证在修改对象引用时的线程安全性。

CAS的缺陷:当获取对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了2次,并且经过修改后的值又恢复为旧值,此时当前线程就无法正确判断这个对象究竟是否被修改过。发生这种情况的概率很低,当修改的对象没有过程的状态信息,所有的信息都只保存于对象的数值本身。

另外一种场景:能否修改对象的值,不仅取决于当前值,还和对象的过程变化有关,此时AtomicReference就无法解决了,JDK提供了另外一个AtomicStampedReference类可以解决这种问题。

5、带有时间戳的对象引用:AtomicStampedReference

AtomicStampedReference记录对象在修改过程中的状态值,可以解决对象被反复修改导致线程无法正确判断对象状态问题。它内部不仅维护了对象值,还维护了一个时间戳,当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入时才会成功。因此即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入。

重要方法定义
使用AtomicStampedReference模拟充值消费
使用AtomicStampedReference模拟充值消费
运行结果

6、无锁数组:AtomicIntegerArray

除基本数据类型外,JDK还提供了数组等复合结构,如:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,分别表示整数数组、Long数组、对象数组。

AtomicIntegerArray是对int[]类型的封装,使用Unsafe类通过CAS的方式控制其在并发下的安全性。

方法定义
数组值累加

7、让普通变量也享受原子操作:AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater可以让普通的变量也享受CAS操作带来的并发安全性。根据不同数据类型分为AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater,分别对于int、long、普通对象进行CAS操作。

模拟投票

使用AtomicIntegerFieldUpdater的注意事项:

Updater只能修改它可见范围内的变量,它使用反射获得变量,如果变量不可见就会出错。

为了保证变量被正确的读取,它必须是volatile类型的。

由于CAS操作会通过对象实例中的偏移量之间进行赋值,因此它不支持static字段。


--参考文献《实战Java高并发程序设计》

你可能感兴趣的:(无锁策略)