CAS算法全称为Compare and swap,翻译成中文就是“比较与交换”,是一种有名的无锁算法。无锁编程,就是指在不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步。
在CAS算法中需要理解3个操作数,内存值V,旧的预期值A,要修改的新值B。
当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做,这就是CAS算法。现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰。
很多人认为Java没办法直接操作内存,其实不然,sun为Java添加了一个“后门”,可以允许Java简介的操作内存,而这个“后门”就是Unsafe类。
Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作能力方面起了很大的作用。
Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。Oracle正在计划从Java 9中去掉Unsafe类。
而我们的CAS算法也是在Unsafe类中实现的。
如:
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Unsafe类中绝大部分的方法都是native方法,调用本地方法实现,CAS算法也是。
CAS算法有一个经典的ABA问题;
ABA问题是指,线程1从内存V中取出值为A,然后期望改成某一值,同一时刻一个线程2从内存V中取出值也为A,然后期望改为B,此时由于某些原因,线程1被阻塞,线程2继续执行,将内存V的值改为了B,然后又一线程将内存V的值改为了A,此时线程1被唤醒,由于内存V的值为A所以导致线程1修改成功。其实此时线程1对比的内存V的值A并不是开始的那个A了,应该要修改失败才对。
要解决ABA问题其实很简单,可以为内存V的值添加一个版本号或者修改次数,当预期值A与内存V值相等时而版本号或修改次数与V的版本号或修改次数不等,则拒绝修改。
Java中AtomicStampedReference类解决了ABA问题,我们来看下它是怎么实现的。
AtomicStampedReference类中有一个内部类Pair,Pair类里reference属性保存了旧的期望值,stamp属性保存了版本号或者是修改次数。
我们来看下AtomicStampedReference类的compareAndSet方法源码:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
//只有当版本号相同的情况下才有可能修改成功
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
Unsafe类提供了一个静态方法来获取它:
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
虽然Unsafe类提供了静态方法去获取它,但是我们还是拿不到,我们从源码可以看到,在调用getUnsafe方法时,对该方法做了安全限制,会抛出异常;
尽管如此,我们还是可以通过Java万能的反射来获取这个类的实例的,在Unsafe类中,Unsafe使用了theUnsafe属性来存储了自身的实例对象,我们可以通过反射,来获取Unsafe的theUnsafe属性的值,从而获取Unsafe实例。
Class<?> clazz = Unsafe.class;
Field f = clazz.getDeclaredField("theUnsafe");
f.setAccessible(true);
UNSAFE = (Unsafe) f.get(null);
首先我们创建一个UnsafeLock的类,来完成使用Unsafe完成锁的功能;
这个类里面我们需要一个status属性表示加锁状态,一个unsafe属性来存储Unsafe实例;
Unsafe在修改一个内存值时需要传入这个内存值的偏移量,所以我们还需要一个属性来存储status的偏移量;
然后再实现我们的lock方法和unLock方法。
我们自己实现的锁源码如下:
public class UnsafeLock {
private Unsafe unsafe;
private int status = 0;
private long statusOffset;
private Thread localThread;
public UnsafeLock() throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = Unsafe.class;
Field f = clazz.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
statusOffset = unsafe.objectFieldOffset(UnsafeLock.class.getDeclaredField("status"));
}
public void tryLock() throws Exception {
if (unsafe.compareAndSwapInt(this, statusOffset, 0, 1)) {
localThread = Thread.currentThread();
return;
} else {
throw new Exception("获取锁失败");
}
}
public void unlock() {
if (Thread.currentThread() != localThread)
return;
if (unsafe.compareAndSwapInt(this, statusOffset, 1, 0)) {
localThread = null;
}
}
}
最后我们来写一个测试类来测试下这个tryLock:
public class MyLockTest {
private static int code = 0;
public static void main(String[] args) throws Exception {
UnsafeLock lock = new UnsafeLock();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
lock.tryLock();
System.out.println(">>>>>>>>线程"+Thread.currentThread().getName()+"获取锁成功>>>>>>>>");
for (int j = 0; j < 10; j++)
System.out.println(Thread.currentThread().getName() + ":" + code++);
} catch (Exception e) {
System.out.println("--------线程"+Thread.currentThread().getName()+"获取锁失败--------");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
}
}
运行的结果如下图:
原文:https://www.cnblogs.com/Sirius-/p/13934585.html