1. 程式人生 > >JUC——檢視閱讀

JUC——檢視閱讀

JUC——檢視閱讀

參考資料

JUC知識圖參考

JUC框架學習順序參考

J.U.C學習總結參考,簡潔直觀

易百併發程式設計,實踐操作1,不推薦閱讀,不及格

JUC文章,帶例子講解,可以學習2

Doug Lea併發程式設計文章全部譯文

juc部落格

維護一個屬於自己的知識框架圖;隔三差五去看看你所記的東西。


J.U.C包的作者:Doug Lea

JUC底層實現

Concurrent包下所有類底層都是依靠CAS操作來實現,而sun.misc.Unsafe為我們提供了一系列的CAS操作。

CAS,即Compare And Swap。 顧名思義就是比較並交換。CAS操作一般涉及三個運算元:記憶體值,預期原值,新值。如果記憶體值與預期原值相同,則將會用新值替換記憶體值,返回更新成功,否則,什麼也不處理,返回更新失敗。java.util.concurrent包的底層即是依靠CAS操作來實現,CAS在java中的具體實現是sun.misc.Unsafe類,作為java.util.concurrent的實現基石,學習sun.misc.Unsafe類的方法特性就會顯得十分重要。

CAS缺點:

  1. ABA問題 。(ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼 A-B-A 就會變成1A-2B-3A。類AtomicStampedReference來解決ABA問題 .)
  2. 迴圈時間長開銷大 。(如果JVM能支援處理器提供的pause指令那麼效率會有一定的提升)
  3. 只能保證一個共享變數的原子操作 。(加鎖或者使用AtomicReference類來保證引用物件之間的原子性,把多個變數放在一個物件裡來進行CAS操作。 )

JAVA CAS操作與volatile的記憶體模型關係

java的CAS同時具有 volatile 讀和volatile寫的記憶體語義。

concurrent包的原始碼實現,會發現一個通用化的實現模式:

首先,宣告共享變數為volatile;

然後,使用CAS的原子條件更新來實現執行緒之間的同步;

同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的記憶體語義來實現執行緒之間的通訊。

AQS,非阻塞資料結構和原子變數類(java.util.concurrent.atomic包中的類)這三種concurrent包中的基礎類都是使用這種模式來實現的,而concurrent包中的高層類又是依賴於這些基礎類來實現的。

concurrent包的實現架構分層:

CAS與sun.misc.Unsafe參考

public class UnSafeTest {
    public static Unsafe unsafeBean = null;
    static {
        Field unsafe = null;
        try {
            // private static final Unsafe theUnsafe;獲取該域
            //有必要複習一下反射,全忘光了
            unsafe = Unsafe.class.getDeclaredField("theUnsafe");
            unsafe.setAccessible(true);
            try {
                unsafeBean = (Unsafe)unsafe.get(null);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

        System.out.println(unsafeBean);
    }
}

JUC架構由五個模組組成

分別是Atomic,Locks,Collections,Executor,Tools。

java併發

多工是當多個程序共享,如CPU處理公共資源。 多執行緒將多工的概念擴充套件到可以將單個應用程式中的特定操作細分為單個執行緒的應用程式。每個執行緒可以並行執行。 作業系統不僅在不同的應用程式之間劃分處理時間,而且在應用程式中的每個執行緒之間劃分處理時間。

執行緒的生命週期

執行緒生命週期的階段 :

  • 新執行緒(New) - 新執行緒在新的狀態下開始其生命週期。直到程式啟動執行緒為止,它保持在這種狀態。它也被稱為出生執行緒。
  • 可執行(Runnable) - 新誕生的執行緒啟動後,該執行緒可以執行。狀態的執行緒被認為正在執行其任務。
  • 等待(Waiting) - 有時,執行緒會轉換到等待狀態,而執行緒等待另一個執行緒執行任務。 只有當另一個執行緒發訊號通知等待執行緒才能繼續執行時,執行緒才轉回到可執行狀態。
  • 定時等待(Timed Waiting) - 可執行的執行緒可以在指定的時間間隔內進入定時等待狀態。 當該時間間隔到期或發生等待的事件時,此狀態的執行緒將轉換回可執行狀態。
  • 終止(Dead) - 可執行執行緒在完成任務或以其他方式終止時進入終止狀態。

執行緒優先順序

每個Java執行緒都有一個優先順序,可以幫助作業系統確定安排執行緒的順序。Java執行緒優先順序在MIN_PRIORITY(常數為1)和MAX_PRIORITY(常數為10)之間的範圍內。 預設情況下,每個執行緒都被賦予優先順序NORM_PRIORITY(常數為5)。

具有較高優先順序的執行緒對於一個程式來說更重要,應該在低優先順序執行緒之前分配處理器時間。注意,執行緒優先順序不能保證執行緒執行的順序,並且依賴於平臺。只是有優選的許可權,並不代表一定優先執行。

java建立執行緒的4種方式

  1. 繼承Thread類建立執行緒類 。
  2. 實現Runnable介面建立執行緒類 。(其中還可以使用“匿名內部類”建立多執行緒)
  3. 通過Callable和Future建立執行緒 。
  4. 通過執行緒池建立執行緒。

繼承Thread類建立執行緒類

步驟:

1、定義一個類繼承Thread類,並重寫run()方法,run()方法的方法體就是執行緒任務;

2、建立該類的例項物件,即建立了執行緒物件;

3、呼叫執行緒物件的start()方法來啟動執行緒;

例項:

public class DaqingMountainThread extends Thread {

    private static final Logger LOGGER = LoggerFactory.getLogger(DaqingMountainThread.class);
    @Override
    public void run() {
        for (int i = 0; i <3; i++) {
            LOGGER.error("----大青山副團長做第"+i+"個傭兵任務----");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

測試類:

DaqingMountainThread thread = new DaqingMountainThread();
  thread.start();

例項2:

public class DQMountainThread extends Thread {

    private static final Logger LOGGER = LoggerFactory.getLogger(DQMountainThread.class);

    private String name;

    private Thread thread;

    public DQMountainThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 0; i <3; i++) {
            LOGGER.error("----大青山副團長做第"+i+"個傭兵任務----");
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void start(){
        LOGGER.error("start thread "+name);
        if (thread == null) {
            //該建構函式Thread(Runnable target, String name) 可用是因為Thread 實現了 Runnable
            thread = new Thread(this,name);
        }
        thread.start();
    }
}

測試類

DQMountainThread thread = new DQMountainThread("大青山副團長");
thread.start();

輸出:

ERROR - start thread 大青山副團長
ERROR - ----大青山副團長做第0個傭兵任務----
ERROR - ----大青山副團長做第1個傭兵任務----
ERROR - ----大青山副團長做第2個傭兵任務----

實現Runnable介面建立執行緒類

步驟:

1、定義一個類實現Runnable介面,該類的run()方法是該執行緒的執行緒任務;

2、建立該Runnable的物件例項;

3、將Runnable物件例項作為構造器引數傳入Thread類例項物件,這個物件才是真正的執行緒物件;

4、呼叫執行緒物件的start()方法啟動該執行緒

示例1:

public class AmyRunnable implements Runnable{
        private static final Logger LOGGER = LoggerFactory.getLogger(AmyRunnable.class);
    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            LOGGER.error("----艾米團長做第"+i+"個傭兵任務----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

測試類:

AmyRunnable amyRunnable = new AmyRunnable();
//艾米團長為該執行緒命名
 Thread t = new Thread(amyRunnable,"艾米團長");
 t.start();

例項2:

public class AmyHaborRunnable implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(AmyRunnable.class);

    private String name;

    private Thread thread;

    public AmyHaborRunnable(String name) {
        this.name = name;
    }
    
    @Override
    public void run() {
        for (int i = 0; i <10; i++) {
            LOGGER.error("----艾米團長做第"+i+"個傭兵任務----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void start(){
        LOGGER.error("start thread "+name);
        if (thread == null) {
            thread = new Thread(this,name);
        }
        thread.start();
    }
}

測試類:

AmyHaborRunnable amyHaborRunnable = new AmyHaborRunnable("艾米團長");
amyHaborRunnable.start();

通過Callable和Future建立執行緒

步驟:

1、建立Callable介面實現類,並實現call()方法,是該執行緒的執行緒任務,有返回值。

2、建立Callable實現類的物件例項,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值;

3、使用FutureTask物件例項作為構造器引數傳入Thread類例項物件。

4、呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值。

示例:

public class GreenDragon implements Callable<String> {
    private static final Logger LOGGER = LoggerFactory.getLogger(GreenDragon.class);
    @Override
    public String call() throws Exception {
        for (int i = 0; i <10; i++) {
            LOGGER.error("----綠兒發射第"+i+"個龍息----");
            TimeUnit.SECONDS.sleep(1);
        }
        return "綠兒完成任務賞蜥蜴幹";
    }
}

測試類1:

GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//Callable是否任務完成
if (!futureTask.isDone()) {
    TimeUnit.SECONDS.sleep(1);
}
//get()方法阻塞當前執行緒,直到呼叫的執行緒執行結束。
LOGGER.error(futureTask.get());

測試類2:

GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//get(long timeout, TimeUnit unit) 方法只會阻塞到設定的時間超時就會返回超時異常TimeoutException
LOGGER.error(futureTask.get(10,TimeUnit.SECONDS));

測試類3,取消任務:

GreenDragon greenDragon = new GreenDragon();
FutureTask<String> futureTask = new FutureTask<>(greenDragon);
Thread thread = new Thread(futureTask);
thread.start();
//取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。
// 引數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設定true,則表示可以取消正在執行過程中的任務。
// 如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;
// 如果任務正在執行,若mayInterruptIfRunning設定為true,則返回true,若mayInterruptIfRunning設定為false,則返回false;
// 如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
/**
 * 經過實驗,當mayInterruptIfRunning設定為false時,任務並不會中斷,會繼續執行完整個task。這裡的任務指的是這條執行緒執行的call方法體
 * 輸出:
 * ERROR - ----綠兒發射第0個龍息----
 * ERROR - ----綠兒發射第1個龍息----
 * ERROR - true
 * ERROR - ----綠兒發射第2個龍息----
 * ERROR - ----綠兒發射第3個龍息----
 * ERROR - ----綠兒發射第4個龍息----
 * ERROR - ----綠兒發射第5個龍息----
 * ERROR - ----綠兒發射第6個龍息----
 * ERROR - ----綠兒發射第7個龍息----
 * ERROR - ----綠兒發射第8個龍息----
 * ERROR - ----綠兒發射第9個龍息----
 *
 * 當mayInterruptIfRunning設定為true時,會取消正在執行過程中的任務,任務中斷。會返回true。
 * 輸出:
 * ERROR - ----綠兒發射第0個龍息----
 * ERROR - ----綠兒發射第1個龍息----
 * ERROR - true
 *
 * 特別注意的是,在呼叫futureTask.cancel(true)方法時,呼叫執行緒或者說主執行緒不能呼叫futureTask.get()方法,get()方法阻塞當前執行緒,直到呼叫的執行緒執行結束。
 * 因此在get()方法後面呼叫cancel是沒有意義的,任務已經結束,返回肯定是false;
 * 另一種情況是也不能呼叫get(long timeout, TimeUnit unit) 方法,因為當超時時呼叫執行緒或者說主執行緒就會丟擲TimeoutException異常而中斷執行不到,
 * 而如果沒有超時的話任務已經結束,返回肯定是false;
 */
boolean cancel = futureTask.cancel(true);
LOGGER.error(String.valueOf(cancel));

通過執行緒池建立執行緒

待新增。

四種建立執行緒方式對比

待對比。

Java併發執行緒間通訊 (早期用法)

編號 方法 描述
1 public void wait() 使當前執行緒等到另一個執行緒呼叫notify()方法。
2 public void notify() 喚醒在此物件監視器上等待的單個執行緒。
3 public void notifyAll() 喚醒所有在同一個物件上呼叫wait()的執行緒。

例項:

public class Dialogue {

    boolean flag = false;

    public synchronized void question(String msg) {
        if (flag) {
            try {
                wait();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("question: "+msg);
        flag = true;
        notify();
    }

    public synchronized void answer(String msg) {
        if (!flag) {
            try {
                wait();
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("answer: "+msg);
        flag = false;
        notify();
    }
}

public class AmyHabor implements Runnable {

    private Dialogue dialogue;
    private String[] strArr = {"綠兒~~~","想不想吃蜥蜴幹呀?","來,咱們先簽個綠龍穴探索財產分割協議!"};

    public AmyHabor(Dialogue dialogue) {
        this.dialogue = dialogue;
    }

    @Override
    public void run() {
        for (int i = 0; i < strArr.length; i++) {
            dialogue.question(strArr[i]);
        }
    }
}

public class GreenDragon implements Runnable {

    private Dialogue dialogue;
    private String[] strArr = {"啊球,鼻涕留下來!","好啊,好啊","艾米哥哥真好!(喝不盡的仇人血啊)",};

    public GreenDragon(Dialogue dialogue) {
        this.dialogue = dialogue;
    }

    @Override
    public void run() {
        for (int i = 0; i < strArr.length; i++) {
            dialogue.answer(strArr[i]);
        }
    }
}

測試類:

Dialogue dialogue = new Dialogue();
AmyHabor amy = new AmyHabor(dialogue);
GreenDragon dragon = new GreenDragon(dialogue);
new Thread(amy).start();
new Thread(dragon).start();

輸出:

question: 綠兒~~~
answer: 啊球,鼻涕留下來!
question: 想不想吃蜥蜴幹呀?
answer: 好啊,好啊
question: 來,咱們先簽個綠龍穴探索財產分割協議!
answer: 艾米哥哥真好!(喝不盡的仇人血啊)

Java簡單併發同步

可以用同步方法或者同步程式碼塊synchronized來實現併發同步。他們的原理都是一個物件例項就是一把鎖,當大家用同一把鎖(物件)時,該物件的同步方法(無論有幾個),一次就只能有一條執行緒在執行其中的一個方法,執行完釋放了鎖才能讓下一個執行緒獲取到鎖執行同步方法。

例項:

public class CoinCount {

    public synchronized void count() {
        for (int i = 0; i < 3; i++) {
            System.out.println(i + "個金幣");
        }
    }
}

public class ChiHanFeng implements Runnable {

    private Thread thread;
    private String name;
    private CoinCount coinCount;

    public ChiHanFeng(String name, CoinCount coinCount) {
        this.name = name;
        this.coinCount = coinCount;
    }

    @Override
    public void run() {
        System.out.println(name+"開始算錢");
        coinCount.count();
    }

    public void start(){
        if (thread == null) {
            thread = new Thread(this,name);
        }
        thread.start();
    }
}

測試類:

CoinCount coinCount = new CoinCount();
ChiHanFeng chiHanFeng = new ChiHanFeng("chihanfeng_1",coinCount);
chiHanFeng.start();
ChiHanFeng chiHanFeng2 = new ChiHanFeng("chihanfeng_2",coinCount);
chiHanFeng2.start();

輸出:

chihanfeng_1開始算錢
0個金幣
1個金幣
2個金幣
chihanfeng_2開始算錢
0個金幣
1個金幣
2個金幣

Java併發死鎖

死鎖描述了兩個或多個執行緒等待彼此而被永久阻塞的情況。 當多個執行緒需要相同的鎖定但以不同的順序獲取時,會發生死鎖。

//待看java併發程式設計書詳解。

例項:

public class AmyHaborRunnable implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(AmyHaborRunnable.class);

    private String name;

    private Thread thread;

    public AmyHaborRunnable(String name) {
        this.name = name;
    }


    @Override
    public void run() {
        synchronized (JUCTest.rengar) {
            LOGGER.error("----艾米團長獲取雷葛老師學習魔法----");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.error("----艾米團長嘗試獲取池寒楓練習長劍----");
            synchronized (JUCTest.chiHanFeng) {
                for (int i = 0; i < 10; i++) {
                    LOGGER.error("----雷葛,池寒楓給艾米團長教學----");
                }
            }
        }

    }

    public void start() {
        LOGGER.error("start thread " + name);
        if (thread == null) {
            thread = new Thread(this, name);
        }
        thread.start();
    }
}

public class DQMountainThread extends Thread {

    private static final Logger LOGGER = LoggerFactory.getLogger(DQMountainThread.class);

    private String name;

    private Thread thread;

    public DQMountainThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        synchronized (JUCTest.chiHanFeng) {
            LOGGER.error("----大青山副團長獲取池寒楓練習龍槍----");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.error("----大青山副團長嘗試獲取雷葛老師學習帝國曆史----");
            synchronized (JUCTest.rengar) {
                for (int i = 0; i < 10; i++) {
                    LOGGER.error("----雷葛,池寒楓給大青山副團長教學----");
                }
            }
        }
    }

    @Override
    public void start(){
        LOGGER.error("start thread "+name);
        if (thread == null) {
            //該建構函式Thread(Runnable target, String name) 可用是因為Thread 實現了 Runnable
            thread = new Thread(this,name);
        }
        thread.start();
    }
}

測試類

//雷葛
public static Object rengar = new Object();
//池寒楓
public static Object chiHanFeng = new Object();
public static void main(String[] args) {
    //死鎖演示
    AmyHaborRunnable amyRunnable = new AmyHaborRunnable("艾米團長");
    DQMountainThread dqMountainThread = new DQMountainThread("大青山副團長");
    amyRunnable.start();
    dqMountainThread.start();
}

輸出:死鎖

ERROR - start thread 艾米團長
ERROR - start thread 大青山副團長
ERROR - ----大青山副團長獲取池寒楓練習龍槍----
ERROR - ----艾米團長獲取雷葛老師學習魔法----
ERROR - ----艾米團長嘗試獲取池寒楓練習長劍----
ERROR - ----大青山副團長嘗試獲取雷葛老師學習帝國曆史----

死鎖解決方法之一:調整鎖的申請順序,總是以相同的順序來申請鎖 。

例項:

@Override
public void run() {
    synchronized (JUCTest.chiHanFeng) {
        LOGGER.error("----大青山副團長獲取池寒楓練習龍槍----");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        LOGGER.error("----大青山副團長嘗試獲取雷葛老師學習帝國曆史----");
        synchronized (JUCTest.rengar) {
            for (int i = 0; i < 1; i++) {
                LOGGER.error("----雷葛,池寒楓給大青山副團長教學----");
            }
        }
    }
}

    @Override
    public void run() {
        synchronized (JUCTest.chiHanFeng) {
            LOGGER.error("----艾米團長嘗試獲取池寒楓練習長劍----");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            LOGGER.error("----艾米團長獲取雷葛老師學習魔法----");
            synchronized (JUCTest.rengar) {
                for (int i = 0; i < 1; i++) {
                    LOGGER.error("----雷葛,池寒楓給艾米團長教學----");
                }
            }
        }
    }

輸出:

ERROR - start thread 艾米團長
ERROR - start thread 大青山副團長
ERROR - ----艾米團長嘗試獲取池寒楓練習長劍----
ERROR - ----艾米團長獲取雷葛老師學習魔法----
ERROR - ----雷葛,池寒楓給艾米團長教學----
ERROR - ----大青山副團長獲取池寒楓練習龍槍----
ERROR - ----大青山副團長嘗試獲取雷葛老師學習帝國曆史----
ERROR - ----雷葛,池寒楓給大青山副團長教學----

原子變數(Atomic)

使用原子變數類最大的好處就是可以避免多執行緒的優先順序倒置和死鎖情況的發生,提升在高併發處理下的效能。

AtomicLong

java.util.concurrent.atomic.AtomicLong類提供了可以被原子地讀取和寫入的底層long值的操作,並且還包含高階原子操作。 AtomicLong支援基礎long型別變數上的原子操作。 **它具有獲取和設定方法,如在volatile變數上的讀取和寫入。

這些原子變數類主要用於在高併發環境下的高效程式處理,來幫助我們簡化同步處理。

AtomicLong類中的方法

序號 方法 描述
1 public long addAndGet(long delta) 將給定值原子地新增到當前值。
2 public boolean compareAndSet(long expect, long update) 如果當前值與預期值相同,則將該值原子設定為給定的更新值。
3 public long decrementAndGet() 當前值原子減1。
4 public double doubleValue() 以double形式返回指定數字的值。
5 public float floatValue() 以float形式返回指定數字的值。
6 public long get() 獲取當前值。
7 public long getAndAdd(long delta) 自動將給定值新增到當前值。
8 public long getAndDecrement() 當前值原子減1。
9 public long getAndIncrement() 當前值原子增加1。
10 public long getAndSet(long newValue) 將原子設定為給定值並返回舊值。
11 public long incrementAndGet() 原子上增加一個當前值。
12 public int intValue() 以int形式返回指定數字的值。
13 public void lazySet(long newValue) 最終設定為給定值。
14 public long longValue() 返回指定數字的值為long型別。
15 public void set(long newValue) 設定為給定值。
16 public String toString() 返回當前值的String表示形式。
17 public boolean weakCompareAndSet(long expect, long update) 如果當前值與預期值相同,則將該值原子設定為給定的更新值。

例項:

public class Soldier implements Callable<String> {

    private RedStoneCounter redStoneCounter;

    public Soldier(RedStoneCounter redStoneCounter) {
        this.redStoneCounter = redStoneCounter;
    }

    @Override
    public String call() throws Exception {
         redStoneCounter.counterSoldier();
        return "yes,sir";
    }
}

public class RedStoneCounter {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedStoneCounter.class);
    private AtomicLong atomicLong = new AtomicLong(0);

    //不是原子變數類的話,不能確保一定是1000,要想保證則要加synchronized 
    //ERROR - 紅石大帝點兵,應到1000人,實到:996
    //private Long atomicLong = 0L;

    public void counterSoldier(){
        //這些都可以用
        atomicLong.getAndIncrement();
        //atomicLong.addAndGet(1);
        //atomicLong.getAndAdd(1);
        //atomicLong.incrementAndGet();
    }

    public Long getTotalSoldier(){
        return atomicLong.get();
    }

    public static void main(String[] args) throws InterruptedException {
        final RedStoneCounter redStone = new RedStoneCounter();
        for (int i = 0; i < 1000; i++) {
            new Thread(new FutureTask<String>(new Soldier(redStone))).start();
        }
        //等待時間不夠,ERROR - 紅石大帝點兵,應到1000人,實到:663
        //TimeUnit.MILLISECONDS.sleep(1);
        //ERROR - 紅石大帝點兵,應到1000人,實到:1000
        TimeUnit.MILLISECONDS.sleep(500);
        LOGGER.error("紅石大帝點兵,應到1000人,實到:" +redStone.getTotalSoldier());
    }
}

AtomicInteger

public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,並設定新的值
public final int getAndIncrement()//獲取當前的值,並自增
public final int getAndDecrement() //獲取當前的值,並自減
public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值

AtomicInteger類:

//AtomicInteger的關鍵域
public class AtomicInteger extends Number implements java.io.Serializable {
    // setup to use Unsafe.compareAndSwapInt for updates
    //unsafe是java提供的獲得物件記憶體地址訪問的類,它的作用就是在更新操作時提供“比較並替換”的作用。實際上就是AtomicInteger中的一個工具。
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //valueOffset是用來記錄value值本身在記憶體的偏移地址的,這個記錄主要是為了在更新操作時在記憶體中找到value的位置,方便比較。
    private static final long valueOffset;

    static {
      try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }
//value是用來儲存整數的實際變數,也就是AtomicInteger的值,這裡被宣告為volatile,是為了保證在更新操作時,當前執行緒可以拿到value最新的值。
    private volatile int value;
    
    //CAS自增
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }
    
    //比較更新值
    public final boolean compareAndSet(int expect, int update) {
        //使用unsafe的native方法,實現高效的硬體級別CAS
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
//...
}

效能測試示例:

public interface Counter {

    void counterSoldier();

}


public class SimpleRedStoneCounter implements Counter {
    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleRedStoneCounter.class);

    private Long atomicLong = 0L;

    @Override
    public synchronized void counterSoldier() {
        atomicLong++;
    }

    public Long getTotalSoldier() {
        return atomicLong;
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int testNum = 100000;
        long start = System.currentTimeMillis();
        final SimpleRedStoneCounter redStone = new SimpleRedStoneCounter();
        //ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

        for (int i = 0; i < testNum; i++) {
            FutureTask<String> futureTask = new FutureTask<>(new Soldier(redStone));
            //poolExecutor.execute(futureTask);
            new Thread(futureTask).start();
            if (i == testNum-1) {
                futureTask.get();
            }
        }
        //TimeUnit.MILLISECONDS.sleep(500);
        long end = System.currentTimeMillis();
        LOGGER.error("紅石大帝點兵,應到" + testNum + "人,實到:" + redStone.getTotalSoldier());
        LOGGER.error("紅石大帝點兵耗時:" + (end - start));
        //poolExecutor.shutdown();
    }
}

public class RedStoneCounter implements Counter {
    private static final Logger LOGGER = LoggerFactory.getLogger(RedStoneCounter.class);
    private AtomicLong atomicLong = new AtomicLong(0);

    //不是原子變數類的話,不能確保一定是1000,ERROR - 紅石大帝點兵,應到1000人,實到:996
    //private Long atomicLong = 0L;

    @Override
    public void counterSoldier() {
        //這些都可以用
        atomicLong.getAndIncrement();
        //atomicLong.addAndGet(1);
        //atomicLong.getAndAdd(1);
        //atomicLong.incrementAndGet();
    }

    public Long getTotalSoldier() {
        return atomicLong.get();
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int testNum = 100000;
        long start = System.currentTimeMillis();
        final RedStoneCounter redStone = new RedStoneCounter();
        //ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

        for (int i = 0; i < testNum; i++) {
            FutureTask<String> futureTask = new FutureTask<>(new Soldier(redStone));
            //poolExecutor.execute(futureTask);
            new Thread(futureTask).start();
            if (i == testNum - 1) {
                futureTask.get();
            }
        }
        //等待時間不夠,ERROR - 紅石大帝點兵,應到1000人,實到:663
        //TimeUnit.MILLISECONDS.sleep(1);
        //ERROR - 紅石大帝點兵,應到1000人,實到:1000
        //TimeUnit.MILLISECONDS.sleep(500);
        long end = System.currentTimeMillis();
        LOGGER.error("紅石大帝點兵,應到" + testNum + "人,實到:" + redStone.getTotalSoldier());
        LOGGER.error("紅石大帝點兵耗時:" + (end - start));
        //poolExecutor.shutdown();
    }
}

Soldier類不變。

輸出:

    /**
     * 測試基數:int testNum = 100000;
     * 使用執行緒池耗時:ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(100, 200, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
     * 普通synchronized 方法:
     * ERROR - 紅石大帝點兵,應到100000人,實到:100000
     * ERROR - 紅石大帝點兵耗時:118
     *
     *AtomicLong:
     *ERROR - 紅石大帝點兵,應到100000人,實到:99905
     * ERROR - 紅石大帝點兵耗時:74
     *
     * 使用new Thread 建立執行緒:
     *普通synchronized 方法:
     * ERROR - 紅石大帝點兵,應到100000人,實到:100000
     * ERROR - 紅石大帝點兵耗時:10959
     *
     * AtomicLong:
     *ERROR - 紅石大帝點兵,應到100000人,實到:100000
     * ERROR - 紅石大帝點兵耗時:10392
     *
     * 結論:AtomicLong確實會比普通synchronized效能更高,JNI本地的CAS效能遠超synchronized關鍵字,
     * 不過synchronized也是挺快的現在,最讓人經驗的是執行緒池如果運用得當,效率是普通建立執行緒的百倍。
     */

AtomicBoolean

AtomicIntegerArray

public class AtomicIntegerArrayTest {
    private static final Logger LOGGER = LoggerFactory.getLogger(AtomicIntegerArrayTest.class);
    private static AtomicIntegerArray foodStore = new AtomicIntegerArray(3);

    public static void main(String[] args) throws InterruptedException {
        LOGGER.error("從軍每人分糧2斛");
        for (int i = 0; i < foodStore.length(); i++) {
            foodStore.set(i,2);
        }

        Thread supplyOfficer = new Thread(new DecrementSupply());
        Thread bossCao = new Thread(new RewardSupply());
        supplyOfficer.start();
        bossCao.start();
        supplyOfficer.join();
        bossCao.join();
        LOGGER.error("如今分糧情況:"+foodStore.toString());
    }


    public static class DecrementSupply implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < foodStore.length(); i++) {
                int decrement = foodStore.decrementAndGet(i);
                LOGGER.error("曹老闆要求小斛分糧,士兵:"+i+"現在分糧:"+decrement);
            }
        }
    }

    public static class RewardSupply implements Runnable{

        @Override
        public void run() {
            LOGGER.error("汝妻子我養之,汝無慮也!");
            LOGGER.error("軍需官擅自小斛分糧,已被我祭旗,所有小斛分糧的將士們每天分糧3斛!");
            for (int i = 0; i < foodStore.length(); i++) {
                boolean decrement = foodStore.compareAndSet(i,1,3);
                LOGGER.error("曹老闆要求小斛分糧,士兵:"+i+"現在分糧:"+foodStore.get(i));
            }
        }
    }
}

輸出:

ERROR - 從軍每人分糧2斛
ERROR - 曹老闆要求小斛分糧,士兵:0現在分糧:1
ERROR - 曹老闆要求小斛分糧,士兵:1現在分糧:1
ERROR - 曹老闆要求小斛分糧,士兵:2現在分糧:1
ERROR - 汝妻子我養之,汝無慮也!
ERROR - 軍需官擅自小斛分糧,已被我祭旗,所有小斛分糧的將士們每天分糧3斛!
ERROR - 曹老闆要求小斛分糧,士兵:0現在分糧:3
ERROR - 曹老闆要求小斛分糧,士兵:1現在分糧:3
ERROR - 曹老闆要求小斛分糧,士兵:2現在分糧:3
ERROR - 如今分糧情況:[3, 3, 3]

AtomicLongArray

java.util.concurrent.atomic.AtomicLongArray類提供了可以原子讀取和寫入的底層long型別陣列的操作,並且還包含高階原子操作。 AtomicLongArray支援對基礎long型別陣列變數的原子操作。

AtomicLongArray類方法列表。

序號 方法 描述
1 public long addAndGet(int i, long delta) 原子地將給定的值新增到索引i的元素。
2 public boolean compareAndSet(int i, long expect, long update) 如果當前值期望值,則將位置i處的元素原子設定為給定的更新值。
3 public long decrementAndGet(int i) 索引i處的元素原子並自減1。
4 public long get(int i) 獲取位置i的當前值。
5 public long getAndAdd(int i, long delta) 原子地將給定的值新增到索引i的元素。
6 public long getAndDecrement(int i) 索引i處的元素原子並自減1,並返回舊值。
7 public long getAndIncrement(int i) 將位置i處的元素原子設定為給定值,並返回舊值。
8 public long getAndSet(int i, long newValue) 將位置i處的元素原子設定為給定值,並返回舊值。
9 public long incrementAndGet(long i) 在索引i處以原子方式自增元素。
10 public void lazySet(int i, long newValue) 最終將位置i處的元素設定為給定值。
11 public int length() 返回陣列的長度。
12 public void set(int i, long newValue) 將位置i處的元素設定為給定值。
13 public String toString() 返回陣列的當前值的String表示形式。
14 public boolean weakCompareAndSet(int i, int expect, long update) 如果當前值
期望值,則將位置i處的元素原子設定為給定的更新值。

AtomicReferenceArray

java.util.concurrent.atomic.AtomicReferenceArray類提供了可以原子讀取和寫入的底層引用陣列的操作,並且還包含高階原子操作。 AtomicReferenceArray支援對底層引用陣列變數的原子操作。

AtomicReferenceArray類方法:

序列 方法 描述
1 public boolean compareAndSet(int i, E expect, E update) 如果當前值期望值,則將位置i處的元素原子設定為給定的更新值。
2 public E get(int i) 獲取位置i的當前值。
3 public E getAndSet(int i, E newValue) 將位置i處的元素原子設定為給定值,並返回舊值。
4 public void lazySet(int i, E newValue) 最終將位置i處的元素設定為給定值。
5 public int length() 返回陣列的長度。
6 public void set(int i, E newValue) 將位置i處的元素設定為給定值。
7 public String toString() 返回陣列的當前值的String表示形式。
8 public boolean weakCompareAndSet(int i, E expect, E update) 如果當前值
期望值,則將位置i處的元素原子設定為給定的更新值。

實用類例項

ThreadLocal

ThreadLocal類用於建立只能由同一個執行緒讀取和寫入的執行緒區域性變數。 例如,如果兩個執行緒正在訪問引用相同threadLocal變數的程式碼,那麼每個執行緒都不會看到任何其他執行緒操作完成的執行緒變數。

ThreadLocal類中可用的重要方法的列表:

編號 方法 描述
1 public T get() 返回當前執行緒的執行緒區域性變數的副本中的值。
2 protected T initialValue() 返回此執行緒區域性變數的當前執行緒的“初始值”。
3 public void remove() 刪除此執行緒區域性變數的當前執行緒的值。
4 public void set(T value) 將當前執行緒的執行緒區域性變數的副本設定為指定的值。

示例:

public class FlyDragonRunnable implements Runnable {
    private static final Logger logger = LoggerFactory.getLogger(FlyDragonRunnable.class);
    int count = 0;
    ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    @Override
    public void run() {
        if (threadLocal.get() != null) {
            logger.error("風之精靈呀,幻化成守護的龍吧!" + threadLocal.get() + 1);
        } else {
            threadLocal.set(1);
            logger.error("風之精靈呀,幻化成守護的龍吧! :" + threadLocal.get());
        }
        count++;
        logger.error("風之幻龍數:" + count);
    }
}

    public static void main(String[] args) throws InterruptedException {
        FlyDragonRunnable flyDragonRunnable = new FlyDragonRunnable();
        LOGGER.error("雷葛召喚守護3條風龍!");
        for (int i = 0; i < 3; i++) {
            Thread flyDragon = new Thread(flyDragonRunnable);
            flyDragon.start();
            flyDragon.join();
        }
    }

輸出:

ERROR - 雷葛召喚守護3條風龍!
ERROR - 風之精靈呀,幻化成守護的龍吧! :1
ERROR - 風之幻龍數:1
ERROR - 風之精靈呀,幻化成守護的龍吧! :1
ERROR - 風之幻龍數:2
ERROR - 風之精靈呀,幻化成守護的龍吧! :1
ERROR - 風之幻龍數:3

ThreadLocalRandom

java.util.concurrent.ThreadLocalRandom是從jdk 1.7開始引入的實用程式類,當需要多個執行緒或ForkJoinTasks來生成隨機數時很有用。 它提高了效能,並且比Math.random()方法佔用更少的資源。

ThreadLocalRandom方法

以下是ThreadLocalRandom類中可用的重要方法的列表。

編號 方法 說明
1 public static ThreadLocalRandom current() 返回當前執行緒的ThreadLocalRandom。
2 protected int next(int bits) 生成下一個偽隨機數。
3 public double nextDouble(double n) 返回偽隨機,均勻分佈在0(含)和指定值(獨佔)之間的double值。
4 public double nextDouble(double least, double bound) 返回在給定的least值(包括)和bound(不包括)之間的偽隨機均勻分佈的值。
5 public int nextInt(int least, int bound) 返回在給定的least值(包括)和bound(不包括)之間的偽隨機均勻分佈的整數值。
6 public long nextLong(long n) 返回偽隨機均勻分佈的值在0(含)和指定值(不包括)之間的長整數值。
7 public long nextLong(long least, long bound) 返回在給定的最小值(包括)和bound(不包括)之間的偽隨機均勻分佈的長整數值。
8 public void setSeed(long seed) 設定偽隨機的種子值,丟擲UnsupportedOperationException異常。

Random 之所以在多執行緒環境中效能不高的原因是多個執行緒共享同一個 Random 例項並進行爭奪。 ThreadLocalRandom 結合了 Random 和 ThreadLocal 類,並被隔離在當前執行緒中。因此它通過避免任何對 Random 物件的併發訪問,從而在多執行緒環境中實現了更好的效能。

不同於 Random, ThreadLocalRandom 明確的不支援設定隨機種子。 它重寫了 Random 的 setSeed(long seed) 方法並直接丟擲了 UnsupportedOperationException 異常。

在多執行緒下使用 ThreadLocalRandom 產生隨機數時,直接使用 ThreadLocalRandom.current().int()

ThreadLocalRandom參考

Locks(鎖 )

Lock ——ReentrantLock

java.util.concurrent.locks.Lock介面用作執行緒同步機制,類似於同步塊。新的鎖定機制更靈活,提供比同步塊更多的選項。 鎖和同步塊之間的主要區別如下:

  • 序列的保證 - 同步塊不提供對等待執行緒進行訪問的序列的任何保證,但Lock介面處理它。
  • 無超時,如果未授予鎖,則同步塊沒有超時選項。Lock介面提供了這樣的選項。
  • 單一方法同步塊必須完全包含在單個方法中,而Lock介面的方法lock()和unlock()可以以不同的方式呼叫。

Lock類中的方法

以下是Lock類中可用的重要方法的列表。

編號 方法 描述說明
1 public void lock() 獲得鎖
2 public void lockInterruptibly() 獲取鎖定,除非當前執行緒中斷
3 public Condition newCondition() 返回繫結到此Lock例項的新Condition例項
4 public boolean tryLock() 只有在呼叫時才可以獲得鎖
5 public boolean tryLock(long time, TimeUnit unit) 如果在給定的等待時間內自由,並且當前執行緒未被中斷,則獲取該鎖。
6 public void unlock() 釋放鎖

ReentrantLock類是Lock介面的一個實現。 ReentrantLock類允許執行緒鎖定方法,即使它已經具有其他方法鎖。

ReentranLock和synchronize的區別

①都是獨佔鎖,執行緒阻塞同步

ReentrantLock和synchronized都是加鎖式同步,當一個執行緒獲取了物件鎖後,其它要進入同步塊的執行緒就必須阻塞在同步塊外等待。執行緒的阻塞和喚醒需要作業系統在使用者態和核心態之間切換,所以,ReentrantLock和synchronized都是代價比較高的。

②實現方式不同,synchronized的鎖機制是由jvm實現,ReentrantLock是api層面的鎖。

synchronized是java語言的關鍵字,它的鎖機制是由jvm實現的,是原生語法層面上的互斥,最底層是mutex。而ReentrantLock則是JDK1.5之後,提供的api層面 的鎖,需要在程式碼中顯示呼叫lock、unlock等方法來完成。

所以,從便利性來說,synchronized使用起來更簡單一些。但是從靈活度來說,ReentrantLock更靈活,可控性也更強,可實現更細粒度的鎖。但是,在使用ReentrantLock時,一定要注意lock和unlock的匹配和順序,否則就可能造成死鎖。常見的方案是把unlock放在異常處理的finally語句塊中。

③效能效率相差不多

人們很容易被大眾化的觀點所誤導,認為synchronized的效率會比ReentrantLock差很多。但是事實上,synchronized在JDK的發展過程中,經過了不斷優化,比如引入了偏向鎖,輕量級鎖,鎖升級機制等,目前,已經和ReentrantLock的效率相差不多了。如果沒有特殊的場景,推薦使用synchronized,因為它使用起來比較簡單,且不會造成死鎖。

④是否公平鎖,synchronized是非公平鎖,ReentrantLock可以實現公平鎖和非公平鎖。

排隊等廁所,廁所門上有把鎖。裡面的人用完出來,把鑰匙給隊伍最前面的人,這就是公平鎖。如果裡面的人用完出來,把鑰匙直接扔地上,誰搶上算誰的,這就是非公平鎖。

synchronized是非公平鎖,並且它無法實現公平鎖。要實現公平鎖,可以通過ReentrantLock來實現。ReentrantLock預設是非公平鎖,通過new ReentrantLock(true)可以用來構造一個公平鎖。

⑤都是可重入鎖

一個執行緒可以對某個資源重複加鎖,稱之為可重入鎖。這個情形很常見於遞迴。如果鎖不可重入,就有可能會發生如下情況:

A執行緒獲取方法B的鎖,在方法B中,有程式碼遞迴呼叫了自己。於是,A執行緒需要在方法B中再次獲取B的鎖。如果鎖不可重入,A就會發現,方法B上已經有鎖,A就進入了等待。但事實上,給B加鎖的就是A自己。自己一直在等待自己,豈不是可笑?

synchronized就是一把可重入鎖。當然了,使用ReentrantLock也可以實現可重入鎖。

⑥synchronized等待不可響應中斷,ReentrantLock等待可響應中斷。

等待可中斷是使用ReentrantLock時,可以實現的一個機制。當某個執行緒等待鎖過長時間時,程式可以通過lockInterruptibly方法來使當前執行緒中斷等待,轉去執行其它的執行緒。

⑦ReentrantLock可實現執行緒分組喚醒,synchronized不能。

有些場景下,我們可能不希望喚醒所有的執行緒,而是喚醒部分執行緒。這種方式在synchronized下是無法實現的。但是,ReentrantLock通過提供一個Condition類,可以同時繫結多個物件,以此,來實現執行緒的分組喚醒。

推薦寫多執行緒程式碼的小口訣:

  1. 高內聚/低耦合前提下,執行緒—— 操作——資源類 (高內聚/低耦合)
  2. 判斷/幹活/通知
  3. 多執行緒互動中,必須要防止多執行緒的虛假喚醒,也即(判斷只用while,不能用if)
  4. 一定要注意,標誌位的修改更新.

示例:

public class MercenaryTask {
    private static final Logger logger = LoggerFactory.getLogger(MercenaryTask.class);
    private final Lock lock = new ReentrantLock();

    public void adoptTask() throws InterruptedException {
        lock.lock();
        try {
            int level = ThreadLocalRandom.current().nextInt(1, 4);
            logger.error(Thread.currentThread().getName()+"領取了一個"+level+"級別任務!");
            TimeUnit.SECONDS.sleep(level);
        } finally {
            logger.error(Thread.currentThread().getName()+"接收了任務成功!");
            lock.unlock();
        }
    }
}

public class MercenaryRunnable implements Runnable {

    private MercenaryTask mercenaryTask;

    public MercenaryRunnable(MercenaryTask mercenaryTask) {
        this.mercenaryTask = mercenaryTask;
    }

    @Override
    public void run() {
        try {
            mercenaryTask.adoptTask();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

測試類:

MercenaryRunnable mercenaryRunnable = new MercenaryRunnable(new MercenaryTask());
for (int i = 0; i < 3; i++) {
    Thread mercenary = new Thread(mercenaryRunnable,"mercenary "+i);
    mercenary.start();
}

輸出:

ERROR - mercenary 0領取了一個2級別任務!
ERROR - mercenary 0接收了任務成功!
ERROR - mercenary 1領取了一個3級別任務!
ERROR - mercenary 1接收了任務成功!
ERROR - mercenary 2領取了一個3級別任務!
ERROR - mercenary 2接收了任務成功!

Condition實現通知 :

class ShareResource
{
    private int number = 1;//1:A 2:B 3:C
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print(int totalLoopNumber)
    {
        lock.lock();
        try
        {
            //1 判斷
            while(number != 1 && Thread.currentThread().getName().equals("A"))
            {
                //A 就要停止
                c1.await();
            }
            while(number != 2 && Thread.currentThread().getName().equals("B"))
            {
                //B 就要停止
                c2.await();
            }
            while(number != 3 && Thread.currentThread().getName().equals("C"))
            {
                //C 就要停止
                c3.await();
            }
            //2 幹活
            System.out.println(Thread.currentThread().getName()+"\t"+"\t totalLoopNumber: "+totalLoopNumber);
            //3 通知
            if (Thread.currentThread().getName().equals("A")){
                number = 2;
                c2.signal();
            }else if (Thread.currentThread().getName().equals("B")){
                number = 3;
                c3.signal();
            }else {
                number = 1;
                c1.signal();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class AutoBuildVehicle {
    public static void main(final String[] args) {
        final ShareResource shareResource = new ShareResource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    shareResource.print(5);
                }
            }
        },"A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    shareResource.print(10);
                }
            }
        },"B").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    shareResource.print(15);
                }
            }
        },"C").start();
    }
}

ReadWriteLock ——ReentrantReadWriteLock

java.util.concurrent.locks.ReadWriteLock介面允許一次讀取多個執行緒,但一次只能寫入一個執行緒。

  • 讀鎖 - 如果沒有執行緒鎖定ReadWriteLock進行寫入,則多執行緒可以訪問讀鎖。
  • 寫鎖 - 如果沒有執行緒正在讀或寫,那麼一個執行緒可以訪問寫鎖。

ReentrantReadWriteLock方法:

編號 方法 描述
1 public Lock readLock() 返回用於讀的鎖。
2 public Lock writeLock() 返回用於寫的鎖。

ReentrantReadWriteLock的兩把鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖,描述如下:

執行緒進入讀鎖的前提條件:

沒有其他執行緒的寫鎖,

沒有寫請求或者有寫請求,但呼叫執行緒和持有鎖的執行緒是同一個。

執行緒進入寫鎖的前提條件:

沒有其他執行緒的讀鎖

沒有其他執行緒的寫鎖

讀寫鎖有以下三個重要的特性:

①公平選擇性:支援非公平(預設)和公平的鎖獲取方式,吞吐量還是非公平優於公平。

②重進入:讀鎖和寫鎖都支援執行緒重進入。

③鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。

示例:對共享資源的多讀少寫情況:

class MyCache{
    /*
    “volatile在Java併發程式設計中常用於保持記憶體可見性和防止指令重排序。
    記憶體可見性(MemoryVisibility):所有執行緒都能看到共享記憶體的最新狀態
    防止指令重排:在基於偏序關係的Happens-Before記憶體模型中,指令重排技術大大提高了程式執行效率
     */
    private volatile Map<String,Object> map = new HashMap<>();
    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void set(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"嘗試獲取寫鎖!");
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"開始寫");
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"寫完了:"+key);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"嘗試獲取讀鎖!");
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"開始讀");
            Object re = map.get(key);
            System.out.println(Thread.currentThread().getName()+"讀完了:"+re);
        } finally {
            rwLock.readLock().unlock();
        }
    }


}
public class ReadWriteLockTest{
    public static void main(String[] args) {
        final MyCache myCache = new MyCache();
        for (int i = 0; i < 3; i++) {
            final int tempI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myCache.set(tempI+"",tempI);
                }
            },"A"+String.valueOf(i)).start();
        }

        for (int i = 0; i < 3; i++) {
            final int tempI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myCache.get(tempI+"");
                }
            },"A"+String.valueOf(i)).start();
        }
    }
}

輸出:

A1嘗試獲取寫鎖!
A0嘗試獲取寫鎖!
A1開始寫
A2嘗試獲取寫鎖!
A1寫完了:1
A0嘗試獲取讀鎖!
A0開始寫
A0寫完了:0
A2開始寫
A1嘗試獲取讀鎖!
A2寫完了:2
A2嘗試獲取讀鎖!
A0開始讀
A0讀完了:0
A2開始讀
A1開始讀
A2讀完了:2
A1讀完了:1

Condition

java.util.concurrent.locks.Condition介面提供一個執行緒掛起執行的能力,直到給定的條件為真。 Condition物件必須繫結到Lock,並使用newCondition()方法獲取物件。

Condition類的方法

以下是Condition類中可用的重要方法的列表。

序號 方法名稱 描述
1 public void await() 使當前執行緒等待,直到發出訊號或中斷訊號。
2 public boolean await(long time, TimeUnit unit) 使當前執行緒等待直到發出訊號或中斷,或指定的等待時間過去。
3 public long awaitNanos(long nanosTimeout) 使當前執行緒等待直到發出訊號或中斷,或指定的等待時間過去。
4 public long awaitUninterruptibly() 使當前執行緒等待直到發出訊號。
5 public long awaitUntil() 使當前執行緒等待直到發出訊號或中斷,或者指定的最後期限過去。
6 public void signal() 喚醒一個等待執行緒。
7 public void signalAll() 喚醒所有等待執行緒。

示例:

//待補充。

Collections

LinkedBlockingQueue

BlockingQueue

java.util.concurrent.BlockingQueue介面是Queue介面的子介面,另外還支援諸如在檢索元素之前等待佇列變為非空的操作,並在儲存元素之前等待佇列中的空間變得可用 。

BlockingQueue介面中的方法

序號 方法 描述
1 boolean add(E e) 將指定的元素插入到此佇列中,如果可以立即執行此操作,而不會違反容量限制,在成功時返回true,並且如果當前沒有空間可用,則丟擲IllegalStateException。
2 boolean contains(Object o) 如果此佇列包含指定的元素,則返回true。
3 int drainTo(Collection<? super E> c) 從該佇列中刪除所有可用的元素,並將它們新增到給定的集合中。
4 int drainTo(Collection<? super E> c, int maxElements) 最多從該佇列中刪除給定數量的可用元素,並將它們新增到給定的集合中。
5 boolean offer(E e) 將指定的元素插入到此佇列中,如果可以立即執行此操作而不違反容量限制,則成功返回true,如果當前沒有空間可用,則返回false。
6 boolean offer(E e, long timeout, TimeUnit unit) 將指定的元素插入到此佇列中,等待指定的等待時間(如有必要)才能使空間變得可用。
7 E poll(long timeout, TimeUnit unit) 檢索並刪除此佇列的頭,等待指定的等待時間(如有必要)使元素變為可用。
8 void put(E e) 將指定的元素插入到此佇列中,等待空間/容量可用。
9 int remainingCapacity() 返回此佇列可理想地(在沒有記憶體或資源約束的情況下)接受而不阻止的附加元素數,如果沒有內在限制則返回Integer.MAX_VALUE。
10 boolean remove(Object o) 從該佇列中刪除指定元素的單個例項(如果存在)。
11 E take() 檢索並刪除此佇列的頭,如有必要,等待元素可用。

示例:

public class Producer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 3; i++) {
                int nextInt = ThreadLocalRandom.current().nextInt(0,10);
                //將指定的元素插入到此佇列中,等待空間/容量可用。
                queue.put(nextInt);
                System.out.println("put:"+nextInt);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Consumer implements Runnable {

    private BlockingQueue<Integer> queue;

    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 3; i++) {
                //檢索並刪除此佇列的頭,如有必要,等待元素可用。
                System.out.println("take:" +queue.take());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();

輸出:

put:6
put:1
put:3
take:6
take:1
take:3

ConcurrentMap

java.util.concurrent.ConcurrentMap介面是Map介面的子介面,支援底層Map變數上的原子操作。

ConcurrentMap介面中的方法

序號 方法 描述
1 default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 嘗試計算指定鍵及其當前對映值的對映(如果沒有當前對映,則為null)。
2 default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) 如果指定的鍵尚未與值相關聯(或對映到null),則嘗試使用給定的對映函式計算其值,並將其輸入到此對映中,除非為null。
3 default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) 如果指定鍵的值存在且非空,則嘗試計算給定鍵及其當前對映值的新對映。
4 default void forEach(BiConsumer<? super K,? super V> action) 對此對映中的每個條目執行給定的操作,直到所有條目都被處理或操作引發異常。
5 default V getOrDefault(Object key, V defaultValue) 返回指定鍵對映到的值,如果此對映不包含該鍵的對映,則返回defaultValue。
6 default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) 如果指定的鍵尚未與值相關聯或與null相關聯,則將其與給定的非空值相關聯。
7 V putIfAbsent(K key, V value) 如果指定的鍵尚未與值相關聯,請將其與給定值相關聯。
8 boolean remove(Object key, Object value) 僅噹噹前對映到給定值時才刪除鍵的條目。
9 V replace(K key, V value) 僅噹噹前對映到某個值時才替換該項的條目。
10 boolean replace(K key, V oldValue, V newValue) 僅噹噹前對映到給定值時才替換鍵的條目。
11 default void replaceAll(BiFunction<? super K,? super V,? extends V> function) 將每個條目的值替換為對該條目呼叫給定函式的結果,直到所有條目都被處理或該函式丟擲異常。

示例:

    public static void main(final String[] arguments){

        Map<String,String> map = new ConcurrentHashMap<String, String>();

        map.put("1", "One");
        map.put("2", "Two");
        map.put("3", "Three");
        map.put("5", "Five");
        map.put("6", "Six");

        System.out.println("Initi