锁的几种概念

悲观锁

总是假设最坏的情况,每次获取数据都认为别人会修改,所以拿数据时会上锁,一直到释放锁不允许其他线程修改数据。Java中如synchronized和reentrantLock就是这种实现。

乐观锁

总是假设最好的情况,每次去拿数据时都认为别人不会修改,所以不上锁,等更新数据时判断一下在此期间是否有其他人更新过这个数据,可以使用CAS算法实现。乐观锁适用于多读少写的应用类型,可以大幅度提高吞吐量。乐观锁的实现机制主要包括版本号机制(给数据加一个版本号,数据被修改版本号会加一,更新时读取版本号,若读取到的版本号和之前一致才更新,否则驳回)和CAS算法(下详)。

自旋锁与互斥锁

多线程互斥访问时会进入锁机制。互斥设计时会面临一个情况:没有获得锁的进程如何处理。通常有两种办法:一种是没有获得锁就阻塞自己,请求OS调度另一个线程上的处理器,即互斥锁;另一种时没有获得锁的调用者就一直循环,直到锁的持有者释放锁,即自旋锁。

自旋锁是一种较低级的保护数据的方式,存在两个问题:递归死锁,即递归调用时试图获得相同的自旋锁。过多占用CPU资源,自旋锁不成功时会持续尝试,通常一个自旋锁会有参数限制尝试次数,超出后放弃time slice,等待一下一轮机会。

但在锁持有者保持锁的时间较短的前提下,选择自旋而非睡眠则大大提高了效率,因而在这种情况下自旋锁效率远高于互斥锁。

CAS

CAS算法

CAS即compare and swap,是一种系统原语,是不可分割的操作系统指令。CAS是一种乐观锁实现。

CAS有三个操作数,内存值V,旧的预期内存值A,要修改的新值B,当且仅当A=V,才将内存值V修改为B,否则不会执行任何操作。一般情况下CAS是一个自旋操作,即不断重试。

CAS开销

CAS是CPU指令集的操作,只有一步的原子操作,所以非常快,CAS的开销主要在于cache miss问题。如图

这是一个8核CPU系统,共有4个管芯,每个管芯中有两个CPU,每个CPU有cache,管芯内有一个互联模块,让管芯的两个核可以互相通信。图中的系统连接模块可以让四个管芯通信。例如,此时CPU0进行一个CAS操作,而该变量所在的缓存线在CPU7的高速缓存中,则流程如下:

  • CPU检查本地缓存,没有找到缓存线。
  • 请求被转发到CPU0和CPU1的互联模块,检查CPU1的本地高速缓存,没有找到缓存线。
  • 请求被转发到系统互联模块,检查其他三个管芯,得知缓存线在CPU6和CPU7所在的管芯中。
  • 请求被转发到CPU6和CPU7的互联模块,检查这两个CPU的高速缓存,在CPU7中找到缓存线。
  • CPU7将缓存线发给互联模块,并刷新自己的缓存线。
  • CPU6和CPU7的互联模块将缓存线发送给系统互联模块。
  • 系统互联模块将缓存线发送给CPU0和CPU1的互联模块。
  • CPU0对高速缓存中的变量执行CAS操作。

Java中的CAS

JDK5增加java.util.concurrent包,其中很多类使用了CAS操作。这些CAS操作基于Unsafe类中的native方法实现:

//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作,
//设置成功返回true,否则返回false。
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

由于CAS作用的对象在主存里而不是在线程的高速缓存里,CAS操作在Java中需要配合volatile使用。

Java中的CAS主要包含以下几个问题:

  • ABA问题,即变量V初次读时是A值,被赋值时也是A值,但期间变量被赋值成B值,CAS会误认为他从没被修改过。AtomicStampedReference和AtomicMarckableReference类提供了监测ABA问题的能力,其中的compareAndSet方法首先检查当前引用是否等于预期引用,并且当前标志等于预期标志,全部相等则以原子方式将该引用和该标志的值设置为给定的更新值。
  • 循环开销,自旋CAS长时间不成功会给CPU带来非常大的执行开销。若JVM能支持pause命令,效率有一定提升。因为pause命令一方面可以延迟流水线执行命令,使CPU不会消耗过多的执行资源,另一方面可以避免退出循环时由内存顺序冲突引起的CPU流水线被冲突,从而提高CPU的执行效率。
  • 只能保证一个共享变量的原子操作,当操作涉及跨多个共享变量时CAS无效。可用AtomicReference封装多个字段来保证引用对象之间的原子性。

CAS与synchronized

  • 资源竞争少时,synchronized同步锁进行线程阻塞,唤醒切换,用户内核态间切换,浪费额外CPU资源,CAS基于硬件实现,不进入内核,不切换线程,操作自旋几率小,CAS有更高的性能。
  • 资源竞争严重时,CAS自旋概率较大,从而浪费更多的CPU资源,效率低于synchronized。

java.util.concurrent.atomic

jdk1.5提供了一组原子类,由CAS对其实现。其中的类可以分为四组:

  • AtomicBoolean,AtomicInteger,AtomicLong 基本类型,bool, int, long
  • AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 数组类型,包括整形数组,长整型数组,引用类型数组
  • AtomicReference,AtomicStampedReference,AtomicMarkableReference AtomicReference为普通的引用类型原子类,AtomicStampedReference在构造方法中加入了stamp(类似时间戳)作为标识,采用自增int作为stamp,在stamp不重复的前提下可以解决ABA问题,AtomicStampedReference可以获知引用被更改了几次。当我们不需要知道引用被更改几次仅需要知道引用是否被更改过,则可以使用AtomicMarkableReference,这个类用boolean变量表示变量是否被更改过。
  • AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater 三种原子更新对应类型(int, long, 引用)的更新器,用于对普通类进行原子更新。

其作用为对单一数据的操作实现原子化,无需阻塞代码,但访问两个或两个以上的atomic变量或对单个atomic变量进行2次或2次以上的操作被认为是需要同步的以便这些操作是一个原子操作。

AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference, AtomicStampedReference, AtomicMarkableReference

前四种类型用来处理Boolean,Integer, Long, 对象,后两个类支持的方法和AtomicReference基本一致,仅作用不同。以上类型均包含以下方法:

  • 构造函数,默认值分别为false, 0, 0, null。带参数则参数为初始化数据。
  • set(newValue)和get()方法,常规的设置/读取值,非原子操作。其中set是volatile操作。
  • lazySet(newValue),设置值,原子操作,调用后的一小段时间其他线程可能会读取到旧值。
  • getAndSet(newValue)相当于先使用get再set,但是是一个原子操作。
  • compareAndSet(expectedData, newData),接受两个参数,若atomic内数据和期望数据一致,则将新数据赋值给atomic数据返回true,否则不设置并返回false。
  • weakCompareAndSet(expectedData, newData),与前者类似,但更高效,不同的是可能会返回虚假的失败,不提供排序的保证,最好用于无关于happens-before的程序。

对于AtomicInteger, AtomicLong,还实现了getAndIncrement(), increateAndGet(), getAndDecreate(), decreateAndGet(), addAndGet(delta), getAndAdd(delta)方法,以实现加减法的原子操作。

AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray

这三种类型用于处理数组,常用方法如下:

  • set(index, newValue)和get(index)方法,常规的设置/读取索引对应值,非原子操作。其中set是volatile操作。
  • lazySet(index, newValue),设置索引对应值,原子操作,调用后的一小段时间其他线程可能会读取到旧值。
  • getAndSet(index, newValue)相当于先使用get再set,但是是一个原子操作。
  • compareAndSet(index, expectedData, newData),接受三个参数,索引,期望数据,新数据。若atomic内数据和期望数据一致,则将新数据赋值给atomic数据返回true,否则不设置并返回false。

对于AtomicIntegerArray, AtomicLongArray,还实现了getAndIncrement(index), increateAndGet(index), getAndDecreate(index), decreateAndGet(index), addAndGet(index, delta), getAndAdd(index, delta)方法,以实现加减法的原子操作。

AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater

这三种类型用于处理普通对象中某个字段的CAS更新,由于是CAS更新,要求该字段必须是volatile的,常用方法如下:

  • AtomicReferenceFiledUpdater.newUpdater(holderClassName, fieldClassName, fieldNameString):对于普通的引用更新器,创建一个更新器需要以下三个参数:指定的类的类型,类中要更新的字段的类型,该字段的名字。该方法使用反射寻找需要更新的字段,且由于字段是成员变量,需要特别注意要能够访问到字段。对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater,由于已经确定了字段类型,只需要提供指定的类的类型和字段名即可。
  • lazySet(object, newValue),设置值,原子操作,调用后的一小段时间其他线程可能会读取到旧值。
  • getAndSet(object, newValue)相当于先使用get再set,但是是一个原子操作。
  • compareAndSet(object, expectedData, newData),接受两个参数,若atomic内数据和期望数据一致,则将新数据赋值给atomic数据返回true,否则不设置并返回false。

示例操作如下:

User类(由普通类改造成的CAS更新类)

public class User {
private static AtomicReferenceFieldUpdater<User, String> nameUpdater = AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
private static AtomicIntegerFieldUpdater<User> ageUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
private volatile String name;
private volatile int age; public User(String name, Integer age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public Integer getAge() {
return age;
} public void setAge(Integer age) {
this.age = age;
} public void lazySetName(String name) {
nameUpdater.lazySet(this, name);
} public String getSetName(String name) {
return nameUpdater.getAndSet(this, name);
} public void compareAndSetName(String exceptedName, String newName) {
nameUpdater.compareAndSet(this, exceptedName, newName);
} public void lazySetAge(int age) {
ageUpdater.lazySet(this, age);
} public Integer getSetAge(int age) {
return ageUpdater.getAndSet(this, age);
} public void compareAndSetAge(int exceptedAge, int newAge) {
ageUpdater.compareAndSet(this, exceptedAge, newAge);
}
}

主程序

public class AtomicTest {
public void run() {
User user = new User("Atomic", 10);
user.compareAndSetName("Atomic", "Ass");
user.compareAndSetAge(10, 11);
System.out.println(user.getName() + user.getAge());
} public static void main(String[] args) throws Exception {
new AtomicTest().run();
}
}

输出结果:

Ass11

参考文献

深入理解CAS算法原理

面试必备之乐观锁与悲观锁

Java之多线程 Atomic(原子的)

对 volatile、compareAndSet、weakCompareAndSet 的一些思考

并发编程面试必备:JUC 中的 Atomic 原子类总结

AtomicReference,AtomicStampedReference与AtomicMarkableReference的区别

JAVA中的CAS

Java多线程:CAS与java.util.concurrent.atomic的更多相关文章

  1. 谈论高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference看看如何解决源代码CAS的ABA问题

    于谈论高并发(十一)几个自旋锁的实现(五岁以下儿童)中使用了java.util.concurrent.atomic.AtomicStampedReference原子变量指向工作队列的队尾,为何使用At ...

  2. Java:多线程,java.util.concurrent.atomic包之AtomicInteger/AtomicLong用法

    1. 背景 java.util.concurrent.atomic这个包是非常实用,解决了我们以前自己写一个同步方法来实现类似于自增长字段的问题. 在Java语言中,增量操作符(++)不是原子的,也就 ...

  3. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  4. java.util.concurrent.atomic 包详解

    Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compar ...

  5. 并发之java.util.concurrent.atomic原子操作类包

    15.JDK1.8的Java.util.concurrent.atomic包小结 14.Java中Atomic包的原理和分析 13.java.util.concurrent.atomic原子操作类包 ...

  6. java.util.concurrent.atomic 类包详解

    java.util.concurrent包分成了三个部分,分别是java.util.concurrent.java.util.concurrent.atomic和java.util.concurren ...

  7. java多线程-cas及atomic

    大纲: cas atomic 一.cas cas:compareAndSwap,一种乐观锁. cas思想:cas需要三个值,v是内存值,e是期望值,n是要修改的值.当内存中的值v等于预期值e(说明内存 ...

  8. Java并发—原子类,java.util.concurrent.atomic包(转载)

    原子类 Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量 ...

  9. Java 原子类 java.util.concurrent.atomic

    Java 原子类 java.util.concurrent.atomic 1.i++为什么是非线程安全的 i++其实是分为3个步骤:获取i的值, 把i+1, 把i+1的结果赋给i 如果多线程执行i++ ...

随机推荐

  1. C# 多种方式连接Oracle。

    废话不多说直接正题: 首先我们先在Oracle数据库下建了一个用户叫做lisi,密码为lisi,在这个用户下建立一张表叫做“USERS”,在这个表下新增三个数据. 方式一:利用OleDb连接Oracl ...

  2. zabbix系列(五)zabbix3.0.4 探索主机Discovery自动发现主机详细图文教程

    Zabbix 自动发现(Discovery)功能使用 随着监控主机不断增多,有的时候需要添加一批机器,特别是刚用zabbix的运维人员需要将公司的所有服务器添加到zabbix,如果使用传统办法去单个添 ...

  3. python之鸭子类型

    python不支持多态,也不用支持多态,python是一种多态语言,崇尚鸭子类型. 在程序设计中,鸭子类型是动态类型的一种风格,不是由继承特定的类或实现特定的接口,而是当前的方法和属性的集合决定,鸭子 ...

  4. 多CPU,多核,多进程,多线程

    当面临这些问题的时候,有两个关键词无法绕开,那就是并行和并发. 首先,要先了解几个概念: 1.进程是程序的一次执行. 2.进程是资源分配的基本单位(调度单位). 3.一个进程可以包括多个线程. 4.在 ...

  5. PHP 将数组的值赋值给一组变量

    经常需要将一个字符串分割成一组值,然后赋值给不同的变量. 逐行赋值非常繁琐,于是查了一下 PHP 中是否有类似 python 中 a, b = (a, b) 的操作. 果然有 $info = arra ...

  6. python-随机操作(random)

    random模块作用是返回随机数,只要跟随机元素相关的,都可以使用它. Python标准库中的random函数,可以生成随机浮点数.整数.字符串,甚至帮助你随机选择列表序列中的一个元素,打乱一组数据等 ...

  7. Extjs MVC模式开发,循序渐进(一)

    本文讲述extjs mvc的Helloworld,tabPanel,event,页面布局layout等内容. 本页包含:MVC模式案例(一)~MVC模式案例(六),从搭建extjs mvc到点击按钮生 ...

  8. ubuntu axel

    ubuntu下rar解压工具安装方法: 压缩功能 安装 sudo apt-get install rar 卸载 sudo apt-get remove rar 解压功能 安装 sudo apt-get ...

  9. (转)HIBERNATE与 MYBATIS的对比

    第一方面:开发速度的对比 就开发速度而言,Hibernate的真正掌握要比Mybatis来得难些.Mybatis框架相对简单很容易上手,但也相对简陋些.个人觉得要用好Mybatis还是首先要先理解好H ...

  10. moodle 笔记

    默认版块的设置: //这些变量为新课程定义了DEFAULT块变量如果设置了这个变量,它将覆盖所有其他变量,并且是唯一使用的变量s $CFG->defaultblocks_override = ' ...