zoukankan      html  css  js  c++  java
  • JDK源码学习之 java.util.concurrent.automic包

    一、概述  

      Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

      atomic包里的类基本都是使用Unsafe实现的包装类,在Atomic包里一共有12个类,四种原子更新方式,这里我们将对着四种方式进一步分析。

    二、解析
      1. 原子更新基本类型类
      用于通过原子的方式更新基本类型,Atomic包提供了以下三个类:
        AtomicBoolean:原子更新布尔类型。
        AtomicInteger:原子更新整型。
        AtomicLong:原子更新长整型。

      这里我们着重介绍下AtomicInteger这个类的常用方法,如下:
      (1)int addAndGet(int delta) :

        以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
      (2)boolean compareAndSet(int expect, int update) :

        如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
      (3)int getAndIncrement():

        以原子方式将当前值加1,注意:这里返回的是自增前的值。
      (4)void lazySet(int newValue)
      (5)int getAndSet(int newValue):

        以原子方式设置为newValue的值,并返回旧值。

      参考实例如下:

          AtomicInteger a = new AtomicInteger(1);
          a.addAndGet(2); 
          System.out.println(a); //3
          a.getAndIncrement();
          System.out.println(a.get()); //4
    

     这里我们延伸一下,介绍下CAS的实现原理:

      CAS(Compare and Swap,比较并操作),  CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

      CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

            AtomicInteger中的 incrementAndGet 方法源码如下:

      public final int getAndIncrement() {
            for (;;) {
                int current = get();
                int next = current + 1;
                if (compareAndSet(current, next))
                    return current;
            }
      }
    

       通过源码可以看到,方法执行步骤为:先获取到当前的 value 属性值,然后将 value 加 1,赋值给一个局部的 next 变量,然而,这两步都是非线程安全的,但是内部有一个死循环,不断去做compareAndSet操作,直到成功为止,也就是修改的根本在compareAndSet方法里面,compareAndSet()方法的代码如下:

        public final boolean compareAndSet(int expect, int update) {
    	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }
    

      compareAndSwapInt 方法是一个native 方法,如下:

      public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
      
      所以compareAndSet传入的值为执行方法时获取到的value属性值,next为加 1 后的值,compareAndSet调用native的compareAndSwapInt方法来完成,
    compareAndSwapInt 基于的是CPU的CAS指令来实现的。所以基于CAS的操作可认为是无阻塞的,一个线程的失败或挂起不会引起其它线程也失败或挂起。并且由于CAS操作是CPU原语,所以性能比较好。
      但是需要注意的是:在CAS操作中,会出现ABA的问题,即如果V值先由A变为B,再由B变为A,那么会仍然认为是发生了变化,并需要重新执行算法中的步骤。
      对应的解决方案是:不只更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后又变为A,版本号也是不同的。
      例如:AtomicMarkableReference和AtomicStampedReference支持在两个变量上执行原子的条件更新,
    AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。

     2. 原子更新数组类

      通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:
        AtomicIntegerArray:原子更新整型数组里的元素,主要用于原子的方式更新数组里的整型。
        AtomicLongArray:原子更新长整型数组里的元素。
        AtomicReferenceArray:原子更新引用类型数组里的元素。

      这里我们同样着重介绍下AtomicIntegerArray类的常用方法,如下:

      (1)int addAndGet(int i, int delta):

        以原子方式将输入值与数组中索引i的元素相加。
      (2)boolean compareAndSet(int i, int expect, int update):

        如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

      参考实例如下:

      int[] arr = new int[]{1,2,3};
      AtomicIntegerArray arr1= new AtomicIntegerArray(arr);
      arr1.getAndAdd(2,4);
      arr1.getAndSet(0,2);
      System.out.println(arr[2]); //3
      System.out.println(arr1.get(2)); //7

      注:AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组,原因是数组值通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份。

     3. 原子更新引用类型

      原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:
        AtomicReference:原子更新引用类型。
        AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
        AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

      参考实例如下:

      static class User{
            String id;
            String name;
            volatile int age;
    
            public User(String id, String name, int age) {
                this.id = id;
                this.name = name;
                this.age = age;
            }
    
            public String getId() {
                return id;
            }
    
            public String getName() {
                return name;
            }
    
            public int getAge() {
                return age;
            }
      }
      public static void main(String[] args) {
            User user1 = new User("1", "LiLei", 11);
            User user2 = new User("2", "HanMeimei", 12);
    
            AtomicReference<User> user = new AtomicReference<User>(user1);
            System.out.println(user1.getName());  //LiLei
            user.getAndSet(user2);
            System.out.println(user.get().getName()); //HanMeimei 
      }   

     4. 原子更新字段类

      如果我们只需要某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:
        AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
        AtomicLongFieldUpdater:原子更新长整型字段的更新器。
        AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
      原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。

      AtomicIntegerFieldUpdater的例子代码如下:

      AtomicIntegerFieldUpdater<User> userUpdate = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
      userUpdate.getAndIncrement(user2);
      System.out.println(user2.getAge()); //13
    

      

      参考博文:

      http://ifeve.com/java-atomic/
      http://blog.csdn.net/qfycc92/article/details/46489553

  • 相关阅读:
    自定义组件要加@click方法
    绑定样式
    647. Palindromic Substrings
    215. Kth Largest Element in an Array
    448. Find All Numbers Disappeared in an Array
    287. Find the Duplicate Number
    283. Move Zeroes
    234. Palindrome Linked List
    202. Happy Number
    217. Contains Duplicate
  • 原文地址:https://www.cnblogs.com/hunterCecil/p/8459501.html
Copyright © 2011-2022 走看看