`
g21121
  • 浏览: 685531 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

原子操作(CAS)

 
阅读更多

        众所周知锁有两种:乐观锁与悲观锁。独占锁是一种悲观锁,而 synchronized 就是一种独占锁,synchronized 会导致其它所有未持有锁的线程阻塞,而等待持有锁的线程释放锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。而乐观锁用到的机制就是CAS。

        1.CAS(Compare And Set)

        Compare And Set(或Compare And Swap),CAS是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)、新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

        在java中可以通过锁和循环CAS的方式来实现原子操作。Java中 java.util.concurrent.atomic包相关类就是 CAS的实现,atomic包里包括以下类: 

 
AtomicBoolean 可以用原子方式更新的 boolean 值。
AtomicInteger 可以用原子方式更新的 int 值。
AtomicIntegerArray 可以用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。
AtomicLong 可以用原子方式更新的 long 值。
AtomicLongArray 可以用原子方式更新其元素的 long 数组。
AtomicLongFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference<V> AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
AtomicReference<V> 可以用原子方式更新的对象引用。
AtomicReferenceArray<E> 可以用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater<T,V> 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。
AtomicStampedReference<V> AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。

        atomic包中的类可将 volatile 值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类,其形式如下:  

boolean compareAndSet(expectedValue, updateValue);

        如果此方法(在不同的类间参数类型也不同)当前保持 expectedValue,则以原子方式将变量设置为 updateValue,并在成功时报告 true。此包中的类还包含获取并无条件设置值的方法,以及以下描述的较弱条件的原子更新操作 weakCompareAndSet。 

        这些方法的规范使实现能够使用当代处理器上提供的高效机器级别原子指令。但是在某些平台上,该支持可能需要某种形式的内部锁。因而,该方法不能严格保证不被阻塞 - 执行操作之前可能暂时阻塞线程。

 

        2.AtomicInteger

        AtomicInteger可以用原子方式更新的 int 值。AtomicInteger 可用在应用程序中(如以原子方式增加的计数器),并且不能用于替换 Integer。但是,此类确实扩展了 Number,允许那些处理基于数字类的工具和实用工具进行统一访问。 我们拿 AtomicInteger为例来学习下 CAS操作是如何实现的。

        通常情况下,在 Java中,i++等类似操作并不是线程安全的,因为 i++可分为三个独立的操作:获取变量当前值,为该值+1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作时“原子性”的。但是利用加锁的方式来实现该功能的话,代码将非常复杂及难以维护,如:

synchronized (lock) {
	i++;
}

        相关类中还需要增加 Object lock等额外标志,这样就带来了很多麻烦,增加了很多业务无关代码,给开发与维护带来了不便。

        然而利用 atomic包中相关类型就可以很简单实现此操作,以下是一个计数程序实例:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
	private AtomicInteger ai = new AtomicInteger();
	private int i = 0;

	public static void main(String[] args) {
		final Counter cas = new Counter();
		List<Thread> ts = new ArrayList<Thread>();
		// 添加100个线程
		for (int j = 0; j < 100; j++) {
			ts.add(new Thread(new Runnable() {
				public void run() {
					// 执行100次计算,预期结果应该是10000
					for (int i = 0; i < 100; i++) {
						cas.count();
						cas.safeCount();
					}
				}
			}));
		}
		//开始执行
		for (Thread t : ts) {
			t.start();
		}
		// 等待所有线程执行完成
		for (Thread t : ts) {
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("非线程安全计数结果:"+cas.i);
		System.out.println("线程安全计数结果:"+cas.ai.get());
	}

	/** 使用CAS实现线程安全计数器 */
	private void safeCount() {
		for (;;) {
			int i = ai.get();
			// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值
			boolean suc = ai.compareAndSet(i, ++i);
			if (suc) {
				break;
			}
		}
	}

	/** 非线程安全计数器 */
	private void count() {
		i++;
	}
}
//结果:
非线程安全计数结果:9867
线程安全计数结果:10000

        其中非线程安全计数器所计算的结果每次都不相同且不正确,而线程安全计数器计算的结果每次都是正确的。可以看到代码中并不含有任何有关 synchronized 关键字的地方,所以 safeCount可以当做一个普通的方法来使用。

        示例中使用了 compareAndSet方法,那么我们就从此方法开始深入了解 AtomicInteger的用法。

 

        compareAndSet方法

        如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

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

        compareAndSet 调用的是 unsafe.compareAndSwapInt方法。

        Unsafe类是用于执行低级别、不安全操作的方法集合。尽管这个类和所有的方法都是公开的(public),但是这个类的使用仍然受限,你无法在自己的 java程序中直接使用该类,因为只有授信的代码才能获得该类的实例。该类是用来执行较低级别的操作的,比如获取某个属性在内存中的位置,不过一般人很少会有这样的需求。个人不建议直接使用 Unsafe,它远比原生的 Java开发所需要的测试要多。基于这个原因建议还是使用经过测试的库。如果你只是想自己用 Unsafe,建议你最好在一个独立的类库中进行全面的测试。这限制了Unsafe在你的应用程序中的使用方式,但会给你一个更安全的Unsafe。

        其中绝大部分方法都调用的是 compareAndSet方法,比如 getAndIncrement:

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

        其他相关方法都类似,这里就不细说了。

        CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题:ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作。

        ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。 从Java1.5开始JDK的 atomic包里提供了一个类AtomicStampedReference 来解决ABA问题。这个类的 compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) 

        如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。其中参数代表:expectedReference - 该引用的预期值;newReference - 该引用的新值;

expectedStamp - 该标志的预期值;newStamp - 该标志的新值。

        循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。 

        只能保证一个共享变量的原子操作:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

        原子类不是 java.lang.Integer 和相关类的通用替换方法。它们不 定义诸如 hashCode 和 compareTo 之类的方法。(因为原子变量是可变的,所以对于哈希表键来说,它们不是好的选择。)另外,仅为那些通常在预期应用程序中使用的类型提供类。例如,没有表示 byte 的原子类。这种情况不常见,如果要这样做,可以使用 AtomicInteger 来保持 byte 值,并进行适当的强制转换。也可以使用 Float.floatToIntBits 和 Float.intBitstoFloat 转换来保持 float 值,使用 Double.doubleToLongBits 和 Double.longBitsToDouble 转换来保持 double 值。  

        类 AtomicBoolean、AtomicInteger、AtomicLong 和 AtomicReference 的实例各自提供对相应类型单个变量的访问和更新。每个类也为该类型提供适当的实用工具方法。例如,类 AtomicLong 和 AtomicInteger 提供了原子增量方法。一个应用程序将按以下方式生成序列号: 

class Sequencer {
  private final AtomicLong sequenceNumber
    = new AtomicLong(0);
  public long next() {
    return sequenceNumber.getAndIncrement();
  }
}

 

        原子访问和更新的内存效果一般遵循以下可变规则:

  •  get 具有读取 volatile 变量的内存效果。 
  •  set 具有写入(分配)volatile 变量的内存效果。 
  •  除了允许使用后续(但不是以前的)内存操作,其自身不施加带有普通的非 volatile 写入的重新排序约束,lazySet 具有写入(分配)volatile 变量的内存效果。在其他使用上下文中,当为 null 时(为了垃圾回收),lazySet 可以应用不会再次访问的引用。 
  •  weakCompareAndSet 以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。 
  •  compareAndSet 和所有其他的读取和更新操作(如 getAndIncrement)都有读取和写入 volatile 变量的内存效果。 

        除了包含表示单个值的类之外,atomic包还包含 Updater 类,Updater 类可用于获取任意选定类的任意选定 volatile 字段上的 compareAndSet 操作。AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater 和 AtomicLongFieldUpdater 是基于反射的实用工具,可以提供对关联字段类型的访问。它们主要用于原子数据结构中,该结构中同一节点(例如,树节点的链接)的几个 volatile 字段都独立受原子更新控制。这些类在如何以及何时使用原子更新方面具有更大的灵活性,但相应的弊端是基于映射的设置较为拙笨、使用不太方便,而且在保证方面也较差。 

        AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray 类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。 

        原子类也支持 weakCompareAndSet 方法,该方法具有受限制的适用性。在某些平台上,弱版本在正常情况下可能比 compareAndSet 更有效,但不同的是 weakCompareAndSet 方法的任何给定调用可能意外 返回 false(即没有明确的原因)。返回 false 仅意味着可以在需要时重新尝试操作,具体取决于重复执行调用的保证,当该变量保持 expectedValue 并且没有其他线程也在尝试设置该变量时,最终将获得成功。(例如,这样的虚假失败可能是由于内存争用的结果,该争用与期望值和当前值是否相等无关)。 此外,weakCompareAndSet 不提供通常需要同步控制的排序保证。但是,在这样的更新与程序的其他 happen-before 排序不相关时,该方法可用于更新计数器和统计数据。当一个线程看到对 weakCompareAndSet 导致的原子变量的更新时,它不一定能看到在 weakCompareAndSet 之前发生的对任何其他 变量的更新。例如,在更新性能统计数据时,这也许可以接受,但其他情况几乎不可以。 

        AtomicMarkableReference 类将单个布尔值与引用关联起来。例如,可以在数据结构内部使用此位,这意味着引用的对象在逻辑上已被删除。AtomicStampedReference 类将整数值与引用关联起来。例如,这可用于表示与更新系列对应的版本号。 

        设计原子类主要用作各种构造块,用于实现非阻塞数据结构和相关的基础结构类。compareAndSet 方法不是锁的常规替换方法。仅当对象的重要更新限定于单个 变量时才应用它。 

分享到:
评论

相关推荐

    锁与原子操作CAS以及无锁队列的底层实现相关资源

    锁与原子操作CAS以及无锁队列的底层实现相关资源

    笔记-3、原子操作CAS1

    循环(死循环,自旋)里不断的进行CAS操作CAS的问题A---》B----》A,版本号: A1B2-A3CAS操作长期不成功,cpu不断的循环,Jdk中相关原子

    Java原子操作CAS原理解析

    主要介绍了Java原子操作CAS原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    并发编程——原子操作CAS.pdf

    关于java中线程的一些基础知识详解文档和知识点,内容详细,通俗易懂,非常适合当接触线程知识的同学,以及复习线程理论知识人员

    HotspotOverview.pdf

    HotSpot虚拟机的概况文档 *每一个java对象都是一个潜在的monitor(监视器) ...*使用原子操作CAS引导尝试使mark word 指向lock record *如果CAS成功,线程获得锁 *如果CAS失败,竞争:锁膨胀(制造heavy-weight 重锁)

    3、并发编程之CAS&amp;Atomic原子操作详解.pdf

    3、并发编程之CAS&Atomic原子操作详解

    并发编程笔记20190526.docx

    5、原子操作CAS (compare atomic swap) 32 三、显式锁和AQS 34 1、AQS定义两种资源共享方式: 34 2、深入源码 37 3、了解Condition的实现 42 4、 锁的可重入 44 第三章 并发容器ConcurrentHashMap 46 一、JDK1.7中...

    理解原子操作,CAS加锁是线程安全的.docx

    偏向锁、轻量级锁和重量级锁不同的地方在于不是通过信号量机制(强制阻塞)而是通过自旋CAS实现互斥访问的,避免了强制阻塞时用户态与核心态之间切换带来的开销(系统调用),这里的开销主要是保存用户态的上下文...

    面试官:谈谈你对并发编程下原子性操作的认识

    文章目录引出问题(代码示例)问题原理说明问题解决方法1:使用锁机制方法2:原子类AtomicInteger原子类CAS机制实现线程安全概述源码分析CAS与Synchronized:乐观锁,悲观锁。 概述:所谓的原子性是指在一次操作或者...

    CAS原理分析

    这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置...

    原子指令于Lock-Free数据结构教学笔记

    无论其他处理器执行什么指令,原子操作都会成功或完全失败。 原子指令可以用来做同步处理。由于原子指令可用于更改共享数据而无需获取和释放锁,因此可以实现更高的并行性。但是,由于它们是低层的,并且只能对数据...

    CAS简介以及CAS的缺点和处理方法

    CAS是什么 CAS是指比较并交换(compy and set),底层原理为native修饰,直接操作地址 生活中的例子如修改成绩单,老师将...只能保证一个共享变量的原子操作,对多个共享变量操作时,循环CAS就无法保证操作的原子性,

    python实现redis三种cas事务操作

    cas全称是compare and set,是一种典型的事务操作。 简单的说,事务就是为了存取数据库中同一数据时不破坏操作的隔离性和原子性,从而保证数据的一致性。 一般数据库,比如MySql是如何保证数据一致性的呢,主要是...

    使用Java的Memory Model实现一个简单的计数器.txt

    `AtomicInteger`是一个支持原子操作的整数类,它内部使用了CAS(Compare And Swap)算法来实现线程安全的操作。在这个计数器中,我们使用`AtomicInteger`的`incrementAndGet()`方法来实现自增操作,并使用`get()`...

    Atomics:原子操作API

    原子操作API 下一个例子:

    CAS你以为你真的懂?

    CAS(Compare and swap)直译过来就是比较和替换,也有人叫compare and exchange,是一种通过硬件实现并发安全的常用技术,底层通过利用CPU的CAS指令对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。...

    CAS

    由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的...

    Java Unsafe类的使用.docx

    CAS算法是指先读取要修改的变量值,对它进行计算,然后执行检查并更新这个步骤(更新前判断那个值是否是之前那个读到的值),检查并更新是个原子操作,它由硬件来保证可靠性,CAS算法的最后一步的方法就是Unsafe类的...

    varhandle2:JVM 原子操作的安全而有效的实现

    JVM 原子操作的安全而有效的实现。 这个 API 允许使用不安全的操作(a la sun.misc.Unsafe)是一种安全的方式,但与使用 sun.misc.Unsafe 一样有效。 主要思想是基本上让 JIT 生成完全(或大部分)相同的代码,...

    java并发编程之cas详解

    主要介绍了java并发编程之cas详解,涉及cas使用场景和cas用作原子操作等内容,具有一定参考价值,需要的朋友可以了解下。

Global site tag (gtag.js) - Google Analytics