JAVA并发编程学习笔记之CAS操作

CAS操作

  CAS是单词compare and set的缩写,意思是指在set之前先比较该值有没有变化,只有在没变的情况下才对其赋值。

  我们常常做这样的操作

  if(a==b) {

  a++;

  }

  试想一下如果在做a++之前a的值被改变了怎么办?a++还执行吗?出现该问题的原因是在多线程环境下,a的值处于一种不定的状态。采用锁可以解决此类问题,但CAS也可以解决,而且可以不加锁。


int expect = a;

if(a.compareAndSet(expect,a+1)) {

doSomeThing1();

} else {

doSomeThing2();

}

  这样如果a的值被改变了a++就不会被执行。

  按照上面的写法,a!=expect之后,a++就不会被执行,如果我们还是想执行a++操作怎么办,没关系,可以采用while循环


while(true) {

int expect = a;

if (a.compareAndSet(expect, a + 1)) {

doSomeThing1();

return;

} else {

doSomeThing2();

}

}

  采用上面的写法,在没有锁的情况下实现了a++操作,这实际上是一种非阻塞算法。

  应用

   java.util.concurrent.atomic包中几乎大部分类都采用了CAS操作,以AtomicInteger为例,看看它几个主要方法的实现:


public final int getAndSet(int newValue) {

for (;;) {

int current = get();

if (compareAndSet(current, newValue))

return current;

}

}

  getAndSet方法JDK文档中的解释是:以原子方式设置为给定值,并返回旧值。原子方式体现在何处,就体现在compareAndSet上,看看compareAndSet是如何实现的:

  public final boolean compareAndSet(int expect, int update) {

  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

  }

  不出所料,它就是采用的Unsafe类的CAS操作完成的。

  再来看看a++操作是如何实现的:


public final int getAndIncrement() {

for (;;) {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return current;

}

}

  几乎和最开始的实例一模一样,也是采用CAS操作来实现自增操作的。

  ++a操作和a++操作类似,只不过返回结果不同罢了


public final int incrementAndGet() {

for (;;) {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return next;

}

}

  此外,java.util.concurrent.ConcurrentLinkedQueue类全是采用的非阻塞算法,里面没有使用任何锁,全是基于CAS操作实现的。CAS操作可以说是JAVA并发框架的基础,整个框架的设计都是基于CAS操作的。

  缺点:

  1、ABA问题

  CAS操作容易导致ABA问题,也就是在做a++之间,a可能被多个线程修改过了,只不过回到了最初的值,这时CAS会认为a的值没有变。a在外面逛了一圈回来,你能保证它没有做任何坏事,不能!!也许它讨闲,把b的值减了一下,把c的值加了一下等等,更有甚者如果a是一个对象,这个对象有可能是新创建出来的,a是一个引用呢情况又如何,所以这里面还是存在着很多问题的,解决ABA问题的方法有很多,可以考虑增加一个修改计数,只有修改计数不变的且a值不变的情况下才做a++,也可以考虑引入版本号,当版本号相同时才做a++操作等,这和事务原子性处理有点类似!

  2、比较花费CPU资源,即使没有任何争用也会做一些无用功。

  3、会增加程序测试的复杂度,稍不注意就会出现问题。

  总结:

  可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题。

最新内容请见作者的GitHub页:http://qaseven.github.io/

时间: 2024-07-30 13:24:53

JAVA并发编程学习笔记之CAS操作的相关文章

Java 并发编程学习笔记之核心理论基础_java

并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

Java 并发编程学习笔记之Synchronized简介_java

一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题.从语法上讲,Synchronized总共有三种用法: (1)修饰普通方法 (2)修饰静态方法 (3)修饰代码块 接下来我就通过几个例子程序来说明一下这三种使用方式(为了便于比较,三段代码除了Synchronized的使用方式不同以外,

java并发编程学习:用 Semaphore (信号量)控制并发资源

并发编程这方面以前关注得比较少,恶补一下,推荐一个好的网站:并发编程网 - ifeve.com,上面全是各种大牛原创或编译的并发编程文章. 今天先来学习Semaphore(信号量),字面上看,根本不知道这东西是干啥的,借用 并发工具类(三)控制并发线程数的Semaphore一文中的交通红绿信号灯的例子来理解一下: 一条4车道的主干道,假设100米长,每辆车假设占用的长度为10米(考虑到前后车距),也就是说这条道上满负载运行的话,最多只能容纳4*(100/10)=40辆车,如果有120辆车要通过的

java并发编程实践笔记

1, 保证线程安全的三种方法:     a, 不要跨线程访问共享变量     b, 使共享变量是final类型的     c, 将共享变量的操作加上同步 2, 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易. 3, 编写多线程程序, 首先保证它是正确的, 其次再考虑性能. 4, 无状态或只读对象永远是线程安全的. 5, 不要将一个共享变量裸露在多线程环境下(无同步或不可变性保护) 6, 多线程环境下的延迟加载需要同步的保护, 因为延迟加载会造成对象重复实例化 7, 对于volatil

java并发编程学习: ThreadLocal使用及原理

多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点.  先来看一下示例: package yjmyzz.test; public class ThreadLocalTest1 { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLo

java并发编程学习: 原子变量(CAS)

先上一段代码: package test; public class Program { public static int i = 0; private static class Next extends Thread { public void run() { i = i + 1; System.out.println(i); } } public static void main(String[] args) { Thread[] threads = new Thread[10]; for

java 数据库编程 学习笔记 不断更新

最近开始学习java,感觉java的数据库编程需要发个随笔记录一下,话不多说 切入正题.   一.数据库访问技术的简介                      应用程序  →  执行SQL语句 →数据库 → 检索数据结果 → 应用程序     ( ODBC         JDBC(两个常用的API))    java主要使用的 JDBC驱动程序进行数据库的编程 Java 应用程序 <------> JDBC   <------>  数据库     二.JDBC 的体系结构  

Java并发编程开发笔记——1简言

线程 风险:Java对线程的支持其实是一把双刃剑. 安全性问题:线程安全性可能是非常复杂的,在没有充足同步的情况下,多个线程的操作执行顺序是不可预测的,甚至会产生奇怪的结果. @NotThreadSafe是一个自定义标注,用于说明类和类成员的并发属性.(其他标注包括@ThreadSafe和@Immutable).如果用@ThreadSafe来标注某个类,那么开发人员可以放心地在多线程环境下使用这个类,维护人员也会发现它能保证线程安全性,而软件分析工具还可以识别出潜在的编码错误. 在UnsafeS

Java并发编程开发笔记——2线程安全性

在构建稳健的并发程序时,必须正确地使用线程和锁.但这些终归只是一些机制.要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的(Shared)和可变的(Mutable)状态的访问. "共享"意味着变量可以由多个线程同时访问,而"可变"则意味着变量的值在其生命周期内可以发生变化.我们将像讨论代码那样讨论线程安全性,但更侧重于如何防止在数据上发生不可控的并发访问. 一个对象是否需要是线程安全的,取决于它是否被多个线程访问.这指的是在程序中访问对象的方式