现在java中多使用cas来实现无锁化
定义
CAS(Compare and Swap),如果原始值和给定值相同,则修改为新值,否则失败
使用
借助java9推出的VarHandle实现cas操作
VarHandle通过以下方法获取
1
| MethodHandles.lookup().findVarHandle(CASExample.class, "x", int.class)
|
三个参数依次代表:
- CASExample.class 需要操作的变量所在的类
- x 变量的名字,在此处就是变量在CASExample中的名字
- int.class 变量的类型
调用如下:
1
| Varhandle.compareAndSet(example, 0, 1)
|
第一个参数是CASEXample的实例,第二次参数是期望值,第三个参数就想要设置的新值
样例如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class CASExample {
private volatile int x = 0;
public static void main(String[] args) { try { VarHandle xVarhandle = MethodHandles.lookup().findVarHandle(CASExample.class, "x", int.class);
CyclicBarrier barrier = new CyclicBarrier(2);
CASExample example = new CASExample();
for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { barrier.await(); while (true) { if (xVarhandle.compareAndSet(example, 0, 1)) { break; } } System.out.println(Thread.currentThread().getName() + "设置完成" + example.x); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); }
} }).start(); }
} catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
}
}
|
输出一个1,同时cpu占有率开始上升,很明显是另一个线程死循环导致的
注意,我用的是openjdk11,jdk中存在两个Unsafe,分别是sun.misc.Unsafe和jdk.internal.misc.Unsafe,这里使用第二个
原子类
写一个原子类试一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class AtomicInt {
private volatile int value; private VarHandle handle;
public AtomicInt() { try { value = 0; handle = MethodHandles.lookup().findVarHandle(AtomicInt.class, "value", int.class); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
}
public boolean add() { while (true) { int v = value; if (handle.compareAndSet(this, v, v + 1)) { return true; } } }
public static void main(String[] args) { int threadCount = 100; int count = 1000; CountDownLatch latch = new CountDownLatch(threadCount);
AtomicInt atomicInt = new AtomicInt();
for (int i = 0; i < threadCount; i++) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < count; j++) { atomicInt.add(); } latch.countDown(); } }).start(); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicInt.value); }
}
|
结果是100000,正确
ABA问题
ABA是指数据由A->B->A的转换,CAS会误以为没有变化。JDK提供了AtomicStampedReference和AtomicMarkableReference来解决。