1. 程式人生 > 實用技巧 >JUC併發程式設計原子操作類

JUC併發程式設計原子操作類

  

      大綱:

        一、原子操作類介紹

       二、原子性型別

  

      原子操作類介紹

      多執行緒資源的共享,需要為其增加同步鎖,保證資料的結果正確性,但是過多的同步操作可能會造成死鎖,導致程式進入停滯狀態,且這樣的問題很難排查。而且這樣對效能也有影響。所以在這種情況下就引入了原子性的控制,來解決這樣的問題。

      範例:沒有提供同步操作觀察問題

      此時加入有10個執行緒同時在執行,每一次執行讓num的變數的值加一,那麼最終的結果一定是10。但是此時返回的結果是9,原因是執行緒是隨機執行的,哪一個執行緒先佔用CPU的資源,哪一個執行緒就先執行,那麼這裡的情況就是多個執行緒同時去搶佔一個資源時,可能一個執行緒還沒有遞增演算法操作,而下一個執行緒就已經為其增加好了,這樣就會導致結果不正確。這個問題在最原始的解決方案就是加入同步鎖(synchronized)來解決,這個方案就是當有一個執行緒正在執行時,就不會讓其他執行緒執行,必須等到正在執行的執行緒執行完畢之後,才執行下一個執行緒。

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
class Count{
    private int num = 0;
    public void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num ++;
    }
    public int getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 { public static void main(String[] args) throws Exception{ Count count = new Count(); for(int x = 0 ; x < 10 ; x ++) { new Thread(() -> { count.addOne(); }).start(); } TimeUnit.SECONDS.sleep(2); System.out.println(
"最終計算結果:" + count.getNum()); // 結果 = 9 多執行幾次結果不一 } }

      範例:加入原始的同步鎖解決

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
class Count{
    private int num = 0;
    public synchronized void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num = this.num + 1;
        System.out.println(this.num);
    }
    public synchronized int getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 {
    public static void main(String[] args) throws Exception{
        Count count = new Count();
        for(int x = 0 ; x < 10 ; x ++) {
            System.out.println(x);
            new Thread(() -> {
                count.addOne();
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("最終計算結果:" + count.getNum());
    }
}

     

      範例:用原子類實現,此時避免了大量的使用synchronized(同步鎖)和喚醒處理機制。

package cn.txp.juc.atom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class Count{
    private AtomicInteger num = new AtomicInteger();
    public void addOne() {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (Exception e) {}
        this.num.addAndGet(1); // 加一
    }
    public AtomicInteger getNum() {
        return this.num;
    }
}
public class JUCAtomDemo1 {
    public static void main(String[] args) throws Exception{
        Count count = new Count();
        for(int x = 0 ; x < 10 ; x ++) {
            System.out.println(x);
            new Thread(() -> {
                count.addOne();
            }).start();
        }
        TimeUnit.SECONDS.sleep(2);
        System.out.println("最終計算結果:" + count.getNum());
    }
}

      原子性型別

      在JUC中,原子性型別分為了一下幾種:

        1、基本原子型別

        2、原子的陣列型別

        3、原子的引用型別

        4、原子的屬性修改器

        5、原子計算器

      

    1、基本原子型別

      主要描述的是一些常規的資料操作,有AtomicBoolean, AtomicInteger, AtomicLong這三種對應型別,這幾種對應的型別操作都非常的類似,上面的一個demo中就是使用AtomicInteger來進行操作的。    

      常用操作方法:

public AtomicLong() {} 設定儲存的型別為0
public AtomicLong(long num) {} 設定指定的初始化類容
public final long addAndGet(long delta) {} 增加資料同時返回計算後的結果
public final long decrementAndGet() {} 自減並返回結果
public final long incrementAndGet() {} 自增並返回結果
public final void set(long newValue) {} 設定新的資料內容
public final long get() {} 獲取原始的資料內容
public final boolean compareAndSet(long expectedValue, long newValue) {} CAS操作,進行資料的安全操作修改

     例:

package cn.txp.juc.atom;
import java.util.concurrent.atomic.AtomicLong;
public class JUCAtomDemo2 {
    public static void main(String[] args) throws Exception{
        AtomicLong al = new AtomicLong(10);
        System.out.println("增加並返回結果:" + al.addAndGet(10));
        al.set(99);
        System.out.println(al.get());
    }
}

      範例:compareAndSet()方法是採用CAS定義的,CAS是樂觀鎖的處理機制,CAS進行操作的時候並不是利用Java虛擬機器來實現的,而是直接利用底層的互動類來實現的,也就是說CAS是利用最底層的程式碼實現的。CAS的操作就是將當前的內容與預期的內容進行判斷,如果相同則進行內容的交換。

package cn.txp.juc.atom;
import java.util.concurrent.atomic.AtomicLong;
public class JUCAtomDemo3 {
    public static void main(String[] args) throws Exception{
        AtomicLong al = new AtomicLong(10);
        System.out.println(al.compareAndSet(20, 70));
        System.out.println(al.get());
    }
}

     2、原子陣列型別

      原子陣列型別有如下幾個類:AtomicIntegerArray<整型陣列>、AtomicLongArray<長整型陣列>、AtomicReferenceArray<引用陣列>。    

      AtomicReferenceArray為例:

方法 描述
public AtomicReferenceArray(int length) 定義要運算元組的空白長度
public AtomicReferenceArray(E[] array) 設定直接要操作的陣列
public final boolean compareAndSet(int i , E expectedValue, E newValue) 進行陣列的比較替換操作
public final E get(int i) 獲取指定索引的陣列
public final void set(int i , E newValue) 設定指定索引的陣列

      

      範例:

package cn.txp.juc.atom.array;
import java.util.concurrent.atomic.AtomicReferenceArray;
public class JucAtomicReferenceArrayDemo {
    public static void main(String[] args) {
        AtomicReferenceArray<String> array = new AtomicReferenceArray<String>(5);
        array.set(0, "www.baidu.com");
        array.set(1, "test");
        array.set(2, "array");
        System.out.println(array.compareAndSet(1, "test", "dd"));
        System.out.println(array.get(1));
    }
}

      3、原子的引用型別  

        引用原子操作類有幾個常用類:AtomicReference 引用型別原子類、AtomicMarkableReference 標記節點原子引用類、AtomicStampedReference 引用版本號原子類

方法 描述
public AtomicReference() 設定一個空引用的資料內容
public AtomicReference(V initValue) 設定要引用的資料內容
public final boolean compareAndSet(V expectedValue, V newValue) CAS比較替換操作
public final V get() 獲取設定的內容
public final V set(V newValue) 設定要操作的資料內容

    範例:AtomicReference

package cn.txp.juc.atom.reference;
impor java.util.concurrent.atomic.AtomicReference;
class Person{
    private String name;
    private Integer age;
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "name: " + this.name + "\t" + "age: " + this.age;
    }
}
public class JucRefrenceDemo {
    public static void main(String[] args) {
        Person per1 = new Person("A1", 10);
        Person per2 = new Person("C", 10);
        AtomicReference<Person> atomicRe = new AtomicReference<>(per1);
        System.out.println(atomicRe.compareAndSet(per1, per2));
        System.out.println(atomicRe.get());
    }
}

      範例:AtomicStampeReference,根據版本號實現資料的修改

方法 描述
public AtomicStampedReference(V initref, int initialStamp) 置一個初始化引用和初始化數值標記
public V getReference() 獲取當前引用
public int getStamp() 獲取當前的標記
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) CAS比較替換操作
public void set(V newReference, int newStamp) 設定新的內容並指定新的標記

      範例:AtomicStampedReference

package cn.txp.juc.atom.reference;
import java.util.concurrent.atomic.AtomicStampedReference;
class Member{
    private String name;
    private Integer age;
    public Member(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "name: " + this.name + "\t" + "age: " + this.age;
    }
}
public class JucRefrenceDemo2 {
    public static void main(String[] args) {
        Member mem1 = new Member("testMember", 10);
        Member mem2 = new Member("testMem", 20);
        AtomicStampedReference<Member> atomicMember = new AtomicStampedReference<Member>(mem1, 1);
        atomicMember.compareAndSet(mem2, mem2, 1, 3);
        System.out.println(atomicMember.getReference());
        System.out.println(atomicMember.getStamp());
    }
}

      範例:AtomicMarkableReference類,在使用AtomicStampedReference類的時候,需要每一次給一個版本作為標識,這樣在開發中過於麻煩,AtomicMarkableReference就屬於它的簡化操作。

方法 描述
public AtomicMarkableReference(V initRefe, boolean initMarkable) 初始化內容並設定一個標記
public boolean isMarked() 獲取當前的標記
public V getReference() 獲取當前引用資料
public void set(V newReference, boolean newMark) 設定引用資料並設定標記
public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) 比較替換內容

      範例:AtomicMarkableReference

package cn.txp.juc.atom.reference;
import java.util.concurrent.atomic.AtomicMarkableReference;
class News{
    private String title;
    private String content;
    public News(String title, String content) {
        this.title = title;
        this.content = content;
    }
    @Override
    public String toString() {
        return "title: " + this.title + "\t" + "content: " + this.content;
    }
}
public class JucRefrenceDemo3 {
    public static void main(String[] args) {
        News news = new News("title1", "內容");
        News newss = new News("titd1", "內容3");
        AtomicMarkableReference<News> mark = new AtomicMarkableReference<News>(news, true);
        mark.compareAndSet(news, newss, true, false);
        System.out.println(mark.isMarked());
        System.out.println(mark.getReference());
        System.out.println(mark.isMarked());
    }
}

      4、原子的屬性修改器

        原子的引用型別是將物件放到原子類中進行保護,從而實現同步的處理,但是這樣的操作無法同步類中的屬性,如果要同步屬性,可以通過原子的屬性修改器來完成。

        原子的屬性修改器有幾個常用類:AtomicIntegerFieldUpdater(針對於Integer型別進行保護)、AtomicLongFieldUpdater(針對於Long型別進行保護)、AtomicReferenceFieldUpdater(針對於引用進行保護

方法 描述
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) 獲取屬性修改器例項物件
public abstract boolean compareAndSet(T obj, int expect, int update) 比較替換操作。

        範例:AtomicIntegerFieldUpdater

package cn.txp.juc.atom.field;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Member{
    private String name;
    private volatile int age;
    public Member(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Member.class, "age");
        fieldUpdater.compareAndSet(this, this.age, age);
    }
    @Override
    public String toString() {
        return "name:" + this.name + "age" + this.age;
    }
}
public class IntegerFieldUpdater {
    public static void main(String[] args) {
        Member mem = new Member("名稱", 20);
        mem.setAge(15);
        System.out.println(mem);
    }
}

     

      5、原子計算器

       併發計算操作中,提供累加器(LongAccumulator、DoubleAccumulator)和加法器(LongAdder、DoubleAdder)的概念。

       範例:使用累加器進行計算

package cn.txp.juc.atom.adder;
import java.util.concurrent.atomic.DoubleAccumulator;
public class AdderDemo1 {
    public static void main(String[] args) {
        DoubleAccumulator doubleAccumulator = new DoubleAccumulator((x, y) -> x + y, 1.1);
        doubleAccumulator.accumulate(20);
        System.out.println(doubleAccumulator.get());
    }
}

package cn.txp.juc.atom.adder;

import java.util.concurrent.atomic.DoubleAdder;

public class AdderDemo2 {
    public static void main(String[] args) {
        DoubleAdder adder = new DoubleAdder();
        adder.add(10);
        adder.add(20);
        System.out.println(adder);
    }
}