java語言基礎--線程相關類
countdownlatch 在一定條件下阻塞線程,條件結束線程繼續執行,
semaphore 信號量,阻塞線程,可以控制同一時間執行線程的數量,
原子類能做到線程安全的原因,incrementAndGet,
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//將當前工作線程傳入的值與主內存的值比較,如果相同 就返回var5與var4之和 return var5; }
AtomicLong與LongAdder的比較
從上面的AtomicInt的實現可以看的出來,Atomic是通過死循環輪訓內做修改嘗試,當競爭不激烈的時候,修改成功的概率比較高,當競爭激烈的時候 修改失敗的概率很高,修改失敗後又繼續進行修改嘗試,因此高並發條件下 性能會受到一定的影響。
LongAdder比Atomic效率高的原因:https://blog.csdn.net/little_newBee/article/details/80352445
其缺點是當有並發更新的時候。其統計結果會有誤差,因此高並發計算的需求下使用LongAdder,低並發下使用AtomicLong,當需要準確的數據的時候,AtomicLong才是應該的選擇。
AtomicReference
//如果是expect 那麽更新為 update public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
使用AtomicIntegerFieldUpdater來 原子性的修改類的某個字段
public class ATomICExample1 { //指定為ATomICExample1的count字段 private static AtomicIntegerFieldUpdater<ATomICExample1> updater = AtomicIntegerFieldUpdater.newUpdater(ATomICExample1.class, "count"); public volatile int count = 100;//這裏必須使用volatile 來修飾 public static void main(String[] args) { ATomICExample1 example5 = new ATomICExample1(); if (updater.compareAndSet(example5, 100, 120)) { System.out.println(example5.getCount());//輸出120 } if (updater.compareAndSet(example5, 100, 120)) { System.out.println(example5.getCount()); } else { System.out.println(example5.getCount());//輸出120 } } public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
使用AtomicBoolean ,在N個線程內 某段代碼只執行一次
public class ATomICExample1 { private static AtomicBoolean isHappened = new AtomicBoolean(false); // 請求總數 public static int clientTotal = 5000; // 同時並發執行的線程數 public static int threadTotal = 200; public static void main(String[] args) throws Exception { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal ; i++) { executorService.execute(() -> { try { semaphore.acquire(); test(); semaphore.release(); } catch (Exception e) { System.out.println(e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); System.out.println("isHappened:"+ isHappened.get()); } private static void test() { if (isHappened.compareAndSet(false, true)) { System.out.println("execute"); } } }
synchronized 鎖
public class ATomICExample1 { // 修飾一個代碼塊 作用的對象是this public void test1(int j) { synchronized (this) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+""+ i); } } } // 修飾一個方法 作用的對象是調用方法的對象 public synchronized void test2(int j) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+""+ i); } } public static void main(String[] args) { ATomICExample1 example1 = new ATomICExample1(); ATomICExample1 example2 = new ATomICExample1(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { example1.test2(1); }); executorService.execute(() -> { example2.test2(2); }); } }
public class ATomICExample1 { // 修飾一個類 public static void test1(int j) { synchronized (ATomICExample1.class) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+"--"+ i); } } } // 修飾一個靜態方法 public static synchronized void test2(int j) { for (int i = 0; i < 10; i++) { System.out.println("test1 {} - {}"+ j+"--"+ i); } } public static void main(String[] args) { ATomICExample1 example1 = new ATomICExample1(); ATomICExample1 example2 = new ATomICExample1(); ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { example1.test1(1); }); executorService.execute(() -> { example2.test1(2); }); } }
可見性 :一個線程對主內存的修改可用被其他線程觀察到,
synchronized 實現可見性, JMM對其有以下兩條規定,
1 線程解鎖前 必須把共享變量刷新到主內存中,
2 線程加鎖前 將清空本地共享變量的值,重新去主內存中獲取共享變量的值
volatile 實現可見性,
1 對 volatile 寫操作後,會在寫操作後加入store屏障,將本地線程的共享變量的值刷新到主內存中
2 對 volatile 寫操作前, 會在讀操作前加入load屏障,從主內存中加載讀取共享變量的值。
因此,任何時候,線程獲取到 volatile 修飾的變量都是最新的值, 但是volatile修飾的共享變量 不是線程安全的,因為count++,是分三步的,1 獲取變量,2 遞增 3刷新數據到主內存。volatile 適合做B線程等待A線程完成某件事的場景,或者雙重檢測
對象的安全發布
public class UnsafePublish { private String[] states = {"a", "b", "c"}; public String[] getStates() { return states; } } public class Test { public static void main(String[] args) { UnsafePublish unsafePublish = new UnsafePublish(); System.out.println(Arrays.toString(unsafePublish.getStates())); unsafePublish.getStates()[0] = "d"; System.out.println(Arrays.toString(unsafePublish.getStates())); } }
以上是非安全的發布對象,可以看出來,對於一個類的私有屬性,這種方式是可以修改private域的,假設一個線程修改了這個值,那麽其他線程拿到的值就有可能是不正確的值。
1 餓漢單例模式或者線程安全的懶漢模式(雙鎖檢驗)
public class SingletonExample2 { // 私有構造函數 private SingletonExample2() { } // 單例對象 private static SingletonExample2 instance = new SingletonExample2(); // 靜態的工廠方法 public static SingletonExample2 getInstance() { return instance; } }
public class SingletonExample4 { // 私有構造函數 private SingletonExample4() { } // 單例對象 private static SingletonExample4 instance = null; // 靜態的工廠方法 public static SingletonExample4 getInstance() { if (instance == null) { // 雙重檢測機制 // B synchronized (SingletonExample4.class) { // 同步鎖 if (instance == null) { //這裏的新建對象分三步 ,1 分配對象的內存空間,2 初始化對象 ,3設置instance指向剛分配的內存 //第二步和第三步符合cpu指令重排的條件,所以有可能真正的執行步驟是 1->3->2,這樣當其他線程來執行if (instance == null)時得到的結果將是false,存在線程安全的問題 instance = new SingletonExample4(); } } } return instance; } }
public class SingletonExample4 { // 私有構造函數 private SingletonExample4() { } // 單例對象 加上volatile+雙鎖檢驗 限制指令重排可以達到線程安全 private static volatile SingletonExample4 instance = null; // 靜態的工廠方法 public static SingletonExample4 getInstance() { if (instance == null) { // 雙重檢測機制 // B synchronized (SingletonExample4.class) { // 同步鎖 if (instance == null) { instance = new SingletonExample4(); } } } return instance; } }
惡漢靜態域和靜態塊實現
public class SingletonExample6 { // 私有構造函數 private SingletonExample6() { } // 單例對象 這裏的靜態域如果和下面的靜態塊代碼順序調換將會導致實例化失敗 空指針異常,靜態代碼塊和靜態域是按照聲明順序執行的,這與普通方法是不一樣的 private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); } // 靜態的工廠方法 public static SingletonExample6 getInstance() { return instance; } public static void main(String[] args) { System.out.println(getInstance().hashCode()); System.out.println(getInstance().hashCode()); } }
最安全的單例模式--枚舉類型
不可變對象,使用final修飾變量,當變量為基本類型時,變量的值不可變,當為對象類型時 變量指向的引用不可變,但是變量的內容是可以變化的,比如
public class ImmutableExample1 { private final static Integer a = 1; private final static String b = "2"; private final static Map<Integer, Integer> map = Maps.newHashMap(); static { map.put(1, 2); map.put(3, 4); map.put(5, 6); } public static void main(String[] args) { // a = 2; // b = "3"; // map = Maps.newHashMap(); map.put(1, 3); } private void test(final Map<Integer, Integer> map) { // map = Maps.newHashMap(); } }
如果需要將其設置為內容也不可變,可以使用Collections.unmodifiable方法,如以下操作將會報錯,從而保證了對象的不可變
public class ImmutableExample2 { private static Map<Integer, Integer> map = Maps.newHashMap(); static { map.put(1, 2); map.put(3, 4); map.put(5, 6); map = Collections.unmodifiableMap(map); } public static void main(String[] args) { map.put(1, 3); } }
StringBuild 是線程不安全的,但是效率高,但是使用其處理局部變量是沒有線程安全的問題的,因為局部是堆棧封閉的,不存在線程安全問題,因此可以發現對於一些線程不安全的方法 如simpleDateFormat.parse方法等,要使其線程安全,可以使用局部變量來做。
StringBuffer 是線程安全的,線程安全的原因是其涉及的方法均使用synchronized同步, 因此也帶來了性能上的問題
容器
ArrayList Vector(使用synchronized保證安全) Stack(繼承於Vector,也是使用synchronized保證安全),
HashMap HashTable(也是使用synchronized保證安全)
使用Collections.synchronizedXXX(HashMap,ArrayList,HashSet)生成同步容器類。
CopyOnWriteArrayList:CopyOnWrite容器即寫時復制的容器。通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然後新的容器裏添加元素,添加完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行並發的讀,而不需要加鎖
Synchronized 與Reentranlock比較
Synchronized 在被優化前效率很低,但是優化後效率也還可以,
Synchronized 語法簡單,並且自動加鎖與自動解鎖,很便利。
Reentranlock 獨有的功能
Reentranlock 可以指定公平鎖和非公平鎖
提供了Condition,可以分組的喚醒需要喚醒的線程
可以中斷等待鎖的的線程機制,lock.lockInterruptibly();
Synchronized 能做的事Reentranlock基本都能做到,但是前者語法簡單 使用容易
volatile 的定義及原理
volatile可以被理解為輕量級的synchronized,它可以保證當A線程修改一個共享變量時,B線程能讀到這個修改值。
每個處理器通過嗅探總線上傳播來的數據來檢查自己緩存的值是不是過期,如果過期了,那麽就會將緩存的數據設置為無效狀態,並且刷新緩存值。
synchronized 的原理與應用
synchronized 被稱為重量級鎖,但是java6之後進行各種優化之後,有時候它也不是很重,
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。鎖的三種形式
對於普通同步方法,鎖是當前實例對象。
對於靜態同步方法,鎖是當前類的Class對象。
對於同步方法塊,鎖是synchronized括號裏配置的對象
鎖的級別
由低到高一次是 無鎖<偏向鎖<輕量鎖<重量鎖
偏向鎖:大多數的時候,鎖不存在多線程的競爭,而是總是由一個線程獲取到,偏向鎖就是為了讓線程獲取鎖的代價更低而引入的,當一個線程訪問同步塊並獲取鎖的時候,會在對象頭和棧幀中記錄鎖的偏向線程id,以後該線程在進入或者退出同步塊是不需要CAS操作來加鎖和解鎖,僅僅是測試一下對象頭是否存儲偏向線程id,如果測試成功,那麽即代表已獲得鎖。只有競爭出現的時候才釋放偏向鎖(其他線程訪問同步塊時)。可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false。
java內存模型(JMM)
線程stack主要存放一下基本類型的變量,比如int ,float,double和對象句柄,這裏的Object1 包含的方法中的局部變量,也是存儲在棧上的,
靜態成員變量會跟隨對象存儲在堆上,當線程訪問對象方法時包含了成員變量,這些線程都擁有了這個對象的成員變量的私有拷貝。
線程讀取狀態與法則
lock(鎖定) :作用於主內存變量,把變量標誌為一條線程獨占狀態。
unlock(解鎖):作用於主內存變量,把一個處於鎖定狀態變量解放出來,這樣才能被其他線程鎖定。
read(讀取): 作用於主內存變量,把變量值從主內存傳輸到工作內存中,以便後面的load操作。
load(載入):作用於線程的工作內存,它把read操作從主內存讀取的變量值放入到工作內存的變量副本中。
use(使用):作用線程的工作內存,把工作內存的變量值賦值給執行引擎。
assign(賦值):作用於線程的工作內存,把執行引擎收到的值賦值工作內存的變量。
store(存儲):作用於線程的工作內存,把工作內存的變量值傳遞到主內存中,以便後續的write操作。
write(寫入):作用於主內存變量,它把store操作中的線程工作內存的變量值傳遞到主內存中。
1 如果要把變量從主內存復制到工作內存中,就需要按順序的執行read和load操作,如果把變量從工作內存同步到主內存中,就需要按順序的執行store和write操作,但是java內存模型要求上述操作的順序執行,而沒保證必須是連續執行。
2 不允許read|laod , store|write操作之一單獨出現。
3 不允許一個線程丟棄它最近的assign操作,即變量在工作內存中改變了之後必須同步到主內存中,
4 不允許一個線程無原因的(未發生assign操作)就把數據從工作內存同步到主內存中
5 一個新的變量只能在主內存中誕生。不允許在工作內存中直接使用一個未被初始化(load和assign)的變量。即對一個變量實施use和store操作之前,必須先執行assign和load操作。
6 一個變量在同一個時刻只允許一條線程對其進行lock操作,但是lock操作可以被同一個線程執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量才會被解鎖,lock和unlock必須成對出現.
7 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load和assign操作初始化變量的值。
8 如果一個變量沒有事先被lock鎖定,則不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。
9 對一個變量執行unlock操作之前,必須先把這個變量同步到主內存中(執行store和write操作)
java語言基礎--線程相關類