cas操作流程
public class Test2 {
public static void main(String[] args) throws InterruptedException {
Account account = new Account(100);
Account2 account2 = new Account2(100);
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(() -> {
try {
TimeUnit.MILLISECONDS.sleep(new Random(1).nextInt(10));
} catch (InterruptedException e) {
e.printStackTrace();
}
account.get(10);
account2.get(10);
}));
}
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("cas :" + account.getBalance());
System.out.println("无锁 无cas:" + account2.getBalance());
}
}
class Account {
private AtomicInteger balance;
Account(int over) {
this.balance = new AtomicInteger(over);
}
public void get(int num) {
while (true) {
int o = balance.get();
int remain = o - num;
if (balance.compareAndSet(o, remain)) {
break;
}
}
}
int getBalance() {
return balance.get();
}
}
class Account2 {
private int balance;
Account2(int over) {
this.balance = over;
}
public void get(int num) {
this.balance -= num;
}
int getBalance() {
return this.balance;
}
}
典型的银行取款案例运行结果
发现没有用synchronized同样也保证了线程安全。
其中的关键是这个compareAndSet比较并设置值,简称CAS,它是原子操作的,传入目标值和想要改为多少的值,会先拿到目标值查看该值是不是还是传入的值,如果还是说明没有动过,然后重新设置新值返回true,如果被其他线程动过了,返回false进入下一次循环
Atomic包
Atomic包下提供了很多实现cas操作的工具类
AtomicInteger
AtomicInteger atomicInteger = new AtomicInteger(10);
atomicInteger.get(); // 获取值
atomicInteger.getAndIncrement(); // num++
atomicInteger.incrementAndGet(); // ++num
atomicInteger.getAndDecrement(); // num--
atomicInteger.decrementAndGet(); // --num
atomicInteger.addAndGet(5); // 添加5然后返回
atomicInteger.getAndAdd(5); // 返回然后添加5
atomicInteger.getAndSet(5); // 获取值后重新设置新值5
atomicInteger.set(5); // 返回然后添加5 // 设置新值5
getAndUpdate
发现getAndUpdate方法需要传入一个IntUnaryOperator接口
@FunctionalInterface修饰说明可以用lambda表达式
在getAndUpdate方法里默认调用的就是applyAsInt方法,也就是说我们可以自己来实现里面的操作只要接受一个参数并返回一个参数即可,内部操作自己实现
其内部也是cas操作保证线程安全
AtomicLong ,AtomicBoolean,AtomicReference。。。功能类似
ABA问题
@Slf4j
public class Test2 {
static AtomicReference<String> arf = new AtomicReference<>("A");
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
String prev = arf.get();
other();
TimeUnit.SECONDS.sleep(1);
log.debug("change A->C {}", arf.compareAndSet(prev, "C"));
}
private static void other() throws InterruptedException {
new Thread(() -> {
log.debug("change A->B {}", arf.compareAndSet(arf.get(), "B"));
}, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
log.debug("change B-> A {}", arf.compareAndSet(arf.get(), "A"));
}, "t3").start();
}
}
发现全部操作都成功了,在主线程获取到A的时候然后去等待,线程2和线程3中间多次操作又变回会了A
当它去比较还是之前拿到的那个值是不是一样的时候,发现是一样的于是操作成功了。
AtomicStampedReference解决ABA问题
原理就是在加一个版本号,每次操作的时候把版本号一并跟新
@Slf4j
public class Test2 {
static AtomicStampedReference<String> arf = new AtomicStampedReference<>("A", 1);
public static void main(String[] args) throws InterruptedException {
log.debug("main start...");
String prev = arf.getReference();
int stamp = arf.getStamp();
other();
TimeUnit.SECONDS.sleep(1);
log.debug("change A->C {}", arf.compareAndSet(prev, "C", stamp, stamp + 1));
}
private static void other() throws InterruptedException {
new Thread(() -> {
String prev = arf.getReference();
int stamp = arf.getStamp();
log.debug("change A->B {}", arf.compareAndSet(prev, "C", stamp, stamp + 1));
}, "t2").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
String prev = arf.getReference();
int stamp = arf.getStamp();
log.debug("change B-> A {}", arf.compareAndSet(prev, "C", stamp, stamp + 1));
}, "t3").start();
}
}