博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程系列——原子类的实现(CAS算法)
阅读量:6919 次
发布时间:2019-06-27

本文共 3371 字,大约阅读时间需要 11 分钟。

  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。

 java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

 除了在i++操作时使用synchroinzed关键字实现同步外,还可以使用AtomicInteger原子类进行实现

  • .util.concurrent.atomic 包中提供了以下原子类, 它们是线程安全的类

  • AtomicBoolean -- 原子布尔
  • AtomicInteger -- 原子整型
  • AtomicIntegerArray -- 原子整型数组
  • AtomicLong -- 原子长整型
  • AtomicLongArray -- 原子长整型数组
  • AtomicReference -- 原子引用
  • AtomicReferenceArray -- 原子引用数组
  • AtomicMarkableReference -- 原子标记引用
  • AtomicStampedReference -- 原子戳记引用
  • AtomicIntegerFieldUpdater -- 用来包裹对整形 volatile 域的原子操作
  • AtomicLongFieldUpdater -- 用来包裹对长整型 volatile 域的原子操作
  • AtomicReferenceFieldUpdater -- 用来包裹对对象 volatile 域的原子操作 

 

Java提供的原子类是靠 sun 基于 CAS 实现的,CAS 是一种乐观锁。参考:

  原子变量类相当于一种泛化的 volatile 变量,能够支持原子的和有条件的读-改-写操作。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(如果该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上非常像一个扩展的 Counter 类,但在发生竞争的情况下能提供更高的可伸缩性,因为它直接利用了硬件对并发的支持。

AtomicInteger的实现

 AtomicInteger 是一个支持原子操作的 Integer 类,就是保证对 AtomicInteger 类型变量的增加和减少操作是原子性的,不会出现多个线程下的数据不一致问题。如果不使用 AtomicInteger,要实现一个按顺序获取的 ID,就必须在每次获取时进行加锁操作,以避免出现并发时获取到同样的 ID 的现象。

  接下来通过源代码来看 AtomicInteger 具体是如何实现的原子操作。

  首先看 value 的声明:

private volatile int value;

   volatile 修饰的 value 变量,保证了变量的可见性。

 incrementAndGet() 方法,下面是具体的代码: 

public final int incrementAndGet() {        for (;;) {            int current = get();            int next = current + 1;            if (compareAndSet(current, next))                return next;        } }

 通过源码,可以知道,这个方法的做法为先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做 compareAndSet 操作,直到成功为止,也就是修改的根本在 compareAndSet 方法里面,compareAndSet()方法的代码如下:

public final boolean compareAndSet(int expect, int update) {        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}

  compareAndSet()方法调用的compareAndSwapInt()方法的声明如下,是一个native方法。 

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, intvar5);

 compareAndSet 传入的为执行方法时获取到的 value 属性值,next 为加 1 后的值, compareAndSet 所做的为调用 Sun 的 UnSafe 的 compareAndSwapInt 方法来完成,此方法为 native 方法,compareAndSwapInt 基于的是 CPU 的 CAS 指令来实现的。所以基于 CAS 的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于 CAS 操作是 CPU 原语,所以性能比较好。

  类似的,还有 decrementAndGet() 方法。它和 incrementAndGet() 的区别是将 value 减 1,赋值给next 变量。

      AtomicInteger 中还有 getAndIncrement() 和 getAndDecrement() 方法,他们的实现原理和上面的两个方法完全相同,区别是返回值不同,前两个方法返回的是改变之后的值,即 next。而这两个方法返回的是改变之前的值,即 current。还有很多的其他方法,就不列举了。

 

CAS算法

CAS(Compare-And-Swap)算法保证数据操作的原子性。

CAS 算法是硬件对于并发操作共享数据的支持。

CAS 包含了三个操作数:

  内存值 V
  预估值 A
  更新值 B

当且仅当 V == A 时,V 将被赋值为 B,否则什么都不做,

当然如果需要的话,可以设计成自旋锁的模式,循环着不断进行判断 V 与 A 是否相等。

考虑如下问题:

关于CAS操作 提出问题:

情况1.
两个线程A和B同时对AtomicInteger(10)进行incrementAndGet()方法,都获取到current = 10 ,compareAndSet比较时,内存总的值均未被修改, 那两个线程都将执行了+1,那返回的结果应该都为11吧?
情况2
两个线程A和B同时对AtomicInteger(10)进行incrementAndGet()方法,都获取到current = 10 ,线程A线程先进行了compareAndSwapInt导致内存中的值变为11,那线程B的在和内存中的值比较一直不相等, 那线程B不是死循环了吗?

解决问题:

其实问题还是在CAS上,内存值,预估值,更新值的问题

情况1:不会存在返回结果都是 11 的情况。原子类提供的就是原子操作,多线程情况下不会存在数据不一致的情况。具体原因就是 CAS 操作,它会读取内存和预期值(11)作比较,如果相同才会进行赋值。
情况2:同理。
其实你说的两个线程“同时”,原子类的目的本身就是为了避免这种场景下的数据不一致,所以你说的这两种情况是不存在的。
当然如果使用继承Thread类的方式实现多线程,那它的原子类变量是自己维护的,也就是线程独立的,那就会存在问题。实现Runnable接口就不会存在这个问题,因为是资源共享的。

 

 

参考: 

参考:

转载地址:http://yahcl.baihongyu.com/

你可能感兴趣的文章
zabbix3.0.0升级zabbix3.2.1版本
查看>>
安卓手机远程操作win8.1,RD Client设置教程
查看>>
Java Formatter 阅读心得
查看>>
融360 D轮融资超10亿 平台型互联网金融价值凸显
查看>>
ip_conntrack对路由结果的cache
查看>>
rsync+inotify搭建实时同步系统
查看>>
sccm 2007 r2 step by step 之十七 SCCM的一次总结
查看>>
rsyslog+mysql+loganalyzer构建日志服务器
查看>>
自定义MessageBox
查看>>
MySQL 管理猿利器: MySQL ODBC for iPhone!
查看>>
Linux网络属性及其配置
查看>>
添加第三方类库造成的Undefined symbols for architecture i386:编译错误
查看>>
上不了网,我的解决过程
查看>>
不连续子网掩码的魅力
查看>>
查看Nginx,Apache,lighttpd,Mysql,Php的编译参数
查看>>
在RHEL5下使用bind构建分离解析的域名服务器
查看>>
修复点击网卡本地连接属性无反应netshell注册失败80020009问题
查看>>
ExtJS4.2学习(22)登录界面
查看>>
MySQL下的安全问题--.mysql_history你注意到了吗?
查看>>
Oracle 11gR2 安装RAC错误之--HOSTS文件错误
查看>>