此次不講原理了,主要是一些應用方面的知識,和上幾回的JUC併發編程的知識點更容易理解.html
知識回顧:java
上次主要說了Semaphore信號量的使用,就是一個票據的使用,咱們舉例了看3D電影拿3D眼鏡的例子,還說了內部的搶3D眼鏡,和後續排隊的源碼解析,還有CountDownLatch的使用,咱們是用王者農藥來舉例的,CyclicBarrier柵欄的使用和CountDownLatch幾乎是一致的,Executors用的不多我只是簡單的寫了一個小示例。上次遺漏了一個CountDownLatch和CyclicBarrier的區別。mysql
CountDownLatch和CyclicBarrier的區別:面試
區別的根本在於有無主線程參與,這樣就很容易區別了,CountDownLatch有主線程,CyclicBarrier沒有主線程,咱們來舉兩個例子,CountDownLatch主線程是遊戲程序,而咱們開啓的10個線程是玩家加載程序,咱們的遊戲主程序會等待10個玩家加載完成,線程可能結束,而後主程序遊戲程序繼續運行。CyclicBarrier沒有主線程,可是具備重複性,再舉一個例子,年會了,公司團建活動,三人跨柵欄,要求是必須三人所有跨過柵欄之後才能夠繼續跨下一個柵欄。sql
CountDownLatch和CyclicBarrier都有讓多個線程等待同步而後再開始下一步動做的意思,可是CountDownLatch的下一步的動做實施者是主線程,具備不可重複性;而CyclicBarrier的下一步動做實施者仍是「其餘線程」自己,具備往復屢次實施動做的特色。編程
本次新知識bootstrap
什麼是原子操做?數組
原子(atom)本意是「不能被進一步分割的小粒子」,而原子操做(atomic operation)意爲」不可被中斷的一個或一系列操做」 。就像是咱們的mysql裏面的提到的ACID,原子性,也是不可分割的操做,最小的單位。緩存
咱們之前說的MESI,說到了緩存行,也是上鎖的最小單位,原子變動就不作過多解釋了,就是把一個變量的值改成另一個值。比較與交換咱們在Semaphore源碼裏也接觸過了,也就是CAS操做須要輸入兩個數值,一箇舊值,一個新值,在將要變動爲新值以前,會比較舊值是否已經改變,若是改變了修改失敗,若是沒有改變,修改爲功。安全
Atomic的使用
在Atomic包內一共有12個類,四種原子更新方式,原子更新基本類型,原子更新數組,原子更新字段,Atomic包裏的類基本都是基於Unsafe實現的包裝類。
基本類型:AtomicInteger,AtomicBoolean,AtomicLong。
引用類型:AtomicReference、AtomicReference的ABA實例、AtomicStampedReference、AtomicMarkableReference。
數組類型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray。
屬性原子修改器:AtomicLongFieldUpdater、AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater。
來一個簡單的實例,就是開啓10個線程而後作一個自加的操做,仍是很好理解的。
public class AtomicIntegerTest { static AtomicInteger atomicInteger = new AtomicInteger(); public static void main(String[] args) { for (int i = 0; i<10; i++){ new Thread(new Runnable() { @Override public void run() { atomicInteger.incrementAndGet(); } }).start(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("自加10次數值:--->"+atomicInteger.get()); } }
ABA問題,ABA這樣更能好理解一些,一眼就能夠看出來A已經不是原來的A了,雖然值同樣,可是裏面的屬性變成了紅色的,先來看一段代碼。
package com.xiaocai.main; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { Thread main = new Thread(new Runnable() { @Override public void run() { int a = atomicInteger.get(); System.out.println("操做線程"+Thread.currentThread().getName()+",修改前操做數值:"+a); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isCasSuccess = atomicInteger.compareAndSet(a,2); if(isCasSuccess){ System.out.println("操做線程"+Thread.currentThread().getName()+",Cas修改後操做數值:"+atomicInteger.get()); }else{ System.out.println("CAS修改失敗"); } } },"主線程"); Thread other = new Thread(new Runnable() { @Override public void run() { atomicInteger.incrementAndGet();// 1+1 = 2; System.out.println("操做線程"+Thread.currentThread().getName()+",自加後值:"+atomicInteger.get()); atomicInteger.decrementAndGet();// atomic-1 = 2-1; System.out.println("操做線程"+Thread.currentThread().getName()+",自減後值:"+atomicInteger.get()); } },"干擾線程"); main.start(); other.start(); } }
咱們能夠看到主線程設置一個初始值爲1,而後進行等待,干擾線程將1修改成2,又將2修改回1,而後主線程繼續操做1修改成2,這一系列的動做,主線程並無感知到1已經不是原來的1了。
這樣的操做實際上是很危險的,咱們假象,小王是銀行的職員,他能夠操做每一個帳戶的金額(假設啊,具體能不能我也不知道),他將撕蔥的帳戶轉走了1000萬用於炒股,股市大漲,小王賺了2000萬,還了1千萬,本身還剩下2千萬,過幾天撕蔥來查看本身帳戶錢並無少,可是錢已經不是那個錢了,有人動過的。因此ABA問題咱們仍是要想辦法來處理的。咱們每次轉帳匯款的操做都是有一個流水號(回執單)的,也就是每次咱們加一個版本號碼就能夠了,咱們來改一下代碼。
public class AtomicIntegerTest { static AtomicStampedReference atomicInteger = new AtomicStampedReference<>(1,0); public static void main(String[] args) { Thread main = new Thread(new Runnable() { @Override public void run() { int stamp = atomicInteger.getStamp(); //獲取當前標識別 System.out.println("操做線程"+Thread.currentThread().getName()+"修改前的版本號爲:"+stamp+",修改前操做數值:"+atomicInteger.getReference()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isCasSuccess = atomicInteger.compareAndSet(1,2,stamp,stamp +1); //此時expectedReference未發生改變,可是stamp已經被修改了,因此CAS失敗 if(isCasSuccess){ System.out.println("操做線程"+Thread.currentThread().getName()+",Cas修改後操做數值:"+atomicInteger.getReference()); }else{ System.out.println("CAS修改失敗,當前版本爲:"+atomicInteger.getStamp()); } } },"主線程"); Thread other = new Thread(new Runnable() { @Override public void run() { int stamp = atomicInteger.getStamp(); atomicInteger.compareAndSet(1,2,atomicInteger.getStamp(),atomicInteger.getStamp()+1); System.out.println("操做線程"+Thread.currentThread().getName()+",版本號爲:"+stamp+",修改後的版本號爲:"+atomicInteger.getStamp()+",自加後值:"+atomicInteger.getReference()); int newStamp = atomicInteger.getStamp(); atomicInteger.compareAndSet(2,1,atomicInteger.getStamp(),atomicInteger.getStamp()+1); System.out.println("操做線程"+Thread.currentThread().getName()+",版本號爲:"+newStamp+",修改後的版本號爲:"+atomicInteger.getStamp()+",自減後值:"+atomicInteger.getReference()); } },"干擾線程"); main.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } other.start(); } }
咱們先初始一個主線程,而且設置版本號爲0。而後干擾線程進行修改,每次修改時版本號加一,干擾線程結束,而主線程想繼續修改時,發現版本不匹配,修改失敗。
其他Atomic的類使用都是大同小異的,能夠自行嘗試一遍。
Unsafe魔術類的使用
Unsafe是位於sun.misc包下的一個類,主要提供一些用於執行低級別、不安全操做的 方法,如直接訪問系統內存資源、自主管理內存資源等,這些方法在提高Java運行效率、增 強Java語言底層資源操做能力方面起到了很大的做用。但因爲Unsafe類使Java語言擁有了 相似C語言指針同樣操做內存空間的能力,這無疑也增長了程序發生相關指針問題的風險。 在程序中過分、不正確使用Unsafe類會使得程序出錯的機率變大,使得Java這種安全的語 言變得再也不「安全」,所以對Unsafe的使用必定要慎重。
在過去的幾篇博客裏也說到了Unsafe這個類,咱們須要經過反射來使用它,好比讀寫屏障、加鎖解鎖,線程的掛起操做等等。
如何獲取Unsafe實例?
一、從getUnsafe方法的使用限制條件出發,經過Java命令行命令-Xbootclasspath/a把 調用Unsafe相關方法的類A所在jar包路徑追加到默認的bootstrap路徑中,使得A被 引導類加載器加載,從而經過Unsafe.getUnsafe方法安全的獲取Unsafe實例。 java Xbootclasspath/a:${path} // 其中path爲調用Unsafe相關方法的類所在jar包路徑。
二、經過反射獲取單例對象theUnsafe。
public static Unsafe reflectGetUnsafe() { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { e.printStackTrace(); } return null; }
總結:
此次博客徹底沒有代碼的解析閱讀,都是一些簡單的使用,咱們開始時候說到了什麼是原子操做,接下來咱們說了Atomic類的基本使用,再就是什麼是ABA問題,如何用Atomic來解決ABA問題,再就是咱們的魔術類Unsafe類,越過虛擬機直接來操做咱們的系統的一些操做(不是超級熟練別玩這個,玩壞了很差修復)。但願對你們在工做面試中能有一些幫助。
最進弄了一個公衆號,小菜技術,歡迎你們的加入