1.CAS
CAS 即 Compare and Swap ,它体现的一种乐观锁的思想,比如多个线程要对一个共享的整型变量执 行 +1 操作:
// 需要不断尝试
while(true) {
int 旧值 = 共享变量 ; // 比如拿到了当前值 0
int 结果 = 旧值 + 1; // 在旧值 0 的基础上增加 1 ,正确结果是 1
/* 这时候如果别的线程把共享变量改成了 5,本线程的正确结果 1 就作废了,这时
compareAndSwap 返回 false,重新尝试,直到:
compareAndSwap 返回 true,表示我本线程做修改的同时,别的线程没有干扰
*/
//这里其实是把共享变量的值与旧值做比较,如果相同,就可以安全的把结果写入共享变量
if( compareAndSwap ( 旧值, 结果 )) {
// 成功,退出循环
}
}
CAS对共享变量可以不用加锁。
**获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。**结合 CAS 和 volatile 可以实现无 锁并发,适用于竞争不激烈、多核 CPU 的场景下。
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
-
但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
线程陷入阻塞,会涉及到线程的上下文切换,这样就严重降低了效率。
CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令,下面是直接使用 Unsafe 对象进 行线程安全保护的一个例子
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class TestCAS {
public static void main(String[] args) throws InterruptedException {
DataContainer dc = new DataContainer();
int count = 5;
Thread t1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
dc.increase();
}
});
t1.start();
t1.join();
System.out.println(dc.getData());
}
}
class DataContainer{
private volatile int data;
static final Unsafe unsafe;
static final long DATA_OFFSET;
static {
try {
// Unsafe 对象不能直接调用,只能通过反射获得
Field theUnsafe =Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
}catch(NoSuchFieldException | IllegalAccessException e) {
throw new Error(e);
}
try {
// data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性。
//方便定位data的地址。
DATA_OFFSET=
unsafe.objectFieldOffset(DataContainer.class.getDeclaredField("data"));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
public void increase(){
int oldValue;
while(true) {
// 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解
oldValue = data;
// cas 尝试修改 data 为 旧值 + 1,如果期间旧值被别的线程改了,返回 false
/*第一个参数当前DataContainer对象,第二个参数偏移量。
对前面两个信息我们知道,针对哪个字段做compareandSwap。
后面的参数就是旧值和新值,它首先会把oldValue与共享变量的当前值做比较。
如果两个不一致,表明修改失败,然后我就重新进入while循环,直到旧值和共享变量的值一致了。
然后compareandswap才会返回true,并且新值更新到共享变量data里去。
if条件成立,while就结束了
*/
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue + 1)) {
return;
}
}
}
public void decrease() {
int oldValue;
while(true) {
oldValue = data;
if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue,
oldValue - 1)){
return;
}
}
}
2 乐观锁与悲观锁
CAS 是基于乐观锁的思想:乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系, 我吃亏点再重试呗。
synchronized 是基于悲观锁的思想:悲观的估计,得防着其它线程来修改共享变量,我上了锁 你们都别想改,我改完了解开锁,你们才有机会。
3 原子操作类
juc(java.util.concurrent)中提供了原子操作类,可以提供线程安全的操作,例如:AtomicInteger、 AtomicBoolean等,它们底层就是采用 CAS 技术 + volatile 来实现的。 可以使用 AtomicInteger 改写之前的例子:
// 创建原子整数对象
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 获取并且自增 i++
// i.incrementAndGet(); // 自增并且获取 ++i
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 获取并且自增 i++
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}