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