Java并發(fā)容器與框架(一)阻塞隊(duì)列

我們已經(jīng)看到了形成Java并發(fā)程序設(shè)計(jì)基礎(chǔ)的底層構(gòu)建塊作媚。然而穷躁,對(duì)于實(shí)際編程來(lái)說(shuō)祠挫,應(yīng)該盡可能遠(yuǎn)離底層結(jié)構(gòu)。使用由并發(fā)處理的專(zhuān)業(yè)人士實(shí)現(xiàn)的較高層次的結(jié)構(gòu)要方便得多酝掩、安全得多。

對(duì)于許多線程問(wèn)題眷柔,可以通過(guò)使用一個(gè)或多個(gè)隊(duì)列以優(yōu)雅且安全的方式將其形式化期虾。生產(chǎn)者線程向隊(duì)列插入元素,消費(fèi)者線程則取出他媽闯割。使用隊(duì)列彻消,可以安全地從一個(gè)線程向另一個(gè)線程傳遞數(shù)據(jù)。例如宙拉,考慮銀行轉(zhuǎn)賬程序宾尚,轉(zhuǎn)賬線程將轉(zhuǎn)賬指令對(duì)象插入到一個(gè)隊(duì)列中,而不是直接訪問(wèn)銀行對(duì)象谢澈。另一個(gè)線程從隊(duì)列中取出指令轉(zhuǎn)賬煌贴。只有該線程可以訪問(wèn)該銀行對(duì)象的內(nèi)部。因此不需要同步锥忿。(當(dāng)然牛郑,線程安全的隊(duì)列類(lèi)的實(shí)現(xiàn)者不能不考慮鎖和條件,但是敬鬓,那是他們的問(wèn)題而不是你的問(wèn)題淹朋。)

一笙各、阻塞隊(duì)列方法


方法 正常動(dòng)作 特殊情況下的動(dòng)作
put 添加一個(gè)元素 如果隊(duì)列滿,則阻塞
take 移出并返回頭元素 如果隊(duì)列空础芍,則阻塞
add 添加一個(gè)元素 如果隊(duì)列滿杈抢,拋出IllegalStateException
remove 移出并返回頭元素 如果隊(duì)列空,則拋出NoSuchElementException
element 返回隊(duì)列頭元素 如果隊(duì)列空仑性,拋出NoSuchElementException
offer 添加一個(gè)元素并返回true 如果隊(duì)列滿惶楼,返回false
poll 移出并返回隊(duì)列的頭元素 如果隊(duì)列空,返回null
peek 返回隊(duì)列的頭元素 如果隊(duì)列空诊杆,返回null

阻塞隊(duì)列方法使用上分下面三類(lèi):
1 當(dāng)將隊(duì)列當(dāng)做線程管理工具來(lái)使用時(shí)歼捐,將要用到put和take方法。
2 當(dāng)視圖向滿的隊(duì)列中添加或從空的隊(duì)列中移出元素時(shí)晨汹,add豹储、remove和element拋出異常。
3 當(dāng)然淘这,在一個(gè)多線程程序中颂翼,隊(duì)列會(huì)在任何時(shí)空或滿,因此慨灭,可以用offer、poll和peek代替球及。

二氧骤、java.util.concurrent包


java.util.concurrent包提供了阻塞隊(duì)列的幾個(gè)變種:

  • BlockingQueue

  • LinkedBlockingQueue:鏈表結(jié)構(gòu)組成的無(wú)界(可指定容量)阻塞隊(duì)列

  • ArrayBlockingQueue:數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列

  • PriorityBlockingQueue:無(wú)界優(yōu)先級(jí)隊(duì)列,非FIFO隊(duì)列吃引,元素按照優(yōu)先級(jí)順序被移出

  • DelayQueue:無(wú)界阻塞隊(duì)列筹陵,只有延遲期滿時(shí)才能提取元素

  • SynchronousQueue:阻塞隊(duì)列,插入操作必須等待另一線程的移除操作 镊尺,反之亦然

  • BlockingDeque

  • LinkedBlockingDeque:鏈表結(jié)構(gòu)組成的無(wú)界(可指定容量)雙端阻塞隊(duì)列

絕大部分場(chǎng)景下朦佩,我們只要使用ArrayBlockingQueue或LinkedBlockingQueue就夠了。

示例程序展示了如何使用阻塞隊(duì)列來(lái)控制一組線程庐氮。程序在一個(gè)目錄及它的所有子目錄下搜索所有文件语稠,打印出包含指定關(guān)鍵字的行:

public class BlockingQueueTest {
    
    private static final int FILE_QUEUE_SIZE = 10;
    private static final int SEARCH_THREADS = 100;
    private static final File DUMMY = new File("");
    private static BlockingQueue<File> queue = new ArrayBlockingQueue<>(FILE_QUEUE_SIZE);
    
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in)) {
            System.out.println("Enter base directory (e.g. /opt/jdk1.8.0/src): ");
            String directory = in.nextLine();
            System.out.println("Enter keyword (e.g. volatile): ");
            String keyword = in.nextLine();
            
            Runnable enumerator = () -> {
                try {
                    enumerate(new File(directory));
                    queue.put(DUMMY);
                } catch (InterruptedException e) {
                    
                }
            };
            new Thread(enumerator).start();
            
            for(int i = 1; i <= SEARCH_THREADS; i++) {
                Runnable searcher = () -> {
                    try {
                        boolean done = false;
                        while (!done) {
                            File file = queue.take();
                            if(file == DUMMY) {
                                queue.put(file);
                                done = true;
                            } else {
                                search(file, keyword);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        
                    }
                };
                new Thread(searcher).start();
            }
        }
    }
    
    // 遞歸地枚舉給定目錄及其子目錄中的所有文件
    public static void enumerate(File directory) throws InterruptedException {
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) enumerate(file);
            else queue.put(file);
        }
    }
    
    // 搜索一個(gè)給定關(guān)鍵字的文件,并打印出所有匹配的行
    public static void search(File file, String keyword) throws IOException {
        try (Scanner in = new Scanner(file, "UTF-8")) {
            int lineNumber = 0;
            while (in.hasNextLine()) {
                lineNumber++;
                String line = in.nextLine();
                if (line.contains(keyword))
                    System.out.printf("%s:%d:%n", file.getPath(), lineNumber, line);
            }
        }
    }

}

生產(chǎn)者線程枚舉所有子目錄下的所有文件并把它們放到一個(gè)阻塞隊(duì)列中弄砍。同時(shí)啟動(dòng)大量消費(fèi)者線程仙畦,每個(gè)消費(fèi)者線程從隊(duì)列取出一個(gè)文件,打開(kāi)它音婶,打印所有包含該關(guān)鍵字的行慨畸,然后取出下一個(gè)文件。我們使用一個(gè)小技巧在工作結(jié)束后終止這個(gè)應(yīng)用程序衣式。為了發(fā)出完成信號(hào)寸士,枚舉線程放置一個(gè)虛擬對(duì)象到隊(duì)列中(這就像在行李輸送帶上放著一個(gè)寫(xiě)著“最后一個(gè)包”的虛擬包)檐什。當(dāng)搜索線程取到這個(gè)虛擬對(duì)象時(shí),將其放回并終止弱卡。

注意乃正,不需要顯示的線程同步。在這個(gè)示例中谐宙,已使用了阻塞隊(duì)列作為一種同步機(jī)制烫葬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凡蜻,隨后出現(xiàn)的幾起案子搭综,更是在濱河造成了極大的恐慌,老刑警劉巖划栓,帶你破解...
    沈念sama閱讀 216,843評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兑巾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡忠荞,警方通過(guò)查閱死者的電腦和手機(jī)蒋歌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)委煤,“玉大人堂油,你說(shuō)我怎么就攤上這事”探剩” “怎么了府框?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,187評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)讥邻。 經(jīng)常有香客問(wèn)我迫靖,道長(zhǎng),這世上最難降的妖魔是什么兴使? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,264評(píng)論 1 292
  • 正文 為了忘掉前任系宜,我火速辦了婚禮,結(jié)果婚禮上发魄,老公的妹妹穿的比我還像新娘盹牧。我一直安慰自己,他們只是感情好励幼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布欢策。 她就那樣靜靜地躺著,像睡著了一般赏淌。 火紅的嫁衣襯著肌膚如雪踩寇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,231評(píng)論 1 299
  • 那天六水,我揣著相機(jī)與錄音俺孙,去河邊找鬼辣卒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛睛榄,可吹牛的內(nèi)容都是我干的荣茫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼场靴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼啡莉!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起旨剥,我...
    開(kāi)封第一講書(shū)人閱讀 38,945評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咧欣,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后轨帜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體魄咕,經(jīng)...
    沈念sama閱讀 45,367評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評(píng)論 2 333
  • 正文 我和宋清朗相戀三年蚌父,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哮兰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苟弛,死狀恐怖喝滞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膏秫,我是刑警寧澤囤躁,帶...
    沈念sama閱讀 35,458評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站荔睹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏言蛇。R本人自食惡果不足惜僻他,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腊尚。 院中可真熱鬧吨拗,春花似錦、人聲如沸婿斥。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,692評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)民宿。三九已至娇妓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間活鹰,已是汗流浹背哈恰。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,842評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工只估, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人着绷。 一個(gè)月前我還...
    沈念sama閱讀 47,797評(píng)論 2 369
  • 正文 我出身青樓蛔钙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親荠医。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吁脱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評(píng)論 2 354

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