前言

在Java并发包中有这样一个包,java.util.concurrent.atomic,该包是对Java部分数据类型的原子封装,在原有数据类型的基础上,提供了原子性的操作方法,保证了线程安全。以下以AtomicInteger为例,来看一下是怎样实现的。

  1. public final int incrementAndGet() {
  2. for (;;) {
  3. int current = get();
  4. int next = current + 1;
  5. if (compareAndSet(current, next))
  6. return next;
  7. }
  8. }
  1. public final int decrementAndGet() {
  2. for (;;) {
  3. int current = get();
  4. int next = current - 1;
  5. if (compareAndSet(current, next))
  6. return next;
  7. }
  8. }

以这两个方法为例,incrementAndGet方法相当于原子性的++i,decrementAndGet方法相当于原子性的--i(依据第一章第二章我们知道++i或--i不是一个原子性的操作),这两个方法中都没有使用堵塞式的方式来保证原子性(如Synchronized),那它们是怎样保证原子性的呢,以下引出CAS。

Compare And Swap

CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简介一下这个指令的操作过程:首先,CPU 会将内存中将要被更改的数据与期望的值做比較。然后,当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。这一系列的操作是原子的。它们尽管看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是“我觉得原有的值应该是什么,假设是,则将原有的值更新为新值,否则不做改动,并告诉我原来的值是多少”。(这段描写叙述引自《Java并发编程实践》)
       简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要改动的新值B。当且仅当预期值A和内存值V同样时,将内存值V改动为B,否则返回V。这是一种乐观锁的思路,它相信在它改动之前,没有其他线程去改动它;而Synchronized是一种悲观锁,它觉得在它改动之前,一定会有其他线程去改动它,悲观锁效率非常低。以下来看一下AtomicInteger是怎样利用CAS实现原子性操作的。

volatile变量

  1. private volatile int value;

首先声明了一个volatile变量value,在第二章我们知道volatile保证了变量的内存可见性,也就是全部工作线程中同一时刻都能够得到一致的值。

  1. public final int get() {
  2. return value;
  3. }

Compare And Set

  1. // setup to use Unsafe.compareAndSwapInt for updates
  2. private static final Unsafe unsafe = Unsafe.getUnsafe();
  3. private static final long valueOffset;// 注意是静态的
  4.  
  5. static {
  6. try {
  7. valueOffset = unsafe.objectFieldOffset
  8. (AtomicInteger.class.getDeclaredField("value"));// 反射出value属性,获取其在内存中的位置
  9. } catch (Exception ex) { throw new Error(ex); }
  10. }
  11.  
  12. public final boolean compareAndSet(int expect, int update) {
  13. return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
  14. }

比較并设置,这里利用Unsafe类的JNI方法实现,使用CAS指令,能够保证读-改-写是一个原子操作。compareAndSwapInt有4个參数,this - 当前AtomicInteger对象,Offset - value属性在内存中的位置(须要强调的是不是value值在内存中的位置),expect - 预期值,update - 新值,依据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于运行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe。

循环设置

       如今在来看开篇提到的两个方法,我们拿incrementAndGet来分析一下事实上现过程。
  1. public final int incrementAndGet() {
  2. for (;;) {// 这样优于while(true)
  3. int current = get();// 获取当前值
  4. int next = current + 1;// 设置更新值
  5. if (compareAndSet(current, next))
  6. return next;
  7. }
  8. }

循环内,获取当前值并设置更新值,调用compareAndSet进行CAS操作,假设成功就返回更新至,否则重试到成功为止。这里可能存在一个隐患,那就是循环时间过长,总是在当前线程compareAndSet时,有还有一个线程设置了value(点子太被了),这个当然是属于小概率时间,眼下Java貌似还不能处理这样的情况。

缺点

       尽管使用CAS能够实现非堵塞式的原子性操作,可是会产生ABA问题,关于ABA问题,计划单拿出一章来整理。
       (完)
       本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/38471987,转载请注明。

Java线程(十):CAS的更多相关文章

  1. Java线程专栏文章汇总(转)

    原文:http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程        Java线程(一):线程安全与不安全 Java线程 ...

  2. Java线程专栏文章汇总

        转载自 http://blog.csdn.net/ghsau/article/details/17609747 JDK5.0之前传统线程        Java线程(一):线程安全与不安全 J ...

  3. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  4. Java多线程和并发(十二),Java线程池

    目录 1.利用Executors创建线程的五种不同方式 2.为什么要使用线程池 3.Executor的框架 4.J.U.C的三个Executor接口 5.ThreadPoolExecutor 6.线程 ...

  5. Java 线程总结(十四)

    1.在异步任务进程中,一种常见的场景是,主线程提交多个异步任务,然后希望有任务完成就处理结果,并且按任务完成顺序逐个处理,对于这种场景,Java 并发包提供了一个方便的方法,使用 Completion ...

  6. Java线程和多线程(十二)——线程池基础

    Java 线程池管理多个工作线程,其中包含了一个队列,包含着所有等待被执行的任务.开发者可以通过使用ThreadPoolExecutor来在Java中创建线程池. 线程池是Java中多线程的一个重要概 ...

  7. 深入浅出Java线程池:源码篇

    前言 在上一篇文章深入浅出Java线程池:理论篇中,已经介绍了什么是线程池以及基本的使用.(本来写作的思路是使用篇,但经网友建议后,感觉改为理论篇会更加合适).本文则深入线程池的源码,主要是介绍Thr ...

  8. Java线程的概念

    1.      计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...

  9. 细说进程五种状态的生老病死——双胞胎兄弟Java线程

    java线程的五种状态其实要真正高清,只需要明白计算机操作系统中进程的知识,原理都是相同的. 系统根据PCB结构中的状态值控制进程. 单CPU系统中,任一时刻处于执行状态的进程只有一个. 进程的五种状 ...

随机推荐

  1. C/C++存储区划分

    一. 在c中分为这几个存储区1.栈 - 由编译器自动分配释放2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化 ...

  2. 【Cocos2D-x 3.5实战】坦克大战(1)环境配置

    前言: 最近课比较少,空闲时间比较多,一有时间就东想西想,想着想着就突然想到做手机游戏(android)了,学习下CoCos2d.看了一些CoCos2D的相关文档和教程,觉得是时候实战了,但是苦于没有 ...

  3. BZOJ 2982: combination( lucas )

    lucas裸题. C(m,n) = C(m/p,n/p)*C(m%p,n%p). ----------------------------------------------------------- ...

  4. php内核--SAPI概述

  5. pure学习笔记

    最近研究Pure,发现这个对于写css来说确实是个好的框架,特此总结了一番,如有错误或不足的地方,欢迎交流指点,轻拍. 此文运用的是优雅的Markdown而书 Pure学习笔记 #写在最前 1# Pu ...

  6. PHP自练项目之数字与文字的分页效果在函数中实现

    /** * * @param $_sql * @param $_size */ function _page($_sql,$_size) { //将里面的所有变量取出来,外部可以访问 global $ ...

  7. tomcat远程debug端口开启

    declare -x CATALINA_OPTS="-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt ...

  8. 编写带参数decorator

    无参的@log装饰器: def log(f): def fn(x): print 'call ' + f.__name__ + '()...' return f(x) return fn 发现对于被装 ...

  9. Apache proxy中转设置

    参考http://sjsky.iteye.com/blog/1067119 打开http.conf  (macOS中 Apache配置文件在/etc/apache2/中   etc是隐藏的) 确保下面 ...

  10. NET Core,Ubuntu运行

    NET Core,如何开发跨平台的应用并部署至Ubuntu运行 之前写了一篇博文宣布Rabbit Rpc跨平台了“拥抱.NET Core,跨平台的轻量级RPC:Rabbit.Rpc”,在过程中尝试了如 ...