• java.util.concurrent.atomic 包详解


    Atomic包的作用:

    方便程序员在多线程环境下,无锁的进行原子操作

    Atomic包核心:

    Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作

    关于CAS

    compare and swap,比较和替换技术,将预期值与当前变量的值比较(compare),如果相等则使用新值替换(swap)当前变量,否则不作操作;

    现代CPU已广泛支持CAS指令,如果不支持,那么JVM将使用自旋锁,与互斥锁一样,两者都需先获取锁才能访问共享资源,但互斥锁会导致线程进入睡眠,而自旋锁会一直循环等待直到获取锁;

    另外,有一点需要注意的是CAS操作中的ABA问题,即将预期值与当前变量的值比较的时候,即使相等也不能保证变量没有被修改过,因为变量可能由A变成B再变回A,解决该问题,可以给变量增加一个版本号,每次修改变量时版本号自增,比较的时候,同时比较变量的值和版本号即可

    Atomic包主要提供四种原子更新方式

    • 原子方式更新基本类型
    • 原子方式更新数组
    • 原子方式更新引用
    • 原子方式更新字段

    原子方式更新基本类型

    以下三个类是以原子方式更新基本类型

    • AtomicBoolean:原子更新布尔类型。
    • AtomicInteger:原子更新整型。
    • AtomicLong:原子更新长整型。

    以AtomicInteger为例:

    package cn.com.example.concurrent.atomic;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * Created by Jack on 2017/1/7.
     */
    public class AtomicIntegerTest extends Thread {
    
        private AtomicInteger atomicInteger;
    
        public AtomicIntegerTest(AtomicInteger atomicInteger) {
            this.atomicInteger = atomicInteger;
        }
    
        @Override
        public void run() {
            int i = atomicInteger.incrementAndGet();
            System.out.println("generated  out number:" + i);
        }
    
        public static void main(String[] args) {
            AtomicInteger counter = new AtomicInteger();
            for (int i = 0; i < 10; i++) {//10个线程
                new AtomicIntegerTest(counter).start();
            }
        }
    }

    输出:

    generated  out number:1
    generated  out number:2
    generated  out number:3
    generated  out number:4
    generated  out number:5
    generated  out number:6
    generated  out number:7
    generated  out number:8
    generated  out number:9
    generated  out number:10
    

    注意:Atomic包提供了三种基本类型的原子更新,剩余的Java的基本类型还有char,float和double等,其更新方式可以参考AtomicBoolean的思路来现,AtomicBoolean是把boolean转成整型再调用compareAndSwapInt进行CAS来实现的,类似的short和byte也可以转成整形,float和double可以利用Float.floatToIntBits,Double.doubleToLongBits转成整形和长整形进行相应处理

    原子方式更新数组

    以下三个类是以原子方式更新数组

    • AtomicIntegerArray:原子更新整型数组里的元素。
    • AtomicLongArray:原子更新长整型数组里的元素。
    • AtomicReferenceArray:原子更新引用类型数组里的元素

    以AtomicIntegerArray为例,其方法与AtomicInteger很像,多了个数组下标索引

    package cn.com.example.concurrent.atomic;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.atomic.AtomicIntegerArray;
    
    /**
     * Created by Jack on 2017/1/7.
     */
    public class AtomicIntegerArrayTest {
        private static int threadCount = 1000;
        private static CountDownLatch countDown = new CountDownLatch(threadCount);
        static int[] values = new int[10];
        static AtomicIntegerArray ai = new AtomicIntegerArray(values);
    
        private static class Counter implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    for (int j = 0; j < 10; j++) {//所有元素+1
                        ai.getAndIncrement(j);
                    }
                }
                countDown.countDown();
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread[] threads = new Thread[threadCount];
            for (int i = 0; i < threadCount; i++) {
                threads[i] = new Thread(new Counter());
            }
            for (int i = 0; i < threadCount; i++) {
                threads[i].start();
            }
            countDown.await();
            for (int i = 0; i < 10; i++) {
                System.out.println(ai.get(i) + " ");
            }
            System.out.println();
            for (int i = 0; i < 10; i++) {
                System.out.println(values[i] + " ");
            }
        }
    }
    

    输出:

    100000 
    100000 
    100000 
    100000 
    100000 
    100000 
    100000 
    100000 
    100000 
    100000 
    
    0 
    0 
    0 
    0 
    0 
    0 
    0 
    0 
    0 
    0 
    

    需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。

    原子方式更新引用

    以下三个类是以原子方式更新引用,与其它不同的是,更新引用可以更新多个变量,而不是一个变量

    • AtomicReference:原子更新引用类型。
    • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
    • AtomicMarkableReference:原子更新带有标记位的引用类型。

    以AtomicReference为例

    package cn.com.example.concurrent.atomic;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * Created by Jack on 2017/1/7.
     */
    public class AtomicReferenceTest {
        public static void main(String[] args) {
    
            // 创建两个Person对象,它们的id分别是101和102。
            Person p1 = new Person(101);
            Person p2 = new Person(102);
            // 新建AtomicReference对象,初始化它的值为p1对象
            AtomicReference ar = new AtomicReference(p1);
            // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
            ar.compareAndSet(p1, p2);
    
            Person p3 = (Person) ar.get();
            System.out.println("p3 is " + p3);
            System.out.println("p3.equals(p1)=" + p3.equals(p1));
        }
    }
    
    class Person {
        volatile long id;
    
        public Person(long id) {
            this.id = id;
        }
    
        public String toString() {
            return "id:" + id;
        }
    }
    

    输出:

    p3 is id:102
    p3.equals(p1)=false
    

    新建AtomicReference对象ar时,将它初始化为p1。
    紧接着,通过CAS函数对它进行设置。如果ar的值为p1的话,则将其设置为p2。
    最后,获取ar对应的对象,并打印结果。p3.equals(p1)的结果为false,这是因为Person并没有覆盖equals()方法,而是采用继承自Object.java的equals()方法;而Object.java中的equals()实际上是调用"=="去比较两个对象,即比较两个对象的地址是否相等。

    原子方式更新字段

    以下三个类是以原子方式更新字段

    • AtomicIntegerFieldUpdater:原子更新整型字段的更新器。
    • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
    • AtomicStampedReference:原子更新带有版本号的引用类型,用于解决使用CAS进行原子更新时,可能出现的ABA问题。

    以AtomicIntegerFieldUpdater为例

    package cn.com.example.concurrent.atomic;
    
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    
    /**
     * Created by Jack on 2017/1/7.
     */
    public class AtomicIntegerFieldUpdaterTest {
    
        // 创建原子更新器,并设置需要更新的对象类和对象的属性
        private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
    
        public static void main(String[] args) throws InterruptedException {
            // 设置柯南的年龄是10岁
            User conan = new User("conan", 10);
            // 柯南长了一岁,但是仍然会输出旧的年龄
            System.out.println(a.getAndIncrement(conan));
            // 输出柯南现在的年龄
            System.out.println(a.get(conan));
        }
    
        public static class User {
            private String name;
            public volatile int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public int getOld() {
                return old;
            }
        }
    }
    

    输出:

    10
    11
    

    注意: old 需要声明为 volatile

  • 相关阅读:
    vSphere 6.5支持512e,NVMe SSD呢?
    分布式队列神器 Celery
    www.coursera.org
    各种编码UNICODE、UTF-8、ANSI、ASCII、GB2312、GBK详解
    SSD S.M.A.R.T
    2018世界人工智能大会在上海开幕
    ER TO SQL语句
    E-R图样例
    关于微软的.NET版本系列
    ArcGIS 10.6 安装破解教程
  • 原文地址:https://www.cnblogs.com/Zombie-Xian/p/6259401.html
Copyright © 2020-2023  润新知