一、总述
AtomicInteger是一个原子类,不会出现多线程情况下的不同线程之间的写覆盖等问题。主要涉及到的实现方式是CAS操作(compare and set或者swap)翻译过来就是比较并交换。且这个CAS操作是原子级操作。
二、代码实现
public class AccountCas {
private AtomicInteger balance;
public AccountCas(int balance) {
this.balance = new AtomicInteger(balance);
}
public void withdraw(Integer amount) {
while (true) {
int prev = balance.get();
int next = prev - amount;
if (balance.compareAndSet(prev, next)) {
break;
}
}
}
}
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
private static final Unsafe U = Unsafe.getUnsafe();
private static final long VALUE
= U.objectFieldOffset(AtomicInteger.class, "value");
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
三、代码解释
在AtomicInteger这个类的源码中可以看出,在构造方法使用时是给value这个属性给予初始值,且这个属性被volatile关键字修饰。因此在不同线程之间对于这个变量的修改对于所有线程之间都是可见的。在对于原子对象进行cas操作的时候调用的是compareAndSet方法,在代码的第12行,会返回一个是否修改成功的结果。在这个方法的执行过程中prev这个参数的目的是针对当前线程理应操作的结果,如果在其他线程完成了对于这个balance的修改,则balance本身的value值和prev就不会相等。因此必定返回false,表示修改失败,进而无法设置next这个数值。
四、CAS相对于加锁方式的特点
- 执行效率高,原因在于加锁方式会涉及到上下文切换,这个切换代价是很大的,远远超过于执行失败,直接下次尝试的时间。
- CAS操作一定要发生在多核CPU的情况下,同时CPU的核心数应该大于线程数,这样的话可以保证每一个线程可以一直持续在while循环中运行而不会发生CPU的切换,因为CPU的切换也是需要涉及到上下文切换,具有一定的代价损耗。
- CAS是一种乐观锁的思想,执行失败的话下次重新尝试修改。而加锁方式是基于一种悲观锁的思想,一个线程进来修改这个变量,其他线程必须阻塞等待才行。