除了加锁以外,还可以使用原子类实现操作原子性。它底层采用CAS算法,使用简单、性能高效、线程安全。
简单示范下它的使用。
public class Demo24 { public static void main(String[] args) { AtomicInteger integer = new AtomicInteger(1); // 不能向Integer一样自动装箱(Integer i = 1) System.out.println(integer.getAndIncrement()); //相当于i++ System.out.println(integer.incrementAndGet()); //相当于++i } }
如何验证它的原子性呢?看看下面的代码。
public class Demo25 { public static void main(String[] args) throws InterruptedException { AtomicInteger integer = new AtomicInteger(); Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 1000000; i++) { integer.getAndIncrement(); } System.out.println("thread finish..."); } }; new Thread(r).start(); new Thread(r).start(); TimeUnit.SECONDS.sleep(1); System.out.println(integer.get()); } }
运行输出的结果如下。确实是原子性的哦。
thread finish... thread finish... 2000000 为
为什么会这么神奇呢?我们来阅读下源码一探究竟吧。
private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { }
它使用volatile关键字修饰了value,这样在CAS操作时就不会出错。
private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
在静态块中,通过计算偏移地址,获取value相对于对象的偏移地址,这样就可以直接在对应内存对数据进行操作。
接着看看自增操作是如何完成的。
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
到Unsafe中看看。下面的参数列表中var1是数组或者对象(要修改的值是数组的元素或者对象的属性),var2是offset偏移地址,var4是delta,参数变化量。可以看到,它是一个do-while循环,如果CAS失败会重新尝试,一直到成功。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); // 以Volatile形式读取变量的值 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //对变量进行CAS操作 return var5; }
为了帮助大家更加深入理解,我举一个例子。比如线程1,2同时对变量A进行修改,线程1速度较快,读到它的值是1,准备CAS操作,线程2此时也开始读到数据是1,但是进行CAS操作时线程1在执行,因此它无法CAS成功。当线程1执行完CAS操作后,数据A的值变成2。此时线程2尝试下一轮获取变量的值就是2(变量通过volitile修饰了),然后进行CAS操作值就变成了3.不过由于是do-while循环,var5的值仍然是2,getAndAddInt返回的参数是2,因此也符合getAndIncrement的逻辑定义(先获取值再自增)。
用图片描述下。
如果是incrementAndGet的话,其底层逻辑会不会是while循环呢?答案是否,大家看看源码。
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
原来是在原来的return的值基础上+1,这样就可以复用一个底层方法getAndAddInt
实现两个不同的逻辑。这个思想值得大家学习呀。
可见,原子类底层也是采用CAS算法保证的操作原子性,并且它提供了compareAndSet
直接给外部使用。
// 第一个值是期望值,第二个值是要更新后数据,符合期望值expect就会更新数据为update public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
写个demo测试下
public class Demo26 { public static void main(String[] args) { AtomicInteger integer = new AtomicInteger(10); integer.compareAndSet(20, 15); // fail System.out.println(integer); integer.compareAndSet(10, 30); // success System.out.println(integer); } }