Java并發(fā)編程-JDK并發(fā)包


參考資料:《Java高并發(fā)程序設(shè)計(jì)》


1.同步控制

1.擴(kuò)展了synchronized功能的:重入鎖

1.簡介

  • 使用示例:
import java.util.concurrent.locks.ReentrantLock;

public class Test {
    // 聲明鎖
    private static ReentrantLock lock = new ReentrantLock();
    private static int i = 0;

    private static final Runnable runnable = () -> {
        for (int j = 0; j < 100000; j++) {
            // 加鎖
            lock.lock();
            try {
                i++;
            } finally {
                // 釋放鎖
                lock.unlock();
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
  • 重入鎖可以完全替代synchronized關(guān)鍵字。
  • 與synchronized相比,重入鎖有著明顯的操作過程。開發(fā)人員必須手動(dòng)指定何時(shí)加鎖哗总,何時(shí)釋放鎖琳轿。也正因?yàn)槿绱嘶Ы模厝腈i對邏輯控制的靈活性要遠(yuǎn)好于synchronized剔猿。
  • 需要注意的是,在退出臨界區(qū)時(shí),必須記得釋放鎖入蛆,否則响蓉,其他線程就沒有機(jī)會再訪問臨界區(qū)了。
  • 重入鎖之所以有重入兩字安寺,是因?yàn)?code>這種鎖是可以被同一個(gè)線程反復(fù)進(jìn)入的厕妖。示例中的核心代碼可寫成下面的形式:
lock.lock();
lock.lock();
try {
    i++;
} finally {
    lock.unlock();
    lock.unlock();
}
  • 可重入的目的是為了避免同一個(gè)線程在第2次獲取鎖的時(shí)候和自己產(chǎn)生死鎖。
  • 需要注意的是:如果一個(gè)線程多次獲得鎖挑庶,那么在釋放鎖的時(shí)候言秸,也必須釋放相同的次數(shù)。
    1. 如果釋放鎖的次數(shù)多迎捺,那么會得到一個(gè)IllegalMonitorStateException異常举畸。
    2. 如果加鎖的次數(shù)多,那么相當(dāng)于線程還持有這個(gè)鎖凳枝,其他線程仍無法進(jìn)入臨界區(qū)抄沮。

2.高級功能

1.中斷

  • 對于synchronized來說,如果一個(gè)線程在等待鎖岖瑰,那么結(jié)果只有兩種情況:
    1. 獲得這把鎖繼續(xù)執(zhí)行
    2. 保持等待
  • 而重入鎖提供了另外一種可能:線程可以被中斷叛买。即在等待鎖的過程中,程序可以根據(jù)需要取消對鎖的請求蹋订。
  • 下面的代碼產(chǎn)生了一個(gè)死鎖率挣,但得益于鎖中斷,可以輕易地解決這個(gè)死鎖:
import java.util.concurrent.locks.ReentrantLock;
import lombok.AllArgsConstructor;

public class Test {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    @AllArgsConstructor
    private static class IntLock implements Runnable {
        private int lock;

        @Override
        public void run() {
            try {
                if (lock == 1) {
                    lock1.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        System.out.println("interrupted in sleep");
                    }
                    lock2.lockInterruptibly();
                } else {
                    lock2.lockInterruptibly();
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        System.out.println("interrupted in sleep");
                    }
                    lock1.lockInterruptibly();
                }
            } catch (InterruptedException e) {
                System.out.println("interrupted in business");
            } finally {
                if (lock1.isHeldByCurrentThread()) {
                    lock1.unlock();
                }
                if (lock2.isHeldByCurrentThread()) {
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getId() + ":線程退出");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new IntLock(1));
        Thread t2 = new Thread(new IntLock(2));
        t1.start();
        t2.start();
        Thread.sleep(1000);
        t2.interrupt();
    }
}

// interrupted in business
// 12:線程退出
// 11:線程退出
  • 上述代碼中對鎖的請求露戒,統(tǒng)一使用lockInterruptibly()方法椒功。這是一個(gè)可以對中斷進(jìn)行響應(yīng)的鎖申請動(dòng)作,即在等待鎖的過程中智什,可以響應(yīng)線程中斷动漾。

2.鎖申請等待限時(shí)

  • 可以使用tryLock()方法進(jìn)行一次限時(shí)的鎖申請:
public class Test {
    public static class TimeLock implements Runnable {

        public static ReentrantLock lock = new ReentrantLock();

        @Override
        public void run() {
            try {
                if (lock.tryLock(5, TimeUnit.SECONDS)) {
                    Thread.sleep(6000);
                } else {
                    System.out.println("get lock failed");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        TimeLock lock = new TimeLock();
        Thread t1 = new Thread(lock);
        Thread t2 = new Thread(lock);
        t1.start();
        t2.start();
    }
}

// get lock failed
  • 上述代碼中的tryLock()方法接收兩個(gè)參數(shù),一個(gè)表示等待時(shí)長荠锭,另一個(gè)表示計(jì)時(shí)單位旱眯。如果申請鎖成功,則返回true证九,否則返回false键思。
  • tryLock()方法也可以不帶參數(shù)直接運(yùn)行。這種情況下甫贯,如果鎖未被其他線程占用,則申請鎖成功看蚜,返回true叫搁,否則不會做任何等待,立即返回false。下面演示了這種用法:
public class Test {
    public static class TryLock implements Runnable {

        public static ReentrantLock lock1 = new ReentrantLock();
        public static ReentrantLock lock2 = new ReentrantLock();
        int lock;

        public TryLock(int lock) {
            this.lock = lock;
        }

        public void tryDoubleLock(ReentrantLock firstLock,
                                  ReentrantLock secondLock,
                                  String param, Consumer<String> consumer) {
            while (true) {
                if (firstLock.tryLock()) {
                    try {
                        if (secondLock.tryLock()) {
                            try {
                                consumer.accept(param);
                                return;
                            } finally {
                                secondLock.unlock();
                            }
                        }
                    } finally {
                        firstLock.unlock();
                    }
                }
            }
        }

        @Override
        public void run() {
            String param = ":My Job done";
            Consumer<String> consumer = str ->
                    System.out.println(Thread.currentThread().getId() + str);
            if (lock == 1) {
                tryDoubleLock(lock1, lock2, param, consumer);
            } else {
                tryDoubleLock(lock2, lock1, param, consumer);
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new TryLock(1));
        Thread t2 = new Thread(new TryLock(2));
        t1.start();
        t2.start();
    }
}

// 11:My Job done
// 10:My Job done
  • 先讓t1獲得lock1渴逻,再讓t2獲得lock2疾党,接著讓t1申請lock2,t2申請lock1惨奕。在一般情況下雪位,這會導(dǎo)致t1和t2相互等待,從而引起死鎖梨撞。但因?yàn)槭褂昧藅ryLock()雹洗,線程不會傻傻等待,而是不斷重試卧波,直到某個(gè)線程同時(shí)獲得lock1和lock2兩把鎖时肿。執(zhí)行上述代碼,可以發(fā)現(xiàn)兩個(gè)線程可以很快雙雙正常執(zhí)行完畢港粱。

3.公平鎖

  • 大多數(shù)情況下螃成,鎖的申請都是非公平的。舉個(gè)例子:線程t1首先請求了鎖A查坪,接著線程t2也請求了鎖A寸宏,那么鎖A可用時(shí),是t1獲得鎖還是t2獲得鎖是不確定的偿曙。系統(tǒng)會從這個(gè)鎖的等待隊(duì)列中隨機(jī)挑選一個(gè)氮凝,因此不能保證公平性,有可能會產(chǎn)生饑餓遥昧。
  • synchronized關(guān)鍵字進(jìn)行鎖控制所產(chǎn)生的鎖就是非公平的覆醇。
  • 重入鎖允許通過構(gòu)造函數(shù)對鎖的公平性進(jìn)行設(shè)置。fair為true表示公平炭臭。
public ReentrantLock(boolean fair)
  • 公平鎖的實(shí)現(xiàn)需要維護(hù)一個(gè)有序隊(duì)列永脓,因此鎖的實(shí)現(xiàn)成本高,性能也非常底下鞋仍。因此默認(rèn)情況下鎖是非公平的常摧。如果不是特殊的需求,也不需要公平鎖威创。下面的代碼演示了公平鎖的使用:
public class Test {
    public static class FairLock implements Runnable {

        public static ReentrantLock fairLock = new ReentrantLock(true);

        @Override
        public void run() {
            while (true) {
                try {
                    fairLock.lock();
                    System.out.println(Thread.currentThread().getName() + "獲得鎖");
                } finally {
                    fairLock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        FairLock r1 = new FairLock();
        Thread t1 = new Thread(r1, "Thread_t1");
        Thread t2 = new Thread(r1, "Thread_t2");
        t1.start();
        t2.start();
    }
}

// 程序運(yùn)行一段時(shí)間(穩(wěn)定后)的部分輸出:
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
  • 而如果把fairLock初始化時(shí)的參數(shù)改為false落午,那么根據(jù)系統(tǒng)的調(diào)度,一個(gè)線程會傾向于再次獲取已經(jīng)持有的鎖肚豺,這種分配方式是高效的溃斋,但無公平性可言。

3.總結(jié)

  • 對ReentrantLock的幾個(gè)重要方法整理如下:
  1. lock(): 獲得鎖吸申,如果鎖已經(jīng)被占用梗劫,則等待享甸。
  2. lockInterruptibly(): 獲得鎖,但優(yōu)先響應(yīng)中斷梳侨。
  3. tryLock(): 嘗試獲得鎖蛉威,如果成功返回true,失敗返回false走哺。該方法不等待蚯嫌,立即返回。
  4. tryLock(long time, TimeUnit unit): 在給定的時(shí)間內(nèi)嘗試獲得鎖丙躏。
  5. unlock(): 釋放鎖择示。
  • 從重入鎖的實(shí)現(xiàn)來看,主要包含三個(gè)要素:
  1. 原子狀態(tài)彼哼。原子狀態(tài)使用CAS操作(CompareAndSwap)來存儲當(dāng)前鎖的狀態(tài)对妄,判斷鎖是否已經(jīng)被別的線程持有。
  2. 等待隊(duì)列敢朱。所有沒有請求到鎖的線程剪菱,會進(jìn)入等待隊(duì)列進(jìn)行等待。待有線程釋放鎖后拴签,系統(tǒng)就能從等待隊(duì)列中喚醒一個(gè)線程孝常,繼續(xù)工作。
  3. 阻塞原語park()和unpark()蚓哩。這兩個(gè)方法用來掛起和恢復(fù)線程构灸。沒有得到鎖的線程將會被掛起。有關(guān)park()和unpark()可參考線程阻塞工具類LockSupport岸梨。

2.重入鎖的好搭檔:Condition條件

1.簡介

  • Condition的作用和Object.wait()喜颁、Object.notify()的作用是大致相同的。但是Condition和重入鎖相關(guān)聯(lián)曹阔,而后者是和synchronized關(guān)鍵字合作使用的半开。
  • 通過Lock接口(重入鎖就實(shí)現(xiàn)了該接口)的newCondition()方法可以生成一個(gè)與當(dāng)前重入鎖綁定的Condition實(shí)例。利用Condition實(shí)例就可以讓線程在合適的時(shí)間等待赃份,或在特定的時(shí)刻得到通知繼續(xù)執(zhí)行寂拆。

2.方法

  • await()
    調(diào)用該方法的前提是,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖抓韩,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException纠永。調(diào)用該方法外,當(dāng)前線程會釋放當(dāng)前已經(jīng)獲得的鎖(這一點(diǎn)與Object.wait方法一致)谒拴,并且等待其它線程調(diào)用該條件對象的signal()或者signalAll()方法(這一點(diǎn)與Object.notify()或Object.notifyAll()很像)尝江。或者在等待期間英上,當(dāng)前線程被中斷茂装,則wait()方法會拋出InterruptedException并清除當(dāng)前線程的中斷狀態(tài)怠蹂。

  • await(long time, TimeUnit unit)
    適用條件和行為與await()基本一致,唯一不同點(diǎn)在于少态,指定時(shí)間之內(nèi)沒有收到signal()或signalALL()信號或者線程中斷時(shí)該方法會返回false;其它情況返回true。

  • awaitNanos(long nanosTimeout)
    調(diào)用該方法的前提是易遣,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖彼妻,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException。nanosTimeout指定該方法等待信號的的最大時(shí)間(單位為納秒)豆茫。若指定時(shí)間內(nèi)收到signal()或signalALL()則返回nanosTimeout減去已經(jīng)等待的時(shí)間侨歉;若指定時(shí)間內(nèi)有其它線程中斷該線程,則拋出InterruptedException并清除當(dāng)前線程的中斷狀態(tài)揩魂;若指定時(shí)間內(nèi)未收到通知幽邓,則返回0或負(fù)數(shù)。

  • awaitUntil(Date deadline)
    適用條件與行為與awaitNanos(long nanosTimeout)完全一樣火脉,唯一不同點(diǎn)在于它不是等待指定時(shí)間牵舵,而是等待由參數(shù)指定的某一時(shí)刻。

  • awaitUninterruptibly()
    調(diào)用該方法的前提是倦挂,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖畸颅,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException。調(diào)用該方法后方援,結(jié)束等待的唯一方法是其它線程調(diào)用該條件對象的signal()或signalALL()方法没炒。等待過程中如果當(dāng)前線程被中斷,該方法仍然會繼續(xù)等待犯戏,同時(shí)保留該線程的中斷狀態(tài)送火。

  • signal()
    用于喚醒一個(gè)等待中的線程。類似于Object.notify()

  • signalAll()
    會喚醒所有在等待中的線程先匪。類似于Object.notifyAll()

  • 代碼演示:

public class Test {

    public static class ReenterLockCondition implements Runnable {

        public static ReentrantLock lock = new ReentrantLock();
        public static Condition condition = lock.newCondition();

        @Override
        public void run() {
            try {
                lock.lock(); // 申請并獲得鎖
                condition.await();  // 釋放鎖
                                    // 收到signal信號后重新申請鎖种吸、獲得鎖
                System.out.println("thread is going on");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock(); // 釋放鎖
            }
        }
    }

    public static void main(String[] args) throws Exception {
        new Thread(new ReenterLockCondition()).start();
        Thread.sleep(2000);
        ReenterLockCondition.lock.lock(); // 獲得鎖
        ReenterLockCondition.condition.signal(); // signal通知
        ReenterLockCondition.lock.unlock(); // 釋放鎖,謙讓給被喚醒的線程
    }
}

// thread is going on

3.允許多個(gè)線程同時(shí)訪問:信號量(Semaphore)

1.簡介

  • 從廣義上說胚鸯,信號量是對鎖的擴(kuò)展骨稿。無論是內(nèi)部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個(gè)線程訪問一個(gè)資源姜钳,而信號量卻可以指定多個(gè)線程坦冠,同時(shí)訪問某一個(gè)資源。
  • 信號量主要提供了以下構(gòu)造函數(shù)哥桥。在構(gòu)造信號量對象時(shí)辙浑,必須指定信號量的準(zhǔn)入數(shù),即同時(shí)能申請多少個(gè)許可拟糕。當(dāng)每個(gè)線程每次只申請一個(gè)許可時(shí)判呕,這就相當(dāng)于指定了同時(shí)有多少個(gè)線程可以訪問某一個(gè)資源倦踢。
public Semaphore(int permits) // 參數(shù)為信號量的準(zhǔn)入數(shù)
public Semaphore(int permits, boolean fair) // 第二個(gè)參數(shù)可以指定是否公平

2.方法

  • acquire()
    嘗試獲得一個(gè)準(zhǔn)入的許可。若無法獲得侠草,則線程會等待辱挥,直到有線程釋放一個(gè)許可或者當(dāng)前線程被中斷。

  • acquireUninterruptibly()
    和acquire()方法類似边涕,但是不響應(yīng)中斷晤碘。

  • tryAcquire()
    嘗試獲得一個(gè)許可,如果成功返回true功蜓,失敗返回false园爷,不會等待,立即返回式撼。

  • release()
    用于在線程訪問資源結(jié)束后童社,釋放一個(gè)許可,以使其他等待許可的線程可以進(jìn)行資源訪問著隆。

  • 代碼演示:

public class Test {

    public static class SemaphoreDemo implements Runnable {

        final Semaphore semaphore = new Semaphore(5);

        @Override
        public void run() {
            try {
                semaphore.acquire();
                Thread.sleep(2000);
                System.out.println(System.currentTimeMillis() + "-" +
                        Thread.currentThread().getId() + ":done!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(20);
        final SemaphoreDemo demo = new SemaphoreDemo();
        for (int i = 0; i < 20; i++) {
            exec.submit(demo);
        }
    }
}

// 從輸出可以觀察出系統(tǒng)以5個(gè)線程一組為單位扰楼,依次進(jìn)行輸出

4.讀寫分離鎖:ReadWriteLock

  • 讀寫分離鎖(簡稱“讀寫鎖”)可以有效地幫助 減少鎖競爭,以提升系統(tǒng)性能旅东。
  • 讀寫鎖允許多個(gè)線程同時(shí)讀灭抑,但考慮到數(shù)據(jù)完整性,寫寫操作和讀寫操作間依然是需要相互等待和持有鎖的抵代。讀寫鎖的訪問約束如下表:
-
非阻塞 阻塞
阻塞 阻塞
  • 在系統(tǒng)中腾节,如果讀操作次數(shù)遠(yuǎn)大于寫操作,那么讀寫鎖的功效就會非常明顯荤牍。例如下面的代碼:
public class Test {

    private static Lock lock = new ReentrantLock();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();
    private static CountDownLatch latch = new CountDownLatch(20);
    private int value;

    public Object handleRead(Lock lock) throws InterruptedException {
        try {
            lock.lock(); // 模擬讀操作
            Thread.sleep(1000); // 讀操作的耗時(shí)越多案腺,讀寫鎖的優(yōu)勢就越明顯
            return value;
        } finally {
            latch.countDown();
            lock.unlock();
        }
    }

    public void handleWrite(Lock lock, int index) throws InterruptedException {
        try {
            lock.lock(); // 模擬寫操作
            Thread.sleep(1000);
            value = index;
        } finally {
            latch.countDown();
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception {
        final Test demo = new Test();
        Runnable readRunnable = () -> {
            try {
                demo.handleRead(readLock);
                //demo.handleRead(lock); // 使用重入鎖而不是讀寫鎖
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Runnable writeRunnable = () -> {
            try {
                demo.handleWrite(writeLock, new Random().nextInt());
                //demo.handleRead(lock); // 使用重入鎖而不是讀寫鎖
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        long startTime = System.currentTimeMillis();
        for (int i = 0; i <= 18; i++) {
            new Thread(readRunnable).start();
        }
        for (int i = 19; i < 20; i++) {
            new Thread(writeRunnable).start();
        }
        latch.await();
        System.out.println("used time: " + (System.currentTimeMillis() - startTime));
    }
}

// 使用讀寫鎖
// used time: 2022
// 使用重入鎖
// used time: 20009
  • 通過運(yùn)行結(jié)果可以看出,使用讀寫鎖程序大約2秒多就能結(jié)束(寫線程直接是實(shí)際串行的)康吵。而使用重入鎖劈榨,所有的讀和寫線程之間都必須相互等待,整個(gè)程序的執(zhí)行時(shí)間將長達(dá)20余秒晦嵌。

5.倒計(jì)數(shù)器:CountDownLatch

  • 倒計(jì)數(shù)器通常用來控制線程等待同辣,它可以讓某一個(gè)線程等待直到倒計(jì)數(shù)結(jié)束,再執(zhí)行惭载。

  • 上一節(jié)介紹讀寫鎖統(tǒng)計(jì)用時(shí)時(shí)旱函,就是使用的倒計(jì)數(shù)器。此外倒計(jì)數(shù)器的一種典型場景就是火箭發(fā)射描滔。在火箭發(fā)射前棒妨,為確保萬無一失,往往會進(jìn)行各項(xiàng)設(shè)備儀器的檢查含长。只有等所有的檢查完畢后券腔,引擎才能點(diǎn)火伏穆。這種場景就非常適合使用CountDownLatch。它可以使點(diǎn)火線程等待所有檢查線程全部完工后纷纫,再執(zhí)行枕扫。示意圖如下:

    未命名文件(1).png-12.2kB
    未命名文件(1).png-12.2kB

  • 倒計(jì)時(shí)器的幾個(gè)關(guān)鍵方法

// 構(gòu)造函數(shù)接收一個(gè)整數(shù)作為參數(shù),即當(dāng)前這個(gè)倒計(jì)時(shí)器的計(jì)數(shù)個(gè)數(shù)
public CountDownLatch(int count)

// 計(jì)數(shù)減一
public void countDown()

// 使線程等待涛酗,直到倒計(jì)數(shù)完成
public void await()

6.循環(huán)柵欄:CyclicBarrier

  • CyclicBarrier與CountDownLatch類似铡原,也可實(shí)現(xiàn)線程間的 計(jì)數(shù)等待 ,但功能更加強(qiáng)大:這個(gè)計(jì)數(shù)器可以 反復(fù)使用商叹。
  • 循環(huán)柵欄的一個(gè)使用場景:比如司令下達(dá)命令,要求10個(gè)士兵一起去完成一項(xiàng)任務(wù)只泼。這時(shí)剖笙,就會要求10個(gè)士兵先集合報(bào)道,全部報(bào)道完畢后(第一次計(jì)數(shù)完成)请唱,再一起去執(zhí)行任務(wù)弥咪,當(dāng)10個(gè)士兵把自己的任務(wù)都完成了(第二次計(jì)數(shù)完成),那么司令再對外宣布:任務(wù)完成十绑!
  • CyclicBarrier有兩個(gè)主要的方法:
// parties:計(jì)數(shù)總數(shù)聚至,也就是參與的線程總數(shù)
// barrierAction:當(dāng)計(jì)數(shù)器完成一次計(jì)數(shù)后,系統(tǒng)要執(zhí)行的動(dòng)作
public CyclicBarrier(int parties, Runnable barrierAction)

// 等待一次計(jì)數(shù)完成
// 注意拋出的兩種異常(見下一節(jié)的代碼)
public int await() throws InterruptedException, BrokenBarrierException
  • 上面的的場景用代碼實(shí)現(xiàn)如下:
public class Test {

    private static final int SOLDIER_NUM = 10;
    private volatile static boolean allAssembledFlag = false;
    private static final Runnable barrierRun = () -> {
        if (allAssembledFlag) {
            System.out.println("司令:[士兵" + SOLDIER_NUM + "個(gè)本橙,任務(wù)完成扳躬!]");
        } else {
            System.out.println("司令:[士兵" + SOLDIER_NUM + "個(gè),集合完畢甚亭!]");
        }
    };

    public static class Soldier implements Runnable {
        private final CyclicBarrier cyclic;
        private String soldierName;

        Soldier(CyclicBarrier cyclic, String soldierName) {
            this.cyclic = cyclic;
            this.soldierName = soldierName;
        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldierName + ":任務(wù)完成");
        }

        @Override
        public void run() {
            try {
                // 等待所有士兵到齊
                cyclic.await();
                allAssembledFlag = true;
                doWork();
                // 等待所有士兵完成工作
                cyclic.await();
            } catch (InterruptedException e) {
                System.out.println("線程等待時(shí)被中斷——需要執(zhí)行響應(yīng)外部緊急事件的邏輯");
            } catch (BrokenBarrierException e) {
                System.out.println("當(dāng)前的CyclicBarrier已經(jīng)被損壞," +
                        "系統(tǒng)可能已經(jīng)無法等待所有線程到齊——需要執(zhí)行使等待線程就地解散的邏輯");
                // 例如當(dāng)前循環(huán)柵欄的計(jì)數(shù)總數(shù)為10,若有一個(gè)線程被中斷,
                // 那我們就會得到1個(gè)InterruptedException和9個(gè)BrokenBarrierException
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Thread[] allSoldier = new Thread[SOLDIER_NUM];
        CyclicBarrier cyclic = new CyclicBarrier(SOLDIER_NUM, barrierRun);
        // 設(shè)置屏障點(diǎn)贷币,主要是為了執(zhí)行這個(gè)方法
        System.out.println("集合隊(duì)伍!");
        for (int i = 0; i < SOLDIER_NUM; ++i) {
            System.out.println("士兵 " + i + " 報(bào)道亏狰!");
            allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
            allSoldier[i].start();
        }
    }

}

// 集合隊(duì)伍役纹!
// 士兵 0 報(bào)道!
// 士兵 1 報(bào)道暇唾!
// 士兵 2 報(bào)道促脉!
// 士兵 3 報(bào)道!
// 士兵 4 報(bào)道策州!
// 士兵 5 報(bào)道瘸味!
// 士兵 6 報(bào)道!
// 士兵 7 報(bào)道抽活!
// 士兵 8 報(bào)道硫戈!
// 士兵 9 報(bào)道!
// 司令:[士兵10個(gè)下硕,集合完畢丁逝!]
// 士兵 3:任務(wù)完成
// 士兵 5:任務(wù)完成
// 士兵 9:任務(wù)完成
// 士兵 7:任務(wù)完成
// 士兵 4:任務(wù)完成
// 士兵 1:任務(wù)完成
// 士兵 8:任務(wù)完成
// 士兵 0:任務(wù)完成
// 士兵 6:任務(wù)完成
// 士兵 2:任務(wù)完成
// 司令:[士兵10個(gè)汁胆,任務(wù)完成!]

7.線程阻塞工具類:LockSupport

  • LockSupport是一個(gè)線程阻塞工具霜幼,可以在線程內(nèi)任意位置讓線程阻塞嫩码。
  1. 和Thread.suspend()相比,它彌補(bǔ)了由于resume()在前發(fā)生罪既,導(dǎo)致線程無法繼續(xù)執(zhí)行的情況铸题。
  2. 和Object.wait()相比,它不需要先獲得某個(gè)對象的鎖琢感,也不會拋出InterruptedException異常丢间。
  • LockSupport的 靜態(tài)方法park() 可以阻塞當(dāng)前線程,類似的還有parkNanos()驹针、parkUntil()等方法烘挫,它們實(shí)現(xiàn)了一個(gè)限時(shí)的等待。
  • 之前提到的有關(guān) suspend()永久卡死線程 的例子柬甥,可用LockSupport重寫如下:
public class Test {

    private static final Object u = new Object();

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                try {
                    System.out.println("in " + getName());
                    Thread.sleep(1000);
                    LockSupport.park();
                    System.out.println("out " + getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ChangeObjectThread t1 = new ChangeObjectThread("t1");
        ChangeObjectThread t2 = new ChangeObjectThread("t2");
        t1.start();
        t2.start();
        LockSupport.unpark(t1);
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }

}

// in t1
// out t1
// in t2
// out t2
  • 這里只是將原來的suspend()和resume()方法用park()和unpark()方法做了替換饮六。并且為了確保unpark()在park()之前被調(diào)用,還讓線程多sleep了1秒苛蒲。但執(zhí)行這段代碼可以發(fā)現(xiàn)卤橄,它永遠(yuǎn)可以正常的結(jié)束,不會被永久性的掛起臂外。

  • LockSupport不會因unpark()先于park()執(zhí)行而被永久性掛起的原因窟扑,是因?yàn)樗褂昧祟愃?信號量 的機(jī)制。它為每一個(gè)線程準(zhǔn)備了一個(gè) 許可寄月,如果許可 可用辜膝,那么park()函數(shù)會立即返回,并且 消費(fèi)掉 這個(gè)許可(也就是將許可變?yōu)?不可用)漾肮,如果許可不可用厂抖,就會阻塞。而unpark()則可以使一個(gè)許可變?yōu)榭捎茫ǖ托盘柫坎煌氖强税茫S可不能累加忱辅,你不可能擁有超過一個(gè)許可,它永遠(yuǎn)只有一個(gè))谭溉。

  • 此外墙懂,處于park()掛起狀態(tài)的線程不會像suspend()那樣給出一個(gè)令人費(fèi)解的Runnable的狀態(tài)。它會非常明確地給出一個(gè)WAITING狀態(tài)扮念,而且還會標(biāo)注是park()引起的:

    1.png-312.7kB
    1.png-312.7kB

  • 這種標(biāo)注使得分析問題非常方便损搬。此外,如果使用park(Object)函數(shù),還可以為當(dāng)前線程設(shè)置一個(gè)阻塞對象巧勤。這個(gè)阻塞對象會出現(xiàn)在線程Dump中嵌灰,這樣分析問題就更方便了。例如將上述代碼中的park()改為park(this)颅悉,那么在線程Dump中沽瞭,可以看到如下信息:

    1.png-379.1kB
    1.png-379.1kB

  • 除了有定時(shí)阻塞的功能外,LockSupport.park()還支持中斷響應(yīng)剩瓶。但和其他接收中斷的函數(shù)不同驹溃,park()函數(shù)不會拋出InterruptedException異常。park()函數(shù)只會默默的返回延曙,但我們可以從Thread.interrupted()等方法獲得中斷標(biāo)記豌鹤。例如:

public class Test {

    private static final Object u = new Object();

    public static class ChangeObjectThread extends Thread {
        public ChangeObjectThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (u) {
                System.out.println("in " + getName());
                LockSupport.park();
                if (Thread.interrupted()) {
                    System.out.println(getName() + " 被中斷了");
                }
                System.out.println("out " + getName());
            }
        }
    }

    public static void main(String[] args) throws Exception {
        ChangeObjectThread t1 = new ChangeObjectThread("t1");
        ChangeObjectThread t2 = new ChangeObjectThread("t2");
        t1.start();
        Thread.sleep(100);
        t2.start();
        t1.interrupt();
        LockSupport.unpark(t2);
        t1.join();
        t2.join();
    }

}

// in t1
// t1 被中斷了
// out t1
// in t2
// out t2

end

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市枝缔,隨后出現(xiàn)的幾起案子傍药,更是在濱河造成了極大的恐慌,老刑警劉巖魂仍,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異拣挪,居然都是意外死亡擦酌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門菠劝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赊舶,“玉大人,你說我怎么就攤上這事赶诊×剑” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵舔痪,是天一觀的道長寓调。 經(jīng)常有香客問我,道長锄码,這世上最難降的妖魔是什么夺英? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮滋捶,結(jié)果婚禮上痛悯,老公的妹妹穿的比我還像新娘。我一直安慰自己重窟,他們只是感情好载萌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般扭仁。 火紅的嫁衣襯著肌膚如雪垮衷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天斋枢,我揣著相機(jī)與錄音帘靡,去河邊找鬼。 笑死瓤帚,一個(gè)胖子當(dāng)著我的面吹牛描姚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈次,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轩勘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了怯邪?” 一聲冷哼從身側(cè)響起绊寻,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎悬秉,沒想到半個(gè)月后澄步,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡和泌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年村缸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片武氓。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梯皿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出县恕,到底是詐尸還是另有隱情东羹,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布忠烛,位于F島的核電站属提,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏况木。R本人自食惡果不足惜垒拢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望火惊。 院中可真熱鬧求类,春花似錦、人聲如沸屹耐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至寿弱,卻和暖如春犯眠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背症革。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工筐咧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人噪矛。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓量蕊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親艇挨。 傳聞我的和親對象是個(gè)殘疾皇子残炮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容