这是一个爱好者的投稿。作者【临湾村龙猫】最近在看Java的源码。本文介绍的是concurrent包中原子类的源码。总结。霍利斯稍作修改。介绍在多线程场景下,我们需要保证数据的安全,所以我们会考虑同步的方案,通常使用synchronized或者lock来处理。使用synchronized意味着内核模式的切换。这是一项繁重的手术。有没有办法方便的实现一些简单的数据同步,比如计数器等等。concurrent包下的atomic为我们提供了这样一个轻量级的数据同步选项。Importjava.util.concurrent.CountDownLatch;importjava.util.concurrent.atomic.AtomicInteger;publicclassApp{publicstaticvoidmain(String[]args)throwsException{CountDownLatchcountDownLatch=newCountDownLatch(100);AtomicIntegeratomicInteger=newAtomicInteger(0);for(inti=;i<100;i++){newThread(){@Overridepublicvoidrun(){atomicInteger.getAndIncrement();countDownLatch.countDown();}}.start();}countDownLatch.await();System.out.println(atomicInteger.get());}}上面代码中使用AtomicInteger声明了一个全局变量,在多线程中自增,代码中没有显式加锁。上面代码的输出一直是100,如果把AtomicInteger换成Integer,打印出来的结果基本都不到100,也说明AtomicInteger声明的变量在多线程场景下可以保证线程安全。下面我们分析一下它的原理。原理我们可以看一下AtomicInteger的代码,它的值存储在一个volatileint中。volatile只能保证这个变量的可见性。不能保证他的原子性。你可以看一下getAndIncrement这个类似i++的函数,可以发现它调用了UnSafe中的getAndAddInt。谁是不安全的?UnSafe为java提供了直接操作底层的能力。更进一步,我们可以找到实现方法:如何保证原子性:自旋+CAS(乐观锁)。在这个过程中,通过compareAndSwapInt比较和更新值。如果更新失败,重新获取旧值,然后更新。优点和缺点与其他锁相比,CAS不执行内核态操作,并且有一些性能改进。但是,同时引入了自旋。当锁竞争高时,自旋次数会增加。CPU资源消耗会很高。也就是说,CAS+spin适用于低并发、数据同步的应用场景。Java8所做的改进和努力在Java8中引入了4种新的计数器类型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。它们都继承自Striped64。LongAdder和AtomicLong有什么区别?Atomic*的问题在于它只能用于低并发场景。因此,LongAddr在此基础上引入了段锁的概念。你可以参考《JDK8系列之LongAdder解析》看看你做了什么。大概,在竞争不激烈的时候,所有线程都通过CAS修改同一个变量(Base)。当竞争激烈时,会对当前线程进行哈希修改Cell(多段锁)。可以看出大致的实现原理是:通过CAS乐观锁保证原子性,通过自旋保证当前修改的最终修改成功,通过减小锁粒度(多段锁)来提高并发性能.【本文为专栏作家霍利斯原创文章,作者微信公众号Hollis(ID:hollishuang)】点此阅读更多本作者好文
