zoukankan      html  css  js  c++  java
  • 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类

    这篇说说java.util.concurrent.atomic包里的类,总共12个。网上有非常多文章解析这几个类。这里挑些重点说说。



    这12个类能够分为三组:

    1. 普通类型的原子变量

    2. 数组类型的原子变量

    3. 域更新器


    普通类型的原子变量的6个,

    1. 当中AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference分别相应boolean, int,  long, object完毕主要的原子操作

    2. AtomicMarkableReference, AtomicStampedReference是AtomicReference的功能增强版本号,前者能够把引用跟一个boolean绑定,后者能够把引用和一个int型的版本号号绑定来完毕时间戳的功能。


    AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference这几个类的结构都相似,有几个特点

    1. 底层都是採用sun.misc.Unsafe类来完毕实际的CAS操作

    2. 使用sun.misc.Unsafe直接操作内存对象来完毕类似反射机制的对象属性存取能力

    3. volatile类型的value值保存状态

    4. 原子的get(), set()方法

    5. 主要的compareAndSet方法完毕CAS操作

    6. weakCompareAndSet,弱化版本号的CAS操作,这是API设计是预留地差异化接口,可是眼下没有实现,眼下和compareAndSet是一样的功能

    7. getAndSet方法是利用CAS操作无锁地完毕读取并设置的功能

    8. lazySet方法优化设置,lazySet的使用看这篇 聊聊高并发(十八)理解AtomicXXX.lazySet方法


    原子变量在并发编程中是主要的工具,能够用来实现非堵塞的数据结构和构建相关的基础构件。

    有几种主要的使用方法:

    1. 安全的计数器

    2. compareAndSet方法能够实现“滤网”的功能。找到第一个成功操作的线程,从而做一些操作,能够看看自旋锁相关的实现

    3. compareAndSet方法能够实现“推断操作是否成功”的功能。这里会有ABA的问题,能够採用AtomicStampedReference来避免ABA问题

    4. getAndSet方法能够实现全然的“设置并返回之前值”的功能

    5. AtomicBoolean作为一种二元状态能够用来作为“开关”,实现找到一个打开开关的线程。比方 if(b.compareAndSet(false, true)){// dosomething}

    来看几个典型的操作

    public class AtomicBoolean implements java.io.Serializable {
        private static final long serialVersionUID = 4654671469794556979L;
        // setup to use Unsafe.compareAndSwapInt for updates
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        // 使用Unsafe直接操作内存的方式设置属性的值
        static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicBoolean.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
        // 使用volatile变量来保存状态
        private volatile int value;
        // 使用Unsafe的compareAndSwapXXX方法完毕底层的CAS操作
        public final boolean compareAndSet(boolean expect, boolean update) {
            int e = expect ?

    1 : 0;         int u = update ? 1 : 0;         return unsafe.compareAndSwapInt(this, valueOffset, e, u);     } // 无锁地实现“设置并返回之前值”的功能,无锁的特点就是轮询加CAS操作 public final boolean getAndSet(boolean newValue) {         for (;;) {             boolean current = get();             if (compareAndSet(current, newValue))                 return current;         }     } // 优化volatie变量的写,再不须要保证可见性的场景下使用lazySet来优化,降低内存屏障 public final void lazySet(boolean newValue) {         int v = newValue ? 1 : 0;         unsafe.putOrderedInt(this, valueOffset, v);     }


    AtomicMarkableReference和AtomicStampedReference都是对AtomicReference的增强,内部使用了不可变对象来保存一个二元状态<Reference, XXX>,当原子设置时。就创建新的对象。并把volaitle引用指向最新的不可变对象。很多其它内容请查看聊聊高并发(十二)分析java.util.concurrent.atomic.AtomicStampedReference源代码来看怎样解决CAS的ABA问题

    AtomicMarkableReference能够用来标记对象。经常使用来构建数据结构中表示节点。能够用boolean表示节点是否被删除

    public class AtomicMarkableReference<V> {
    
        private static class Pair<T> {
            final T reference;
            final boolean mark;
            private Pair(T reference, boolean mark) {
                this.reference = reference;
                this.mark = mark;
            }
            static <T> Pair<T> of(T reference, boolean mark) {
                return new Pair<T>(reference, mark);
            }
        }
    
        private volatile Pair<V> pair;
    
        public AtomicMarkableReference(V initialRef, boolean initialMark) {
            pair = Pair.of(initialRef, initialMark);
        }
    
        public boolean compareAndSet(V       expectedReference,
                                     V       newReference,
                                     boolean expectedMark,
                                     boolean newMark) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedMark == current.mark &&
                ((newReference == current.reference &&
                  newMark == current.mark) ||
                 casPair(current, Pair.of(newReference, newMark)));
        } 


    数组类型的原子变量有3个: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray, 它们是普通原子变量的数组版本号。能够完毕对数组中元素的原子操作。

    主要的方法和普通原子类型类似,这里就不反复说了,提一下利用Unsafe读取数组元素的方法

    1. 利用Unsafe.arrayBaseOffset得到数组的第一个元素的偏移量,由于有对象头的存在。所以offset不是从0開始

    2. 利用Unsafe.arrayIndexScale得到数组中元素的长度

    3. 利用移位操作取代乘法提高效率。所以先计算shift,比方8字节长度的元素。须要左移3位,相当与2的3次幂,4字节长度的元素左移2位,相当于2的2次幂

    4. 计算数组中元素的实际位置 = index << shift + base。说白了,就是  index * 元素长度  + base

    public class AtomicIntegerArray implements java.io.Serializable {
    
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        // 获得数组第一个元素的偏移量offset,由于有对象头的存在,所以offset不是从0開始的 
        private static final int base = unsafe.arrayBaseOffset(int[].class);
        // 移位操作的基数,用移位操作取代乘法
        private static final int shift;
        private final int[] array;
    
        static {
            // 获取数组元素的长度,对于int[]数组。 scale = 4
            int scale = unsafe.arrayIndexScale(int[].class);
            // 假设长度不是为2的幂,就报错
            if ((scale & (scale - 1)) != 0)
                throw new Error("data type scale not a power of two");
            // 31 - Integer.numberOfLeadingZeros(scale) 相当于求floor(log2(x)),这里为2。假设是Long,就是3
            // 事实上就是用移位操作取代乘法,比方4字节长度,就要左移2位,8字节长度,就要左移3位。左移1位 = 乘 2
            shift = 31 - Integer.numberOfLeadingZeros(scale);
        }
    
        private long checkedByteOffset(int i) {
            if (i < 0 || i >= array.length)
                throw new IndexOutOfBoundsException("index " + i);
    
            return byteOffset(i);
        }
        // 用移位操作取代乘法,实际上求的是数组的第i个元素的偏移量,方便定位到数组元素的内存地址
        private static long byteOffset(int i) {
            return ((long) i << shift) + base;
        }
    

    最后看看域更新器,有三个: AtomicIntegerFieldUpdate,  AtomicLongFieldUpdate,  AtomicReferenceFieldUpdate

    域更新器是一种优化手段,它提供了现有volatile域的一种基于反射的视图。从而能对现有volatile域进行CAS操作。我们知道volatile字段仅仅保证可见性,可是不保证原子性,

    假设要想对volatile字段进行CAS操作。就要用到域更新器。它的优点是能够让volatile字段具备原子变量的能力,而不须要实际创建这么多的原子变量,毕竟volatile比起原子变量来说还是轻量级的。

    域更新器没有提供对外的构造函数,它须要利用工厂方法的方式来创建。提供一个newUpdater(xxx)方法来返回一个新建的域更新器对象。

    1. tclass指的是字段所在类的Class类型

    2. vclass指的是字段的Class类型,须要注意的是字段必须是volatile标示的,不然会抛出异常

    3. filedName字段名

    4. 调用者的类型,能够用Reflection.getCallerClass()获得

     public static <U, W> AtomicReferenceFieldUpdater<U,W> newUpdater(Class<U> tclass, Class<W> vclass, String fieldName) {
            return new AtomicReferenceFieldUpdaterImpl<U,W>(tclass,
                                                            vclass,
                                                            fieldName,
                                                            Reflection.getCallerClass());
        }
    
      AtomicReferenceFieldUpdaterImpl(Class<T> tclass,
                                            Class<V> vclass,
                                            String fieldName,
                                            Class<?> caller) {
                Field field = null;
                Class fieldClass = null;
                int modifiers = 0;
                try {
                    field = tclass.getDeclaredField(fieldName);
                    modifiers = field.getModifiers();
                    sun.reflect.misc.ReflectUtil.ensureMemberAccess(
                        caller, tclass, null, modifiers);
                    sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
                    fieldClass = field.getType();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
    
                if (vclass != fieldClass)
                    throw new ClassCastException();
    
                if (!Modifier.isVolatile(modifiers))
                    throw new IllegalArgumentException("Must be volatile type");
    
                this.cclass = (Modifier.isProtected(modifiers) &&
                               caller != tclass) ? caller : null;
                this.tclass = tclass;
                if (vclass == Object.class)
                    this.vclass = null;
                else
                    this.vclass = vclass;
                offset = unsafe.objectFieldOffset(field);
            }
    


    AtomicIntegerFieldUpdate这些更新器的接口和原子变量一致。都提供了compareAndSet操作,getAndSet操作等。这里不反复说。举个样例看看怎样使用域更新器

    1. Node类有一个volatile类型的next字段,它没有使用AtomicReference。使用了更轻量级的volatile

    2. 假设想对volatile类型的next做CAS操作。就要创建域更新器AtomicReferenceFieldUpdater

    private class Node<E>{
    		private final E item;
    		private volatile Node<E> next;
    		
    		public Node(E item){
    			this.item = item;
    		}
    	}
    	
    	private static AtomicReferenceFieldUpdater<Node, Node> nextUpdate = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");


  • 相关阅读:
    Linux 入门
    Java OOP——第五章 异常
    Java OOP——第七章 多线程
    Java OOP——第八章 File IO
    Java OOP——JAVA关键字与保留字说明及使用
    JQuery制作网页——第九章 表单验证
    JQuery制作网页——第八章 使用jQuery操作DOM
    JQuery制作网页—— 第七章 jQuery中的事件与动画
    JQuery制作网页—— 第六章 jQuery选择器
    腾讯的一版 类似于fullpage效果
  • 原文地址:https://www.cnblogs.com/yfceshi/p/6760068.html
Copyright © 2011-2022 走看看