zoukankan      html  css  js  c++  java
  • Java 原子操作类

    CAS(Compare And Swap)指令级别保证这是一个原子操作,三个运算符: 一个内存地址V,一个期望的值A,一个新值B;基本思路:如果地址V上的值和期望的值A相等,就给地址V赋给新值B,如果不是,不做任何操作;循环(死循环,自旋)里不断的进行CAS操作;

     

    如果需要获取原子操作类的值并更新,期望值与内存地址中的值不等,则循环(死循环,自旋)里不断的进行CAS操作;(Atomic类的实现)

    AtomicIntegergetAndIncrement()方法

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
    

      

    UnsafegetAndAddInt方法

     public final int getAndAddInt(Object var1, long var2, int var4) {
         int var5;
         //如果var1和var2值不等,会重新进入循环
         do {
             var5 = this.getIntVolatile(var1, var2);
         } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
     ​
         return var5;
     }
    

      

     

    在Atomic包里一共提供了13个类,属于4种类型的更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新属性(字段);Atomic包里基本都是使用Unsafe实现的包装类;

    • 原子更新基本类型类

      AtomicBoolean:原子更新布尔类型

      AtomicInteger:原子更新整型

      AtomicLong:原子更新长整型

       

      AtomicInteger常用方法:

       int addAndGet(int delta): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
       
       boolean compareAndSet(int expect, int update): 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值
           
       int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值
           
       void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还可以读取到旧的值
           
       int getAndSet(int newValue): 以原子方式设置为newValue的值,并返回旧值

     

    Atomic 包提供了三种基本类型的原子更新;Atomic包里的类基本都是使用Unsafe实现的

     /**Unsafe.java
      * 每次在执行CAS操作时,线程会根据valueOffset去内存中获取当前值去跟expect的值做对比如果一致则修改并返回true,如果不一致说明有别的线程也在修改此对象的值,则返回false
      * value 表示 需要操作的对象
      * valueOffset 表示 对象(value)的地址的偏移量(通过Unsafe.objectFieldOffset(Field valueField)获取)
      * expect 表示更新时value的期待值
      * update 表示将要更新的值
      * @return 如果更新成功返回true,否则为false
      */
     public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);
     ​
     public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);
     ​
     public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
    

      

     

    ABA问题

    《Java并发编程实战》的15.4.4节如下:

    ABA问题是一种异常现象:如果在算法中的节点可以被循环使用,那么在使用“比较并交换”指令时就可能出现这个问题(如果在没有垃圾回收机制的环境 中)。在CAS操作中将判断“V的值是否仍然为A?”,并且如果是的话就继续执行更新操作。在大多数情况下,这种判断是足够的。然而,有时候还需要知道 “自从上次看到V的值为A以来,这个值是否发生了变化?”在某些算法中,如果V值首先由A变成B,再由B变成A,那么仍然被认为发生了变化,并需要重新执 行算法中的某些步骤。

     

    比如说线程1从内存位置valueOffset中取出值A,线程2也从内存位置valueOffset中取出值A,并执行一些操作将值变为B,然后再将B变成A,这时候线程1进行CAS操作发现内存中仍然是A,线程1执行成功,但不能代表整个过程中,值没有被修改过;

     public class UseAtomicIntegerTest {
         static AtomicInteger integer = new AtomicInteger(1);
     ​
         public static void main(String[] args) throws InterruptedException {
             int oldValue = integer.get();
             System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue);
     ​
             Thread thread1 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         System.out.println(Thread.currentThread().getName() + " --- " + integer.get());
                         Thread.sleep(2L);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             });
     ​
             Thread thread2 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     // 确保别的线程1先执行
                     Thread.yield();
     ​
                     // integer + 1
                     System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet());
     ​
                     // integer - 1
                     System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet());
                 }
             });
     ​
             thread1.start();
             thread2.start();
     ​
             thread1.join();
             thread2.join();
             boolean result = integer.compareAndSet(oldValue, oldValue + 10);
             System.out.println(Thread.currentThread().getName() + " --- " + integer.get() + " result:" + result);
         }
     }
    

      

    打印如下:

     main oldValue:1
     Thread-0 --- 1
     Thread-1 --- integer:2
     Thread-1 --- integer:1
     main --- 11 result:true
    

      

    执行最后的值是相同的,但不能代表整个过程中,值没有被修改过;

     

    ABA解决方案:

    给变量加一个版本号即可,在比较的时候不仅要比较当前变量的值,还需要比较当前变量的版本号; 在Java5中,已经提供了AtomicStampedReference来解决问题,检查当前引用是否等于预期引用,其次检查当前标志是否等于预期标志,如果都相等就会以原子的方式将引用和标志都设置为新值;

     

    • 原子更新数组

      AtomicIntegerArray: 原子更新整型数组里地元素

      AtomicLongArray: 原子更新长整型数组里的元素

      AtomicReferenceArray: 原子更新引用类型数组里的元素

      AtomicIntergerArray: 主要是提供原子的方式更新数组里的整型

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

        

      AtomicIntergerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的元素进行修改时,不会影响传入的数组;

     

    • 原子更新引用类型

      AtomicReference: 原子更新引用类型

      AtomicReferenceFieldUpdater: 原子更新引用类型里的字段

      AtomicMarkableReference: 原子更新带有标记位的引用类型;可以原子的更新一个布尔类型的标记位和引用类型,构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

     

    • 原子更新字段类

      AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器;

      AtomicLongFieldUpdater: 原子更新长整型字段的更新器;

      AtomicStampedReference: 原子更新带有版本号的引用类型;该类将整型数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题;

     

    使用AtomicStampedReference解决ABA

     public class UseAtomicIntegerTest {
         static AtomicInteger integer = new AtomicInteger(1);
     ​
         public static void main(String[] args) throws InterruptedException {
             int oldValue = integer.get();
             System.out.println(Thread.currentThread().getName() + " oldValue:" + oldValue);
     ​
             Thread thread1 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         System.out.println(Thread.currentThread().getName() + " --- " + integer.get());
                         Thread.sleep(2L);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             });
     ​
             Thread thread2 = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     // 确保别的线程1先执行
                     Thread.yield();
     ​
                     // integer + 1
                     System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.incrementAndGet());
     ​
                     // integer - 1
                     System.out.println(Thread.currentThread().getName() + " --- integer:" + integer.decrementAndGet());
                 }
             });
     ​
             thread1.start();
             thread2.start();
     ​
             thread1.join();
             thread2.join();
             System.out.println(Thread.currentThread().getName() + " --- " + integer.get());
         }
     }
    

      

    打印结果如下:

     main oldValue:1 marked:false
     Thread-0 --- value:1 marked:false
     Thread-1 --- value:2 result:true marked:true
     Thread-1 --- value:2 marked:true
     Thread-1 --- value:1 result:true marked:true
     main --- 1 result:false
    

      

     

  • 相关阅读:
    用于json的 .ashx 小细节
    (转)写让别人能读懂的代码
    Mvc 中ViewBag Model 查找不到解决
    Windows 2008 R2 配置 DNS 实现二级域名
    Windows Server 2008 DNS服务器安装与配置
    【iOS】swift init构造器
    【iOS】OC-UTC日期字符串格式化
    android使用sharesdk的小感
    【iOS】Swift GCD-下
    【iOS】Swift GCD-上
  • 原文地址:https://www.cnblogs.com/coder-zyc/p/12587482.html
Copyright © 2011-2022 走看看