1. 程式人生 > >JAVA多執行緒之——併發包JUC——Atomic

JAVA多執行緒之——併發包JUC——Atomic

前面學習了基礎的多執行緒知識。今天開始學習JAVA的併發包java.util.concurrent。java併發包包括 java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks包。今天開始學習atomic包下的內容’
概念
java從jdk1.5開始引入了併發包。其中java.util.concurrent.atomic包,方便在無鎖的情況下,進行原子操作。原子變數的底層使用的是CPU的原子指令,但是不同的CPU有不同的架構,指令也就不同。所以也有可能需要提供某種內部的鎖機制。所以不能保證執行緒絕對不會堵塞。
Atomic分類


atomic包下面總共有12個類。根據起作用可以分為四類
1.原子更新基本型別

  1. AtomicBoolean 原子更新布林型別
  2. AtomicInteger 原子更新整型
  3. AtomincLong 原子更新長整型

原子更新整型的常用方法:

  • int get() 獲取當前值
  • void set(int newValue) 設定為給定值。
  • int getAndAdd(int delta) 以原子方式將給定值與當前值相加。
  • int decrementAndGet() 以原子方式將當前值減 1。
  • int incrementAndGet() 以原子方式將當前值加 1。
  • void lazySet(int newValue) 最後設定為給定值。

方法可以通過API查詢。我們主要是瞭解,為什麼原子型別的操作就是執行緒安全的?底層又是如何進行原子操作的呢?
對JVM有過一定了解的應該都知道CAS操作。CAS操作有3個運算元,記憶體值M,預期值E,新值U,如果M==E,則將記憶體值修改為B,否則啥都不做。就是當且僅當記憶體值與當前值一致,才進行值的更新。否則不更新。這樣就保證了atomic包下面的操作的原子行。那它是如何實現的呢?看一看原始碼:

public class AtomicInteger extends Number implements
java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } }

這是AtomicInteger中的一段原始碼。我們可以看到其中有一個Unsafe類,一個volatile 修飾的value. 這個value就是先保證了AtomicInteger操作的可見性。然後Unsafe保證對這個value值的操作都是CAS操作。這樣就保證了其原子性。Unsafe原始碼的一部分:

/**
* 比較obj的offset處記憶體位置中的值和期望的值,如果相同則更新。此更新是不可中斷的。
* 
* @param obj 需要更新的物件
* @param offset obj中整型field的偏移量
* @param expect 希望field中存在的值
* @param update 如果期望值expect與field的當前值相同,設定filed的值為這個新值
* @return 如果field的值被更改返回true
*/
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

Unsafe中就是通過這樣的方式來實現CAS操作。同理AtomicBoolean 和AtomicLong也是這樣的方式。但是java中對long型別和double型別的操作是不具有原子性的。這個在今後學習JVM中會詳細學習。還有CAS概念。

2.原子更新陣列型別
理解了原子更新基本型別,對於其它型別就比較好理解了。
通過原子的方式更新數組裡的某個元素,Atomic包提供了以下三個類:

  1. AtomicIntegerArray 原子更新整型陣列
  2. AtomicLongArray 原子更新長整型陣列
  3. AtomicReferenceArray 原子更新引用陣列

AtomicIntegerArray常用的方法:

  • int get(int i) 獲取位置 i 的當前值。
  • void set(int i, int newValue) 將位置 i 的元素設定為給定值。
  • int length() 返回該陣列的長度。
  • int getAndAdd(int i, int delta) 以原子方式將給定值與索引 i 的元素相加。
  • int getAndDecrement(int i) 以原子方式將索引 i 的元素減 1。
  • int getAndIncrement(int i) 以原子方式將索引 i 的元素加 1。
  • AtomicIntegerArray(int length) 建立給定長度的新 AtomicIntegerArray。
  • AtomicIntegerArray(int[] array) 建立與給定陣列具有相同長度的新 AtomicIntegerArray,並從給定陣列複製其所有素。

先來看一下AtomicIntegerArray的部分原始碼:

 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;
public AtomicIntegerArray(int length) {
        array = new int[length];
    }
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }
 public final int getAndAdd(int i, int delta) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, current + delta))
                return current;
        }
    }    

通過觀察原始碼,其實AtomicIntegerArray類,跟AtomicInteger的原理相似。其自身擁有一個 int[]的陣列array。如果我們呼叫構造器構造的時候沒有傳入陣列,則直接初始化自身陣列,否則,對我們的陣列進行克隆。也就是說,它操作的都是自身的陣列,對我們傳入的陣列是不會有任何操作。而對陣列中的資料的操作也是通過Unsafe類來實現其原子性的操作。

3.原子更新引用型別
原子更新基本型別,每次只能更新一個變數,而當需要更新多個變數,比如一個物件的多個屬性時候,我們就必須用到原子更新引用型別類——AtomicReference. 其常用的方法有:

  1. V get() 獲取當前值。
  2. void set(V newValue) 設定為給定值。
  3. V getAndSet(V newValue) 以原子方式設定為給定值,並返回舊值。
  4. boolean compareAndSet(V expect, V update) 如果當前值 == 預期值,則以原子方式將該值設定為給定的更新值。
  5. void lazySet(V newValue) 最終設定為給定值。

先看一個示例:

public class AtomicTest3 {

    public static void main(String[] args) {
        final AtomicReference<Student>  reference  = new AtomicReference<Student>();
        final Student s1 = new Student("張三", 18);
        Student s2 = new Student("李四", 20);
        reference.set(s1);
        reference.compareAndSet(s1, s2);
        System.out.println(reference.get());



    }

    static class Student{
        private String name;
        private int  age;
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + "]";
        }



    }

}

執行結果:

Student [name=李四, age=20]

看來是沒什麼問題。再看一個示例:


public class AtomicTest {

    public static void main(String[] args) {
        AtomicReference<Student>  reference  = new AtomicReference<Student>();
        final Student s1 = new Student("張三", 18);

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    s1.setName("李四");
                    Thread.currentThread().sleep(2000);
                    s1.setAge(20);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }


            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(s1.toString());
            }
        });

        t1.start();
        t2.start();

    }

    static class Student{
        private String name;
        private int  age;
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + "]";
        }



    }

}

執行結果:

Student [name=李四, age=18]

這個結果通過前面學習我們可以知道原因。因為沒有同步操作。OK,那可能就會想,我們今天學習了AtomicReferece。剛好是更新一個物件。那我們可以用上。示例:

public class AtomicTest2 {

    public static void main(String[] args) {
        final AtomicReference<Student>  reference  = new AtomicReference<Student>();
        final Student s1 = new Student("張三", 18);
        reference.set(s1);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reference.get().setName("李四");
                    Thread.currentThread().sleep(2000);
                    reference.get().setAge(20);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }


            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(reference.get().toString());
            }
        });

        t1.start();
        t2.start();

    }

    static class Student{
        private String name;
        private int  age;
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + "]";
        }



    }

}

結果:

Student [name=李四, age=18]

我們發現,結果並沒有對真個物件進行我們預期的“原子操作”。為什麼? 看原始碼:

 private static final long serialVersionUID = -1848883965231344442L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicReference.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile V value;

我們看到 有一個變數 V value。這意味著,我們所有的原子操作都是在操作這整個物件。如果對這個物件單獨的屬性進行操作,那必然會導致這個物件的原子性不一致。同理,如果在AtomicInteger類,加入我們連續的多次呼叫自增1的方法,那也是不能保證原子性的。
4.原子更新欄位
如果我們只需要某個類裡的某個欄位,那麼就需要使用原子更新欄位類,Atomic包提供了以下三個類:

  1. AtomicIntegerFieldUpdater 原子更新整型的欄位的更新器
  2. AtomicLongFieldUpdater 原子更新長整型的欄位更新器
  3. AtomicStampedReference:原子更新帶有版本號的引用型別。該類將整數值與引用關聯起來,可用於原子的更資料和資料的版本號,可以解決使用CAS進行原子更新時,可能出現的ABA問題。

原子更新欄位類都是抽象類,每次使用都時候必須使用靜態方法newUpdater建立一個更新器。原子更新類的欄位的必須使用public volatile修飾符。AtomicIntegerFieldUpdater的示例:

    public static void main(String[] args) {
        Student s1 = new Student("張三", 18);
        AtomicReferenceFieldUpdater  updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, Integer.class, "age");
        System.out.println(updater.get(s1));
        updater.set(s1, 20);
        System.out.println(updater.get(s1));
        System.out.println(s1);


    }

    static class Student{
        private String name;
      volatile Integer  age;
        public Student(String name, int age) {
            super();
            this.name = name;
            this.age = age;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + "]";
        }
    }

}

執行結果:

18
20
Student [name=張三, age=20]

這裡需要注意的是
1.欄位必須用volatile修飾 2.欄位不能修飾為private。原始碼:

  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 (vclass.isPrimitive())
                throw new IllegalArgumentException("Must be reference type");

            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);
        }

        void targetCheck(T obj) {
            if (!tclass.isInstance(obj))
                throw new ClassCastException();
            if (cclass != null)
                ensureProtectedAccess(obj);
        }

        void updateCheck(T obj, V update) {
            if (!tclass.isInstance(obj) ||
                (update != null && vclass != null && !vclass.isInstance(update)))
                throw new ClassCastException();
            if (cclass != null)
                ensureProtectedAccess(obj);
        }

        public boolean compareAndSet(T obj, V expect, V update) {
            if (obj == null || obj.getClass() != tclass || cclass != null ||
                (update != null && vclass != null &&
                 vclass != update.getClass()))
                updateCheck(obj, update);
            return unsafe.compareAndSwapObject(obj, offset, expect, update);
        }

        public boolean weakCompareAndSet(T obj, V expect, V update) {
            // same implementation as strong form for now
            if (obj == null || obj.getClass() != tclass || cclass != null ||
                (update != null && vclass != null &&
                 vclass != update.getClass()))
                updateCheck(obj, update);
            return unsafe.compareAndSwapObject(obj, offset, expect, update);
        }

其中呼叫了 sun.reflect.misc.ReflectUtil.ensureMemberAccess(
caller, tclass, null, modifiers);這個方法。一直跟蹤這個方法:

 public static boolean More ...verifyMemberAccess(Class currentClass,
105                                             // Declaring class of field
106                                             // or method
107                                             Class  memberClass,
108                                             // May be NULL in case of statics
109                                             Object target,
110                                             int    modifiers)
111    {
112        // Verify that currentClass can access a field, method, or
113        // constructor of memberClass, where that member's access bits are
114        // "modifiers".
115
116        boolean gotIsSameClassPackage = false;
117        boolean isSameClassPackage = false;
118
119        if (currentClass == memberClass) {
120            // Always succeeds
121            return true;
122        }
123
124        if (!Modifier.isPublic(getClassAccessFlags(memberClass))) {
125            isSameClassPackage = isSameClassPackage(currentClass, memberClass);
126            gotIsSameClassPackage = true;
127            if (!isSameClassPackage) {
128                return false;
129            }
130        }
131
132        // At this point we know that currentClass can access memberClass.
133
134        if (Modifier.isPublic(modifiers)) {
135            return true;
136        }
137
138        boolean successSoFar = false;
139
140        if (Modifier.isProtected(modifiers)) {
141            // See if currentClass is a subclass of memberClass
142            if (isSubclassOf(currentClass, memberClass)) {
143                successSoFar = true;
144            }
145        }
146
147        if (!successSoFar && !Modifier.isPrivate(modifiers)) {
148            if (!gotIsSameClassPackage) {
149                isSameClassPackage = isSameClassPackage(currentClass,
150                                                        memberClass);
151                gotIsSameClassPackage = true;
152            }
153
154            if (isSameClassPackage) {
155                successSoFar = true;
156            }
157        }
158
159        if (!successSoFar) {
160            return false;
161        }
162
163        if (Modifier.isProtected(modifiers)) {
164            // Additional test for protected members: JLS 6.6.2
165            Class targetClass = (target == null ? memberClass : target.getClass());
166            if (targetClass != currentClass) {
167                if (!gotIsSameClassPackage) {
168                    isSameClassPackage = isSameClassPackage(currentClass, memberClass);
169                    gotIsSameClassPackage = true;
170                }
171                if (!isSameClassPackage) {
172                    if (!isSubclassOf(targetClass, currentClass)) {
173                        return false;
174                    }
175                }
176            }
177        }
178
179        return true;
180    }

在這個方法中如果是私有的就會返回false。於是就會丟擲異常。

 if (!verifyMemberAccess(currentClass, memberClass, target, modifiers)) {
95             throw new IllegalAccessException("Class " + currentClass.getName() +
96                                              " can not access a member of class " +
97                                              memberClass.getName() +
98                                              " with modifiers \"" +
99                                              Modifier.toString(modifiers) +
100                                             "\"");
101        }

至於為什麼不能宣告私有,為何要丟擲這個異常,目前對於sun.reflect.misc.ReflectUtil還沒有深入研究。所以暫時做個筆記。