本文将讲述java并发契约下CAS相关的原子操作,以及Java8如何提升和优化CAS操作的性能。因为Atomic系列的原子类在并发编程、JDK源码或者各种开源项目中经常用到。而且在Java并发面试中,这个也是比较高频的考点,所以很值得大家聊一聊。场景介绍,问题突出,正式开始!假设多个线程需要不断地给一个变量加1,比如下面的代码:其实上面的代码是不行的,因为多个线程直接并发地通过这种方式修改一个数据变量,是一个线程会不安全的行为导致数据值的变化不按预期值变化。比如20个线程分别对data进行data++操作,我们以为data的最终值会变成20,其实不是。最后数据的值可能是18或者19,这是有可能的,因为在多线程并发操作下,会存在这样的安全问题,导致数据结果不准确。至于为什么会不准确?那不在本文的讨论范围之内,因为这个一般只要学过java的同学肯定了解多线程并发的问题。初步解决方案:synchronized那么,对于上面的代码,一般我们会通过加锁的方式对其进行修改,使其成为线程安全的:此时,代码是线程安全的,因为我们加了synchronized,也就是让每个线程尝试加锁在进入increment()方法之前。同一时间只能有一个线程加锁,其他线程需要等待加锁。通过这个过程,可以保证数据每变化一次就加1,不会出现数据混乱的问题。老规矩!我们看下图感受下synchronizedlocking的效果和氛围,相当于N个线程一个一个排队更新值。但是,这么简单的data++操作,却需要一个重磅的synchronized锁来解决多线程并发的问题,有点大材小用了。虽然做了很多优化来与Java版本的更新同步,但是处理这种简单的累加操作还是显得“太重”了。人们同步可以解决更复杂的并发编程场景和问题。而且,这种场景下,如果使用synchronized,不就相当于把每个线程都序列化了吗?一个一个排队,加锁,处理数据,释放锁,下一个进来。更高效的解决方案:Atomic原子类及其底层原理对于这个简单的data++类的操作,我们其实可以换一种方式来实现。java并发下提供了一系列Atomic原子类,比如AtomicInteger。可以在多线程并发安全的情况下保证一个值的高性能并发更新。我们来看下面的代码:看看上面的代码,是不是很简单!多个线程可以并发执行AtomicInteger的incrementAndGet()方法,也就是帮我把data的值加1,累加后返回最新的值。在这段代码中,你看不到锁和释放锁!其实Atomic原子类底层并没有使用传统的锁机制,而是使用了无锁的CAS机制。CAS机制保证了多线程修改一个值的安全性。那么什么是CAS?他的全称是:CompareandSet,意思是先比较再设置。话不多说,先上图吧!我们来看上图。如果有3个线程要同时修改一个AtomicInteger的值,它们的底层机制是这样的:首先,每个线程都会先获取当前值,然后进行原子CAS操作。意味着这个CAS操作必须完全由自己执行,不会被别人打断。然后在CAS操作中,会对比说,唉!大哥!你现在的值是我刚才得到的值吗?如果是,宾果游戏!说明这个值没有人改过,所以可以设置成累加1后的值!同样,如果有人在执行CAS时发现之前得到的值与当前值不一样,也会导致CAS失败。失败后会进入死循环,重新获取值,然后执行CAS操作!好的!现在我们对照上图看一下整个过程:首先假设线程点击结束,然后对AtomicInteger执行incrementAndGet()操作,底层会先获取AtomicInteger的当前值,this的值为0,此时没有其他线程可以和他竞争!他不管那么多,直接执行原子CAS操作,问人家:大哥,你的值现在还是0吗?如果是,说明没有人修改过!太好了,加我1,设置为1。这样AtomicInteger的值就变成了1!然后线程2和线程3同时跑过来了,因为底层不是基于锁机制,而是无锁的CAS机制,所以它们可能并发执行incrementAndGet()操作。然后他们都拿到了AtomicInteger的当前值,也就是1然后线程2先发起了原子CAS操作!注意CAS是原子的,此时只有一个线程在执行!然后线程2问:兄弟,你的值现在还是1吗?如果是,很好,说明没有人改,我改成2,AtomicInteger的值也改成2了。关键点来了:现在线程3发起了CAS操作,但是他还持有之前得到的1!这个时候线程3会问:兄弟,你的值现在还是1吗?坏消息来了!!!此时的值为2!线程3哭了,他说这期间居然有人改了这个值。算了,那我还是再取值吧,于是取了最新的值,取值为2。然后再次发起CAS操作,问一下,现在取值是2了吗?是的!太好了,没人改,我赶紧改了,AtomicInteger的值改成了3!上述整个过程就是所谓Atomic原子类的原理。不是基于锁机制序列化,而是基于CAS机制:先获取一个值,然后发起CAS,比较这个值是否被人改过?如果不是,只需更改值!这个CAS是原子的,别人不会打扰你的!通过这种机制,不需要加锁这种重量级的机制,也可以轻量级的实现对某个值的多个线程安全的并发修改。Java8优化了CAS机制,但是这个CAS有什么问题吗?必须有。比如大量线程并发修改一个AtomicInteger,可能会有很多线程不断自旋,进入无限循环。这些线程不停的获取值,然后发起CAS操作,但是发现这个值被别人改了,于是又进入下一个循环,获取值,发起CAS操作又失败,进入下一个循环再次。当大量线程并发更新AtomicInteger时,这个问题可能会更加明显,导致大量线程空循环,自转,性能和效率都不是特别好。于是,当当当当当,Java8推出了一个新类LongAdder,它正在尝试使用分段CAS和自动分段迁移来大幅提升多线程高并发执行CAS操作的性能!在LongAdder的底层实现中,首先有一个基值。一开始,多线程不停地累加值,都是基于base的累加。比如一开始累加变成base=5,然后如果发现并发更新线程数过多,就会实现分段CAS机制,即在内部构建一个Cell数组,每个数组都是一个数字段。这个时候让大量线程对不同Cell内部的value值进行CAS累加操作,让CAS计算压力分散到不同的Cell段值上!这样可以大大减少多线程并发更新同一个值时出现的死循环问题,大大提高多线程并发更新值的性能和效率!并且其内部实现了自动段迁移的机制,即如果某个cell的值无法进行CAS,则会自动寻找另一个cell段中的值进行CAS操作。这样也解决了线程白纺,一直纺等待执行CAS操作的问题。当一个线程来执行CAS操作时,可以尽快完成操作。最后,如果你想从LongAdder中得到当前累计的总值,会将基值和所有Cell段值相加返回给你。
