A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 时间留下最真 于 2018-12-13 20:08 编辑

java并发包下的CAS相关的原子操作,以及Java 8如何改进和优化CAS操作的性能。
因为Atomic系列的原子类,无论在并发编程、JDK源码、还是各种开源项目中,都经常用到。而且在Java并发面试中,这一块也属于比较高频的考点,所以还是值得给大家聊一聊。
好,我们正式开始!假设多个线程需要对一个变量不停的累加1,比如说下面这段代码:
[AppleScript] 纯文本查看 复制代码
public class HelloWord{
private int data=0;
//多线程同时多data操作 data++
}
实际上,上面那段代码是不ok的,因为多个线程直接这样并发的对一个data变量进行修改,是线程不安全性的行为,会导致data值的变化不遵照预期的值来改变。

举个例子,比如说20个线程分别对data执行一次data++操作,我们以为最后data的值会变成20,其实不是。

最后可能data的值是18,或者是19,都有可能,因为多线程并发操作下,就是会有这种安全问题,导致数据结果不准确。

至于为什么会不准确?那不在本文讨论的范围里,因为这个一般只要是学过java的同学,肯定都了解过多线程并发问题。

所以,对于上面的代码,一般我们会改造一下,让他通过加锁的方式变成线程安全的:
[AppleScript] 纯文本查看 复制代码
public class HelloWord{
private int data=0;
public synchronized void increment(){data++;}
//多线程同时调用increment方法 data++

这个时候,代码就是线程安全的了,因为我们加了synchronized,也就是让每个线程要进入increment()方法之前先得尝试加锁,同一时间只有一个线程能加锁,其他线程需要等待锁。

通过这样处理,就可以保证换个data每次都会累加1,不会出现数据错乱的问题。
但是,如此简单的data++操作,都要加一个重磅的synchronized锁来解决多线程并发问题,就有点杀鸡用牛刀,大材小用了。

虽然随着Java版本更新,也对synchronized做了很多优化,但是处理这种简单的累加操作,仍然显得“太重了”。人家synchronized是可以解决更加复杂的并发编程场景和问题的。

而且,在这个场景下,你要是用synchronized,不就相当于让各个线程串行化了么?一个接一个的排队,加锁,处理数据,释放锁,下一个再进来。
对于这种简单的data++类的操作,其实我们完全可以换一种做法,java并发包下面提供了一系列的Atomic原子类,比如说AtomicInteger。

他可以保证多线程并发安全的情况下,高性能的并发更新一个数值。我们来看下面的代码:
[AppleScript] 纯文本查看 复制代码
public class HelloWord{
private AtomicInteger data=new AtomicInteger (0);
//多线程同时执行 data.incrementAndGet()
}
大家看上面的代码,是不是很简单!多个线程可以并发的执行AtomicInteger的incrementAndGet()方法,意思就是给我把data的值累加1,接着返回累加后最新的值。

这个代码里,就没有看到加锁和释放锁这一说了吧!

实际上,Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性

那什么是CAS呢?他的全称是:Compare and Set,也就是先比较再设置的意思。

首先,每个线程都会先获取当前的值,接着走一个原子的CAS操作,原子的意思就是这个CAS操作一定是自己完整执行完的,不会被别人打断。

然后CAS操作里,会比较一下说,唉!大兄弟!现在你的值是不是刚才我获取到的那个值啊?

如果是的话,bingo!说明没人改过这个值,那你给我设置成累加1之后的一个值好了!

同理,如果有人在执行CAS的时候,发现自己之前获取的值跟当前的值不一样,会导致CAS失败,失败之后,进入一个无限循环,再次获取值,接着执行CAS操作!
来看一下这整个过程:

  • 首先第一步,我们假设线程一咔嚓一下过来了,然后对AtomicInteger执行incrementAndGet()操作,他底层就会先获取AtomicInteger当前的值,这个值就是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是原子的,别人不会打断你!

通过这个机制,不需要加锁这么重量级的机制,也可以用轻量级的方式实现多个线程安全的并发的修改某个数值。


1 个回复

倒序浏览
奈斯
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马