JUC-(6)并發(fā)工具(下)

Exchanger(交換機(jī))

交換機(jī)(Exchanger)主要用于線程之間數(shù)據(jù)交換的工具,它提供一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn)兩個(gè)線程可以交換彼此的數(shù)據(jù).如果第一個(gè)線程先執(zhí)行exchange方法,它會(huì)等待第二個(gè)線程也執(zhí)行exchange方法.當(dāng)兩個(gè)線程都到達(dá)同步點(diǎn)時(shí),這個(gè)兩個(gè)線程就可以交換數(shù)據(jù).如下圖所示:

exchanger.jpg

例如生活中A和B約定地點(diǎn)進(jìn)行商品交易,A是買家B是賣家.我們可以使用Exchanger來(lái)描述兩個(gè)人的交易過(guò)程.

public class App11 {
    public static void main(String[] args) {
        //約定交易地點(diǎn)
        Exchanger<String> exchanger = new Exchanger<>();
        //買家?guī)?00塊錢去買辣條
        Thread buyer = new Thread(new Buyer("500元",exchanger));
        //賣家?guī)Ю睏l去賣
        Thread seller = new Thread(new Seller("500根辣條",exchanger));
        //買家和賣家出發(fā)去交易
        buyer.start();
        seller.start();

    }
}

/**
 * 買家
 */
class Buyer implements Runnable{
    private String amount;
    private Exchanger<String> exchanger;

    public Buyer(String amount, Exchanger<String> exchanger) {
        this.amount = amount;
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("買家去交易地點(diǎn)");
            //模擬去交易地點(diǎn)耗時(shí)
            Thread.sleep(2000);
            //到了交易地點(diǎn)等賣家
            String goods = exchanger.exchange(amount);
            //買到東西
            System.out.println(String.format("買家買到[%s]回家",goods));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 賣家
 */
class Seller implements Runnable{
    private String goods;
    private Exchanger<String> exchanger;

    public Seller(String goods, Exchanger<String> exchanger) {
        this.goods = goods;
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
      try {
          System.out.println("賣家去交易地點(diǎn)");
          //模擬去交易地點(diǎn)
          Thread.sleep(5000);
          //到了交易地點(diǎn)等待買家
          String data = exchanger.exchange(goods);
          //賣出了東西
          System.out.println(String.format("賣家賣了[%s]錢回家",data));
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
    }
}

最后的打印結(jié)果如下:

賣家去交易地點(diǎn)
買家去交易地點(diǎn)
買家買到[500根辣條]回家
賣家賣了[500元]錢回家

上面例子中,買家和賣家調(diào)用exchange沒(méi)有設(shè)置超時(shí)時(shí)間.如果當(dāng)前等待的人一直沒(méi)有等待到對(duì)方,當(dāng)前的人將會(huì)一直等待下去.所以exchange為我們提供了帶有超時(shí)時(shí)間參數(shù)的方法,通過(guò)超時(shí)時(shí)間將會(huì)通過(guò)拋出TimeoutException中斷線程的等待.下面我們模擬買家中途錢被小偷偷了,然后賣家等了時(shí)間久了就放棄交易回去了.下面修改買家和賣家的代碼如下:

/**
 * 買家
 */
class Buyer implements Runnable{
    private String amount;
    private Exchanger<String> exchanger;

    public Buyer(String amount, Exchanger<String> exchanger) {
        this.amount = amount;
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            System.out.println("買家去交易地點(diǎn)");
            //模擬去交易地點(diǎn)耗時(shí)
            Thread.sleep(2000);
            //模擬中途可能遇到小偷錢被偷走
            Random random = new Random();
            int i = random.nextInt(10);
            if (i % 2 == 0){
                throw new RuntimeException("買家中途遇見(jiàn)小偷,錢丟了");
            }
            //到了交易地點(diǎn)等賣家
            String goods = exchanger.exchange(amount);
            //買到東西
            System.out.println(String.format("買家買到[%s]回家",goods));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 賣家
 */
class Seller implements Runnable{
    private String goods;
    private Exchanger<String> exchanger;

    public Seller(String goods, Exchanger<String> exchanger) {
        this.goods = goods;
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
      try {
          System.out.println("賣家去交易地點(diǎn)");
          //模擬去交易地點(diǎn)
          Thread.sleep(5000);
          //到了交易地點(diǎn)等待買家
          String data = exchanger.exchange(goods,2,TimeUnit.SECONDS);
          //賣出了東西
          System.out.println(String.format("賣家賣了[%s]錢回家",data));
      } catch (InterruptedException e) {
          e.printStackTrace();
      } catch (TimeoutException e) {
          System.out.println("賣家等了太久了,不賣了回家");
          e.printStackTrace();
      }
    }
}

多次執(zhí)行代碼,當(dāng)買家的錢被小偷偷了,打印的結(jié)果語(yǔ)句如下:

賣家去交易地點(diǎn)
買家去交易地點(diǎn)
Exception in thread "Thread-0" java.lang.RuntimeException: 買家中途遇見(jiàn)小偷,錢丟了
    at com.buydeem.Buyer.run(App11.java:49)
    at java.lang.Thread.run(Thread.java:745)
java.util.concurrent.TimeoutException
賣家等了太久了,不賣了回家
    at java.util.concurrent.Exchanger.exchange(Exchanger.java:626)
    at com.buydeem.Seller.run(App11.java:80)
    at java.lang.Thread.run(Thread.java:745)

Semaphore (信號(hào)量)

Semaphore通常我們叫它信號(hào)量,可以用來(lái)控制同時(shí)訪問(wèn)特定資源的線程數(shù).它提供acquire方法用來(lái)獲取許可,在沒(méi)有足夠的許可之前將調(diào)用該方法的線程將一直阻塞知道有可用的許可.同時(shí)還提供release方法用來(lái)添加許可,用來(lái)釋放一個(gè)正在阻塞的獲取者.
它的使用場(chǎng)景類似于我們平常去窗口買票.如果現(xiàn)在有10個(gè)人去買票.可是窗口只有三個(gè).也就是說(shuō)最多同時(shí)容納三個(gè)人同時(shí)買票,而其他的人必須等待當(dāng)前窗口買票的人離開(kāi)了才能去窗口買票.這個(gè)例子中的三個(gè)窗口就可以理解為信號(hào)量初始的許可個(gè)數(shù)為3,而去買票的用戶理解為線程.我們使用下面的代碼來(lái)描述整個(gè)過(guò)程:

public class App12 {
    public static void main(String[] args) throws InterruptedException {
        //創(chuàng)建買票的窗口
        Semaphore semaphore = new Semaphore(3);
        //創(chuàng)建乘客買票
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            service.execute(new Passenger("乘客"+i+"號(hào)",semaphore));
        }
        service.shutdown();
    }
}

/**
 * 乘客
 */
class Passenger implements Runnable{
    private String name;
    private Semaphore semaphore;

    public Passenger(String name, Semaphore semaphore) {
        this.name = name;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        boolean success = false;
        try {
            //等待空余窗口買票
            semaphore.acquire();
            success = true;
            System.out.println(String.format("[%s]開(kāi)始買票",name));
            //模擬當(dāng)前用戶買票耗時(shí)
            Random random = new Random();
            int second = random.nextInt(4) + 1;
            Thread.sleep(second * 1000);
            //當(dāng)前用戶買票完成
            System.out.println(String.format("[%s]買票結(jié)束",name));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (success){
                semaphore.release();
            }
        }
    }
}

最后的打印結(jié)果如下:

[乘客1號(hào)]開(kāi)始買票
[乘客2號(hào)]開(kāi)始買票
[乘客0號(hào)]開(kāi)始買票
[乘客0號(hào)]買票結(jié)束
[乘客3號(hào)]開(kāi)始買票
[乘客1號(hào)]買票結(jié)束
[乘客4號(hào)]開(kāi)始買票
[乘客2號(hào)]買票結(jié)束
[乘客5號(hào)]開(kāi)始買票
[乘客5號(hào)]買票結(jié)束
[乘客6號(hào)]開(kāi)始買票
[乘客3號(hào)]買票結(jié)束
[乘客7號(hào)]開(kāi)始買票
[乘客4號(hào)]買票結(jié)束
[乘客8號(hào)]開(kāi)始買票
[乘客6號(hào)]買票結(jié)束
[乘客9號(hào)]開(kāi)始買票
[乘客9號(hào)]買票結(jié)束
[乘客8號(hào)]買票結(jié)束
[乘客7號(hào)]買票結(jié)束

acquire,acquireUninterruptibly和tryAcquire

這三個(gè)方法都是用來(lái)獲取許可的,但是它們之間還是有部分不同之處.

方法 是否阻塞 是否響應(yīng)中斷
acquire
acquireUninterruptibly
tryAcquire
tryAcquire帶超時(shí)時(shí)間

獲取許可的方法基本上就是上面這幾種,同時(shí)每次獲取許可時(shí)還可以指定獲取的個(gè)數(shù).上面的所指的是否會(huì)響應(yīng)中斷指的是當(dāng)線程因?yàn)闊o(wú)法獲取許可而阻塞,該調(diào)用該線程的**interrupt**將會(huì)拋出**InterruptedException**.

relase

該方法用來(lái)釋放許可,將其返回到信號(hào)量.同時(shí)該方法也提供了釋放許可數(shù)量的參數(shù),可以一次釋放多個(gè)信號(hào)量.

公平與非公平

在構(gòu)建信號(hào)量時(shí)可以在構(gòu)造方法中傳入true設(shè)置該信號(hào)量為公平模式.該公平性是針對(duì)獲取許可的線程來(lái)說(shuō)的.該模式能保證對(duì)于任何調(diào)用獲取相關(guān)方法的線程FIFO.需要注意的是可能線程A先于線程B調(diào)用了acquire,但是A線程卻在B線程之后到達(dá)排序點(diǎn),這將導(dǎo)致看上去不公平.還有一點(diǎn)值得注意的就是公平設(shè)置對(duì)于tryAcquire非阻塞方法無(wú)效.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烫幕,更是在濱河造成了極大的恐慌,老刑警劉巖渗勘,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡柏肪,警方通過(guò)查閱死者的電腦和手機(jī)达皿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門天吓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贿肩,“玉大人,你說(shuō)我怎么就攤上這事龄寞√妫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵萄焦,是天一觀的道長(zhǎng)控轿。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拂封,這世上最難降的妖魔是什么茬射? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮冒签,結(jié)果婚禮上在抛,老公的妹妹穿的比我還像新娘。我一直安慰自己萧恕,他們只是感情好刚梭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著票唆,像睡著了一般朴读。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上走趋,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天衅金,我揣著相機(jī)與錄音,去河邊找鬼簿煌。 笑死氮唯,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姨伟。 我是一名探鬼主播惩琉,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼夺荒!你這毒婦竟也來(lái)了瞒渠?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤般堆,失蹤者是張志新(化名)和其女友劉穎在孝,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體淮摔,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡私沮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仔燕。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡造垛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晰搀,到底是詐尸還是另有隱情五辽,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布外恕,位于F島的核電站杆逗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鳞疲。R本人自食惡果不足惜罪郊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望尚洽。 院中可真熱鬧悔橄,春花似錦、人聲如沸腺毫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)潮酒。三九已至睛挚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間急黎,已是汗流浹背竞川。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叁熔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓床牧,卻偏偏與公主長(zhǎng)得像荣回,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子戈咳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348