先看一个例子,AtomicInteger 实现的线程安全的累加器
public class AtomicIntTest { public static void main(String[] args) { AddRunnable addRunnable = new AddRunnable(); Thread myThread1 = new Thread(addRunnable); Thread myThread2 = new Thread(addRunnable); myThread1.start(); myThread2.start(); try { myThread1.join(); myThread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(addRunnable.count); } } class AddRunnable implements Runnable { AtomicInteger count = new AtomicInteger(); @Override public void run() { for (int i = 0; i < 100000; i++) { count.incrementAndGet(); } } }
AtomicInteger源码分析
下面通过AtomicInteger的源码来看一下是怎么在没有锁的情况下保证数据正确性。首先看一下incrementAndGet方法的实现
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } }
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
注意::valueOffset叫做 偏移量 我们知道valueOffset指向的地址对应的值就是原始变量的值 执行逻辑就是 expect 和valueOffset 比较 相同 就替换成 update 不同继续循环
总结:
1 虽然基于CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小,型如计数器这样的需求用起来才有效,否则也不会有锁的存在了。实际来发中,用锁的情况还是较多。
2 并发越高,失败的次数会越多,CAS如果长时间不成功,会极大的增加CPU的开销。(毕竟死循环嘛) 因此CAS不适合竞争十分频繁的场景。
3 CAS只能保证一个共享变量的原子操作。当对多个共享变量操作时,CAS就无法保证操作的原子性,这时就可以用锁,或者把多个共享变量合并成一个共享变量来操作。使用AtomicReference。