《Java并發(fā)編程實(shí)戰(zhàn)》讀書筆記二:構(gòu)建線程安全

一岭粤、用組合來實(shí)現(xiàn)線性安全

1.設(shè)計(jì)線程安全的類

設(shè)計(jì)線程安全類的三個(gè)基本要素:
1. 找出構(gòu)成對(duì)象狀態(tài)的所有變量
2. 找出約束狀態(tài)變量的不變性條件
3. 建立對(duì)象狀態(tài)的并發(fā)訪問管理策略

要分析對(duì)象的狀態(tài),首先從對(duì)象的域開始。如果對(duì)象所有的域都是基本類型的變量皆愉,那么這些域?qū)?gòu)成對(duì)象的全部狀態(tài)今穿;如果對(duì)象的域中引用了其他對(duì)象,那么該對(duì)象的狀態(tài)將包含被引用的對(duì)象的域勉耀。

2.實(shí)例封閉

當(dāng)一個(gè)對(duì)象被封裝到另一個(gè)對(duì)象中盼铁,能夠訪問到被封裝對(duì)象的所有代碼路徑都是已知的粗蔚。通過將封閉機(jī)制和合適的加鎖策略結(jié)合,可以確保以線程安全的方式來使用非線程安全的對(duì)象饶火。

public class PersonSet{  
       private final Set<Person> mySet = new HashSet<Person>();  

       public sychronized void addPersion(Person p) {  
              mySet.add(p)  
         }  

       public sychronized boolean containsPerson(Person p) {  
              return mySet.contains(p);  
         }  
    }  

解析:
雖然HashSet 并非線程安全的鹏控,但是mySet是私有的不會(huì)逸出。唯一能訪問mySet的代碼是addPerson(),和containsPerson()肤寝。在執(zhí)行上他們都要獲的PersonSet 上的鎖当辐。PersonSet的狀態(tài)完全又它的內(nèi)置鎖保護(hù)。所以PersonSet是一個(gè)線程安全的類鲤看。

java 平臺(tái)的類庫有很多實(shí)例封閉的例子缘揪。比如一些基本的容器并非線程安全的,如ArrayList,HashMap义桂。類庫提供的包裝器方法找筝,Collections.synchronizedList(list)、Collections.synchronizedMap(m)只要這些包裝器對(duì)象擁有對(duì)被包裝容器對(duì)象的唯一引用(即把容器對(duì)象封裝在包裝器中)慷吊,非線程安全的類就可以在多線程中使用袖裕。

3.線程安全性的委托

class Counter {

    private AtomicInteger count = new AtomicInteger(0);

    private int inc(){
        return count.incrementAndGet();
    }
}

對(duì)于Counter來說,由于Counter只有一個(gè)域就是AtomicInteger溉瓶,而AtomicInteger又是線程安全的急鳄,所以很容易知道Counter是線程安全的。Counter把它的線程安全性交給了AtomicInteger來決定堰酿,也就說委托給了AtomicInteger來保證疾宏。

但是,當(dāng)委托的狀態(tài)變量超過1個(gè)時(shí)触创,就要看情況而言了坎藐。要看委托的狀態(tài)變量之間是否有某種聯(lián)系。如果委托的狀態(tài)域是彼此獨(dú)立的哼绑,那么不會(huì)影響組合的類的線程安全性顺饮。

class ListenerManager {

    private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();

    public void addKeyListener(KeyListener e){
        keyListeners.add(e);
    }

    public void addMouseListener(MouseListener e){
        mouseListeners.add(e);
    }

    public void removeKeyListener(KeyListener e){
        keyListeners.remove(e);
    }

    public void removeMouseListener(MouseListener e){
        mouseListeners.remove(e);
    }
}

對(duì)于ListenerManager來說,它把它的線程安全性委托給了keyListeners 和mouseListeners凌那,而這兩種狀態(tài)在類中不存在任何的耦合關(guān)系,因此他們組合而成的類也是線程安全的吟逝。(CopyOnWriteArrayList是一個(gè)線程安全的鏈表)

當(dāng)委托的多個(gè)狀態(tài)存在耦合關(guān)系時(shí)帽蝶,委托可能會(huì)失效!

class NumberRange {

    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i){
        //不安全的   先檢查后執(zhí)行
        if(i > upper.get()){
            throw new RuntimeException("最小值不能比最大值大");
        }
        lower.set(i);
    }

    public void setUpper(int i){
        //不安全的   先檢查后執(zhí)行
        if(i < lower.get()){
            throw new RuntimeException("最大值不能比最小值小");
        }
        upper.set(i);
    }
}

雖然lower和upper都是原子操作,但是由于在整個(gè)類中存在一個(gè)不變形條件——lower <= upper励稳,setLower和setUpper都是“先檢查后操作”的不安全操作佃乘,沒有采取足夠的加鎖機(jī)制來保證這兩個(gè)方法是一個(gè)原子操作(前面提到了,非原子性操作在多線程環(huán)境下是線程不安全的)驹尼。假設(shè)一個(gè)線程調(diào)用setLower(5)趣避,另一個(gè)線程調(diào)用setUpper(4),最終由于線程調(diào)度的順序新翎,有可能結(jié)果為(5,4)程帕。

如果一個(gè)類,僅僅靠委托狀態(tài)不足以維持線程安全性地啰,這種情況下愁拭,這個(gè)類必須提供自己的加鎖機(jī)制保證這些復(fù)合操作是原子操作!

4.客戶端加鎖機(jī)制實(shí)現(xiàn)線程安全

class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());
    ....
    public synchronized boolean putIfAbsent(E x){
        boolean absent = !list.contains(x);
        if(absent){
            list.add(x);
        }
        return absent;
    }
}

這種通過擴(kuò)展類亏吝,來實(shí)現(xiàn)我們想要的功能——如果沒有岭埠,則添加。我們很自然想到蔚鸥,在多線程環(huán)境下惜论,putIfAbsent不是一個(gè)原子操作,因此我們會(huì)通過加鎖來實(shí)現(xiàn)線程同步止喷,那么真的實(shí)現(xiàn)了線程安全嗎馆类?
但實(shí)際上,這里用了兩個(gè)不同的鎖启盛。
list是一個(gè)線程安全的鏈表蹦掐,list中使用的鎖的對(duì)象是List本身,而putIfAbsent中加的鎖的對(duì)象是ListHelper僵闯,使用了不同的鎖卧抗,意味著ListHelper相對(duì)于list來說,并不是原子操作鳖粟,也就有可能一個(gè)線程調(diào)用putIfAbsent操作時(shí)社裆,另一個(gè)線程調(diào)用其他list的其它方法。

下面是正確的加鎖方式:

class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public boolean putIfAbsent(E x){
        synchronized(list){
            boolean absent = !list.contains(x);
            if(absent){
                list.add(x);
            }
            return absent;
        }
    }
}

但是這種擴(kuò)展類的方式仍然不值得推薦向图,因?yàn)闀?huì)破壞同步策略的封裝性泳秀。

二、Java類庫的基礎(chǔ)并發(fā)構(gòu)建模塊

1. 同步容器類

同步容器類包括Vector和HashTable榄攀,以及使用Collections.synchronizedXxx(例如Collections.synchronizedList(new ArrayList< T>()))等工廠方法創(chuàng)建的同步類嗜傅。

這些類實(shí)現(xiàn)線程安全的方式是:把他們的狀態(tài)封裝起來,并對(duì)每一個(gè)共有方法都進(jìn)行同步檩赢,使得每次只有一個(gè)線程能訪問容器的狀態(tài)吕嘀。(從這個(gè)描述,可以看出,在高并發(fā)情況下可能效率是一個(gè)問題)

同步容器類存在的問題:
雖然同步容器類是線程安全的偶房,對(duì)于Vector和HashTable趁曼,在類中提供的操作都是原子操作的,在多線程環(huán)境下就可以放心使用Vector和HashTable的方法棕洋。但是在一些復(fù)合操作上還是要加鎖來實(shí)現(xiàn)同步挡闰,例如:迭代,條件運(yùn)算(若不存在則添加)掰盘。
例如:

public static Object getLast(Vector list){
     int lastIndex = list.size() - 1;
     return list.get(lastIndex);
}

對(duì)于這種非原子操作摄悯,必須加鎖達(dá)到線程同步

public static Object getLast(Vector list){
     synchronized(list){
          int lastIndex = list.size() - 1;
          return list.get(lastIndex);
     }
}

2.并發(fā)容器類

同步容器類將所有對(duì)容器狀態(tài)訪問都加了鎖,以實(shí)現(xiàn)線程安全庆杜,代價(jià)就是嚴(yán)重降低了并發(fā)性射众,當(dāng)多個(gè)線程競(jìng)爭(zhēng)容器的鎖時(shí),吞吐量嚴(yán)重降低晃财,而為了改善同步容器的性能叨橱,Java針對(duì)多個(gè)線程并發(fā)訪問,提供了并發(fā)容器類断盛。
例如:
ConcurrentMap罗洗,用來替代同步且基于散列的Map;
CopyOnWriteArrayList钢猛,用于在遍歷操作為主要操作的情況下代替同步的List伙菜。
Queue和BlockingQueue等。

ConcurrentHashMap
CooncurrentMap使用了一個(gè)更加細(xì)化的鎖機(jī)制, 名叫分離鎖. 這個(gè)機(jī)制允許更深層次的共享訪問. 任意數(shù)量的讀取線程可以并發(fā)的訪問Map, 讀者和寫者也可以并發(fā)的訪問, 并且有限數(shù)量的寫線程還可以并發(fā)修改Map, 結(jié)果是為并發(fā)帶來更高的吞吐量, 同時(shí)幾乎沒有損失單線程訪問的性能.

ConcurrentMap接口加入了對(duì)常見復(fù)合操作的支持, 比如”缺少即加入(putIfAbsent)”, 替換和條件刪除, 而且這些操作都是原子操作

public interface ConcurrentMap<K, V> extends Map<K, V> {
     V putIfAbsent(K key, V value);
     boolean remove(Object key, Object value);
     boolean replace(K key, V oldValue, V newValue);
     V replace(K key, V value);
}

由于ConcurrentHashMap不能被加鎖來執(zhí)行獨(dú)占訪問命迈,因此我們無法使用客戶端加鎖來創(chuàng)建新的原子操作贩绕。(為什么,如何體現(xiàn)壶愤?)

CopyOnWriteArrayList
CopyOnWriteArrayList用于替代同步List淑倾,并且在迭代期間不需要對(duì)容器進(jìn)行加鎖或復(fù)制。多個(gè)線程可以對(duì)該容器進(jìn)行迭代, 并且不會(huì)受到另一個(gè)或者多個(gè)想要修改容器的線程帶來的干涉, 迭代的時(shí)候返回的元素嚴(yán)格與創(chuàng)建的時(shí)候一致, 不會(huì)考慮后續(xù)的修改.征椒。

在每次CopyOnWriteArrayList改變時(shí)都需要對(duì)底層數(shù)組進(jìn)行一次復(fù)制, 因此當(dāng)容器比較大時(shí), 不是很合適, 只有當(dāng)容器迭代操作的頻率遠(yuǎn)遠(yuǎn)高于對(duì)容器修改的頻率娇哆, 寫入即復(fù)制容器是一個(gè)合適的選擇。

Queue
BlockingQueue提供了可阻塞的put和take方法, 他們與可定時(shí)的offer和poll是等價(jià)的. 如果Queue已經(jīng)滿了, put方法會(huì)被阻塞直到有空間可用; 如果queue是空的, 那么take方法會(huì)被阻塞, 直到有元素可用. queue的長(zhǎng)度可以有限, 也可以無限.

可以使用BlockingQueue的offer方法來處理這樣一種場(chǎng)景: 如果條目不能被加入到隊(duì)列里, 它會(huì)返回一個(gè)失敗狀態(tài). 這樣可以創(chuàng)建更多靈活的策略來處理超負(fù)荷工作, 比如減輕負(fù)載, 序列化剩余工作條目并寫入硬盤, 減少生產(chǎn)者線程, 或者其他方法兒子生產(chǎn)者線程.

在類庫中包含了BlockingQueue的多種實(shí)現(xiàn)勃救,其中碍讨,LinkedBlockingQueue和ArrayBlockingQueue是FIFO隊(duì)列,二者分別與LinkedList和ArrayList相似蒙秒,但比同步的List擁有更好的并發(fā)性能勃黍。PriorityBlockingQueue是一個(gè)按優(yōu)先級(jí)排序的隊(duì)列,而不是FIFO晕讲。

SynchronousQueue
SynchronousQueue是一種BlockingQueue的實(shí)現(xiàn)覆获,維護(hù)了一個(gè)沒有存儲(chǔ)空間的queue, 如果用洗盤子來比喻的話, 可以認(rèn)為沒有盤子架, 直接將洗好的盤子放到烘干機(jī)中. 因?yàn)槭侵苯右平? 這樣可以減少數(shù)據(jù)在生產(chǎn)者和消費(fèi)者移動(dòng)的延遲.
因?yàn)镾ynchronousQueue沒有存儲(chǔ)能力, 所以除非另一個(gè)線程已經(jīng)準(zhǔn)備好參與移交工作, 否則put和take會(huì)一直阻止, 這類隊(duì)列只有在消費(fèi)者充足的時(shí)候比較合適, 他們總是為下一個(gè)任務(wù)做好準(zhǔn)備.

Deque
Deque(BlockingDeque)是一個(gè)雙端隊(duì)列是對(duì)Queue和BlockingQueue的擴(kuò)展, 允許高效的在頭和尾分別進(jìn)行插入和刪除, 其實(shí)現(xiàn)有ArrayDeque和LinkedBlockingDeque榜田。

雙端隊(duì)列采用的是一種竊取的工作模式, 其原理是每一個(gè)消費(fèi)者都有一個(gè)自己的雙端隊(duì)列, 如果一個(gè)消費(fèi)者完成了自己的雙端隊(duì)列中的全部工作, 它可以偷取其他消費(fèi)者的雙端隊(duì)列中末尾的任務(wù). 由于消費(fèi)者不會(huì)共享同一個(gè)隊(duì)列, 因此相對(duì)于傳統(tǒng)的生產(chǎn)者-消費(fèi)者模式具有更高的可伸縮性. 而且即使一個(gè)工作者要訪問另一個(gè)隊(duì)列, 也是從末尾截取, 這樣可以進(jìn)一步降低對(duì)隊(duì)列的爭(zhēng)奪。

class Producer implements Runnable {  
    private String name;  

    private BlockingDeque<Integer> deque;  

    public Producer(String name, BlockingDeque<Integer> deque) {  
        this.name = name;  
        this.deque = deque;  
    }  

    public synchronized void run() {  

        for (int i = 0; i < 10; i++) {  
            try {  
                deque.putFirst(i);  
                System.out.println(name + " puts " + i);  
                Thread.sleep(300);  

            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  

    }  
}  

class Consumer implements Runnable {  
    private String name;  

    private BlockingDeque<Integer> deque;  

    public Consumer(String name, BlockingDeque<Integer> deque) {  
        this.name = name;  
        this.deque = deque;  
    }  

    public synchronized void run() {  
        for (int i = 0; i < 10; i++) {  
            try {  
                int j = deque.takeLast();  
                System.out.println(name + " takes " + j);  
                Thread.sleep(3000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  

public class BlockingDequeTester {  
    public static void main(String[] args) {  
        BlockingDeque<Integer> deque = new LinkedBlockingDeque<Integer>(5);  
        Runnable producer = new Producer("Producer", deque);  
        Runnable consumer = new Consumer("Consumer", deque);  
        new Thread(producer).start();  
        try {  
            Thread.sleep(500);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  

        new Thread(consumer).start();  
    }  
} 

3.同步工具類

同步工具類可以是任何一個(gè)對(duì)象锻梳,只要它根據(jù)其自身的狀態(tài)來協(xié)調(diào)線程的控制流。阻塞隊(duì)列可以作為同步工具類净捅,其他類型的同步工具類還包括信號(hào)量(Semaphore)疑枯、柵欄(Barried)以及閉鎖(Latch),如果不能滿足自己的需求蛔六,還能自己定制同步工具類荆永。

閉鎖(Latch)

閉鎖可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài),一個(gè)閉鎖工作起來就像一道大門: 直到閉鎖達(dá)到終點(diǎn)狀態(tài)之前, 門一直是關(guān)閉的, 沒有線程通過, 在終點(diǎn)狀態(tài)到來的時(shí)候, 這扇門會(huì)打開并允許所有的線程通過国章。一旦閉鎖到達(dá)了終點(diǎn)狀態(tài), 它就不能再改變狀態(tài)了, 所以它會(huì)永遠(yuǎn)保打開狀態(tài)具钥。

CountDownLatch是一種靈活的閉鎖實(shí)現(xiàn),它可以使一個(gè)或者多個(gè)線程等待一組事件發(fā)生液兽。它的狀態(tài)包括一個(gè)計(jì)數(shù)器, 初始化為一個(gè)正數(shù), 用來表現(xiàn)需要等待的事件數(shù)骂删。countDown方法對(duì)計(jì)數(shù)器做減操作, 表示一個(gè)事件已經(jīng)發(fā)生了, 而await方法等待計(jì)數(shù)器達(dá)到零, 這表示所有需要等待的時(shí)間都已經(jīng)發(fā)生。如果計(jì)數(shù)器入口時(shí)值為非零, await會(huì)一直阻塞知道計(jì)數(shù)器為零, 或者等待線程中斷以及超時(shí)四啰。

public class TestHarness {  
    public long timeTasks(int n, final Runnable task) throws Exception {  
        final CountDownLatch startGate = new CountDownLatch(1);  
        final CountDownLatch endGate = new CountDownLatch(n);  
        for (int i = 0; i < n; i++) {  
            Thread t = new Thread() {  
                public void run() {  
                    try {  
                        startGate.await(); // 所有線程運(yùn)行到此被暫停, 等待一起被執(zhí)行  
                        try {  
                            task.run();  
                        } finally {  
                            endGate.countDown();  
                        }  
                    } catch (Exception e) {  
                    }  
                };  
            };  
            t.start();  
        }  

        long start = System.nanoTime();  
        startGate.countDown(); // 啟動(dòng)所有被暫停的線程  
        endGate.await(); // 等待所有線程執(zhí)行完  
        long end = System.nanoTime();  
        return end - start;  
    }  

    public static void main(String[] args) {  
        TestHarness th = new TestHarness();  
        Runnable r = new Runnable() {  
            public void run() {  
                System.out.println("running");  
            }  
        };  
        try {  
            th.timeTasks(10, r);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }

startGate是一個(gè)開始門宁玫,endGate是結(jié)束門。startGate初始為1柑晒,而endGate初始為工作線程的數(shù)量欧瘪。

FutureTask

FutureTask的計(jì)算是通過Callable實(shí)現(xiàn)的, 它等價(jià)于一個(gè)可以攜帶結(jié)果的Runnable, 并且有三個(gè)狀態(tài):
等待運(yùn)行, 正在運(yùn)行和運(yùn)行完成。

運(yùn)行完成有三種情況:
正常結(jié)束, 取消結(jié)束和異常結(jié)束
一旦FutureTask進(jìn)入完成狀態(tài), 它會(huì)永遠(yuǎn)停止這個(gè)狀態(tài)上匙赞。

FutureTask.get()的行為依賴于任務(wù)的狀態(tài), 如果它已經(jīng)完成, get可以立即返回結(jié)果, 否則會(huì)被阻塞佛掖,直到任務(wù)轉(zhuǎn)入完成狀態(tài), 然后會(huì)返回結(jié)果或者拋出異常.

class PreLoader <V> {
    private final FutureTask<V> future = new FutureTask<V>(new Callable<V>() {

        @Override
        public V call() throws Exception {
            //
            return loadTask();
        }

    });

    private Thread thread = new Thread(future);

    public void start(){
        thread.start();
    }

    public V get() throws InterruptedException, ExecutionException{
        return future.get();
    }

    private V loadTask() {
        //模擬加載任務(wù)
        return null;
    }
}

信號(hào)量(Semaphore)

計(jì)數(shù)信號(hào)量用來控制能夠同時(shí)訪問某特定資源的活動(dòng)的數(shù)量或者同時(shí)執(zhí)行某一給定操作的數(shù)量
技術(shù)信號(hào)量可以用來實(shí)現(xiàn)資源池或者給一個(gè)容器設(shè)定邊界。

一個(gè)Semaphore管理一個(gè)有效的許可集涌庭,許可的初始量通過構(gòu)造函數(shù)來指定芥被。活動(dòng)能夠獲得許可, 并在使用之后釋放許可, 如果已經(jīng)沒有可用的許可了, 那么acquire會(huì)被阻塞脾猛,直到有可用的為止(或者直到被中斷或者操作超時(shí))撕彤,release方法向信號(hào)量返回一個(gè)許可。
一個(gè)初始值為1的Semaphore可以用來充當(dāng)mutex(互斥鎖)猛拴。

public class BoundedHashSet <T>{  
    private final Set<T> set;  
    private final Semaphore sem;  

    public BoundedHashSet(int n) {  
        set = Collections.synchronizedSet(new HashSet<T>());  
        sem = new Semaphore(n);  
    }  

    public boolean add(T element) {  
        try {  
            sem.acquire();  //請(qǐng)求信號(hào)量
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        boolean result = false;  
        try {  
            result = set.add(element);  
        }finally {  
            sem.release();  
        }  
        return result;  
    }  

    public void remove(T o) {  
        boolean result = set.remove(o);  
        if (result) {  
            sem.release();  //返回信號(hào)量
        }  
    }  

    public static void main(String[] args) {  
        final BoundedHashSet<String> bhs = new BoundedHashSet<String>(3);  
        for (int i = 0; i < 4; i++) {  
            Thread t = new Thread() {  
                @Override  
                public void run() {  
                    bhs.add(System.currentTimeMillis() + "");  
                };  
            };  
            t.start();  
        }  
    }  
} 

柵欄(Barrier)
關(guān)卡類似于閉鎖, 他們能夠阻塞一組線程, 直到某些事件發(fā)生, 其中關(guān)卡與閉鎖的關(guān)鍵不同在于, 所有線程必須同時(shí)達(dá)到關(guān)卡點(diǎn), 才能繼續(xù)處理. 閉鎖等待的是事件, 關(guān)卡等待其他線程. 關(guān)卡實(shí)現(xiàn)的是協(xié)議, 就像一些家庭成員指定商場(chǎng)中的集合地點(diǎn):”我們每一個(gè)人6:00在麥當(dāng)勞見, 到了以后不見不散, 之后我們?cè)贈(zèng)Q定接下來做什么羹铅。”

CyclicBarrier允許一個(gè)給定數(shù)量的成員多次集中在一個(gè)柵欄位置愉昆,這在并行迭代算法中非常有用, 這個(gè)算法會(huì)把一個(gè)問題拆分成一系列相互獨(dú)立的子問題, 當(dāng)線程到達(dá)柵欄位置時(shí), 調(diào)用await, await將會(huì)阻塞所有線程到達(dá)柵欄位置职员,直到所有線程到達(dá)關(guān)卡點(diǎn)。

關(guān)卡通常用來模擬這種情況, 一個(gè)步驟的計(jì)算可以并行完成, 但是要求必須完成所有與一個(gè)步驟相關(guān)的工作后才能進(jìn)入下一步跛溉。

public class Cellular {  
    private CyclicBarrier cb;  
    private Worker[] workers;  

    public Cellular() {  
        int count = Runtime.getRuntime().availableProcessors();  
        workers = new Worker[count];  
        for (int i = 0; i < count; i++) {  
            workers[i] = new Worker();  
        }  

        cb = new CyclicBarrier(count, new Runnable() {  
                public void run() {  
                System.out.println("the workers is all end...");  
                }  
                        });  
    }  

    public void start() {  
        for (Worker worker : workers) {  
            new Thread(worker).start();  
        }  
    }  

    private class Worker implements Runnable {  
        public void run() {  
            System.out.println("working...");  
            try {  
                cb.await();//在這里線程阻塞焊切,等待其他線程扮授。  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            } catch (BrokenBarrierException e) {  
                e.printStackTrace();  
            }  
        }  
    }  

    public static void main(String[] args) {  
        Cellular c = new Cellular();  
        c.start();  
    }  
}

Exchanger是關(guān)卡的另外一種形式, 它是一種兩步關(guān)卡, 在關(guān)卡交匯點(diǎn)會(huì)叫喚數(shù)據(jù), 當(dāng)兩方進(jìn)行的活動(dòng)不對(duì)稱時(shí), Exchanger是非常有用的, 比如當(dāng)一個(gè)線程向緩沖寫入一個(gè)數(shù)據(jù), 這是另一個(gè)線程充當(dāng)消費(fèi)者使用這個(gè)數(shù)據(jù)。

三专肪、線程安全總結(jié)

  1. 可變狀態(tài)越少刹勃,越容易保證線程安全性
  2. 盡量將域聲明為final,除非需要它們是可變的
  3. 不可變對(duì)象一定是線程安全的
  4. 封裝有利于管理復(fù)雜性
  5. 用鎖來保護(hù)每一個(gè)可變變量
  6. 當(dāng)保護(hù)同一個(gè)不變性條件的所有變量時(shí)嚎尤,要使用同一個(gè)鎖(最容易忽略)
  7. 在執(zhí)行復(fù)合操作時(shí)荔仁,要持有鎖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市芽死,隨后出現(xiàn)的幾起案子乏梁,更是在濱河造成了極大的恐慌,老刑警劉巖关贵,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遇骑,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡揖曾,警方通過查閱死者的電腦和手機(jī)落萎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翩肌,“玉大人模暗,你說我怎么就攤上這事∧罴溃” “怎么了兑宇?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)粱坤。 經(jīng)常有香客問我隶糕,道長(zhǎng),這世上最難降的妖魔是什么站玄? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任枚驻,我火速辦了婚禮,結(jié)果婚禮上株旷,老公的妹妹穿的比我還像新娘再登。我一直安慰自己,他們只是感情好晾剖,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布锉矢。 她就那樣靜靜地躺著,像睡著了一般齿尽。 火紅的嫁衣襯著肌膚如雪沽损。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天循头,我揣著相機(jī)與錄音绵估,去河邊找鬼炎疆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛国裳,可吹牛的內(nèi)容都是我干的形入。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缝左,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼唯笙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起盒使,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎七嫌,沒想到半個(gè)月后少办,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诵原,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年英妓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍赛。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔓纠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吗蚌,到底是詐尸還是另有隱情腿倚,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布蚯妇,位于F島的核電站敷燎,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏箩言。R本人自食惡果不足惜硬贯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望陨收。 院中可真熱鬧饭豹,春花似錦、人聲如沸务漩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菲饼。三九已至肾砂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間宏悦,已是汗流浹背镐确。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國打工包吝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人源葫。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓诗越,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親息堂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嚷狞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

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