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


    原子类

    Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中
    的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
    因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更
    新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。
    Atomic包里的类基本都是使用Unsafe实现的包装类

    java.util.concurrent.atomic中的类可以分成4组:

    • 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
    • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
    • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
    • 复合变量类:AtomicMarkableReference,AtomicStampedReference

    CAS

    CAS(Compare-And-Swap)算法保证数据操作的原子性。

    CAS 算法是硬件对于并发操作共享数据的支持。

    CAS 包含了三个操作数:
      内存值 V
      预估值 A
      更新值 B

    当且仅当 V == A 时,V 将被赋值为 B,否则循环着不断进行判断 V 与 A 是否相等。

    CAS的全称为Compare-And-Swap,是一条CPU的原子指令,其作用是让CPU比较后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。
    atomic包的原子包下使用这种方案。例如AtomicInteger的getAndIncrement自增+1的方法,经查看源码如下:

    public final int getAndIncrement() {
    for (;;) {
    int current = get();
    int next = current + 1;
    if (compareAndSet(current, next))
    return current;
    }
    }
    public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    源码中for循环体的第一步先取得AtomicInteger里存储的数值,第二步对AtomicInteger的当前数值进行加1操作,关键的第三步调用compareAndSet方法来进行原子更新操作,该方法先检查当前数值是否等于current,等于意味着AtomicInteger的值没有被其他线程修改过,则将AtomicInteger的当前数值更新成next的值,如果不等compareAndSet方法会返回false,程序会进入for循环重新进行compareAndSet操作

    Unsafe只提供了3种CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具体实现都是使用了native方法。

    /**
    * 如果当前数值是expected,则原子的将Java变量更新成x
    * @return 如果更新成功则返回true
    */
    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);

    native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

    JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。

    标量类

    • AtomicBoolean:原子更新布尔变量
    • AtomicInteger:原子更新整型变量
    • AtomicLong:原子更新长整型变量
    • AtomicReference:原子更新引用类型

    具体到每个类的源代码中,提供的方法基本相同,这里以AtomicInteger为例进行说明,AtomicInteger源码主要的方法如下,原理主要都要都是采用了CAS,这里不再累述。

    public class AtomicInteger extends Number implements java.io.Serializable {
       
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
     //返回当前的值
        public final int get() {
            return value;
        }
        //最终会设置成新值
        public final void set(int newValue) {
            value = newValue;
        }
        
        public final void lazySet(int newValue) {
            unsafe.putOrderedInt(this, valueOffset, newValue);
        }
          //原子更新为新值并返回旧值
        public final int getAndSet(int newValue) {
            for (;;) {
                int current = get();
                if (compareAndSet(current, newValue))
                    return current;
            }
        }
         //如果输入的值等于预期值,则以原子方式更新为新值
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    
     //原子自增
        public final int getAndIncrement() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return current;
            }
        }
    
    //原子方式将当前值与输入值相加并返回结果
        public final int getAndAdd(int delta) {
            for (;;) {
                int current = get();
                int next = current + delta;
                if (compareAndSet(current, next))
                    return current;
            }
        } 
    }

    数组类

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

    AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下。

    • int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
    • boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

    以上几个类提供的方法几乎一样,所以这里以AtomicIntegerArray为例进行讲解

    public class AtomicIntegerArray implements java.io.Serializable {
        private static final long serialVersionUID = 2862133569453604235L;
    
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final int base = unsafe.arrayBaseOffset(int[].class);
        private static final int shift;
        private final int[] array;
     
    
        private long checkedByteOffset(int i) {
            if (i < 0 || i >= array.length)
                throw new IndexOutOfBoundsException("index " + i);
    
            return byteOffset(i);
        }
    
        private static long byteOffset(int i) {
            return ((long) i << shift) + base;
        }
    
         
        public AtomicIntegerArray(int length) {
            array = new int[length];
        }
    
       
        public final int get(int i) {
            return getRaw(checkedByteOffset(i));
        }
    
        private int getRaw(long offset) {
            return unsafe.getIntVolatile(array, offset);
        }
    
        
        public final void set(int i, int newValue) {
            unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
        }
    
       
        public final void lazySet(int i, int newValue) {
            unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
        }
    
        
        public final int getAndSet(int i, int newValue) {
            long offset = checkedByteOffset(i);
            while (true) {
                int current = getRaw(offset);
                if (compareAndSetRaw(offset, current, newValue))
                    return current;
            }
        }
     
        public final boolean compareAndSet(int i, int expect, int update) {
            return compareAndSetRaw(checkedByteOffset(i), expect, update);
        }
    
        private boolean compareAndSetRaw(long offset, int expect, int update) {
            return unsafe.compareAndSwapInt(array, offset, expect, update);
        }
    
         
    
    }

    这里关键的实现也还是使用了CAS的策略,具体通过unsafe的native方法进行实现

    原子更新字段类

    如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供
    了以下3个类进行原子字段更新。

    • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
    • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
    • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起
      来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的
      ABA问题。
      要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的
      时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第
      二步,更新类的字段(属性)必须使用public volatile修饰符
    public class AtomicIntegerFieldUpdaterTest {
    // 创建原子更新器,并设置需要更新的对象类和对象的属性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
    newUpdater(User.class, "old");
    public static void main(String[] args) {
    // 设置柯南的年龄是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

    转载自:

    Java并发编程-原子类及并发工具类

  • 相关阅读:
    SQL SERVER NVARCHAR字段INSERT 中文乱码问题解决
    解析$(this).data('type');
    使用HttpWebRequest发送自定义POST请求
    C#使用WebClient下载文件到本地目录
    linux开发时,想用的链接暂存
    程序进行重定位的技术按重定位的时机分类
    文件分类
    快表、页表
    操作系统的发展与分类
    网络操作系统和分布式操作系统的区别
  • 原文地址:https://www.cnblogs.com/Jason-Xiang/p/8449512.html
Copyright © 2020-2023  润新知