博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java线程(十):CAS
阅读量:6710 次
发布时间:2019-06-25

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

前言

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

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

       以这两个方法为例,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变量

private volatile int value;
       首先声明了一个volatile变量value,在 我们知道volatile保证了变量的内存可见性,也就是全部工作线程中同一时刻都能够得到一致的值。
public final int get() {    return value;}

Compare And Set

// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private static final long valueOffset;// 注意是静态的static {  try {    valueOffset = unsafe.objectFieldOffset        (AtomicInteger.class.getDeclaredField("value"));// 反射出value属性,获取其在内存中的位置  } catch (Exception ex) { throw new Error(ex); }}public final boolean compareAndSet(int expect, int update) {  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}
       比較并设置,这里利用Unsafe类的JNI方法实现,使用CAS指令,能够保证读-改-写是一个原子操作。compareAndSwapInt有4个參数,this - 当前AtomicInteger对象,Offset - value属性在内存中的位置(须要强调的是不是value值在内存中的位置),expect - 预期值,update - 新值,依据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于运行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe。

循环设置

       如今在来看开篇提到的两个方法,我们拿incrementAndGet来分析一下事实上现过程。
public final int incrementAndGet() {    for (;;) {// 这样优于while(true)        int current = get();// 获取当前值        int next = current + 1;// 设置更新值        if (compareAndSet(current, next))            return next;    }}
       循环内,获取当前值并设置更新值,调用compareAndSet进行CAS操作,假设成功就返回更新至,否则重试到成功为止。这里可能存在一个隐患,那就是循环时间过长,总是在当前线程compareAndSet时,有还有一个线程设置了value(点子太被了),这个当然是属于小概率时间,眼下Java貌似还不能处理这样的情况。

缺点

       尽管使用CAS能够实现非堵塞式的原子性操作,可是会产生ABA问题,关于ABA问题,计划单拿出一章来整理。
       (完)
       本文来自: ,原文地址: ,转载请注明。
你可能感兴趣的文章
《从0到1学习Flink》—— Flink 写入数据到 Kafka
查看>>
Redis的安装和使用之二------phpredis与phpRedisAdmin
查看>>
内部邮件服务器转发
查看>>
指定mysql数据目录位置(便于查看和管理)
查看>>
Distcc(分布式编译)(方法1)
查看>>
IBM服务器安装ubuntu系统
查看>>
Icinga2监控web、mysql状态
查看>>
Mysql备份之mysqldump
查看>>
站在源码的角度全解Scroller工作机制(三)
查看>>
NTP时间服务器安装部署文档
查看>>
第8章 网关、隧道及中继
查看>>
CSS3的边框(四)
查看>>
openfire集群+nginx负载均衡
查看>>
性能测试规定——可靠性测试规定
查看>>
00031服务SAMBA
查看>>
html暑期实习笔记(二)
查看>>
针对手游业务亚马逊,首都在线,迅达云主机的IO Network 性能测试数据
查看>>
基于lamp+fastcgi+https搭建phpMyAdmin和wordpress
查看>>
win7下安装telnet
查看>>
CCNA-(3)-了解什么是网络
查看>>