• Java多线程:CAS与java.util.concurrent.atomic


    锁的几种概念

    悲观锁

    总是假设最坏的情况,每次获取数据都认为别人会修改,所以拿数据时会上锁,一直到释放锁不允许其他线程修改数据。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

  • 相关阅读:
    个人WPF快速入门笔记 基础样式篇02
    个人WPF快速入门笔记 基础布局篇01
    nginx常用笔记备忘
    【leetcode】1685. Sum of Absolute Differences in a Sorted Array
    【leetcode】1696. Jump Game VI
    【leetcode】1694. Reformat Phone Number
    【leetcode】1684. Count the Number of Consistent Strings
    【leetcode】1695. Maximum Erasure Value
    【leetcode】1671. Minimum Number of Removals to Make Mountain Array
    【leetcode】1689. Partitioning Into Minimum Number Of DeciBinary Numbers
  • 原文地址:https://www.cnblogs.com/cielosun/p/10579586.html
Copyright © 2020-2023  润新知