一文讀懂JAVA并發(fā)容器類ArrayList檬洞,Set,Queue

上節(jié)說了ConcurrentHashMap沟饥,之前的知識會映射到今天的內(nèi)容點上面添怔,學了這些方法到底怎么用湾戳,更多List,Set广料,Queue要去看源碼的時候砾脑,掌握現(xiàn)有知識點,源碼對你難度不太大了艾杏,里面的變量命名比較麻煩韧衣。本次說說List,重要的說里面的原理购桑,使用這塊老鐵們應該都明白畅铭。

(一)ArrayList

  • ① 介紹

List 接口的大小可變數(shù)組的實現(xiàn)。實現(xiàn)了所有可選列表操作勃蜘,并允許包括 null 在內(nèi)的所有元素硕噩。

  • ② 內(nèi)部的存儲方式

ArrayList默認有一個空的數(shù)組, 數(shù)據(jù)的順序插入缭贡,如果當前的數(shù)組長度不夠存儲的時候炉擅,進行擴容處理,直接去創(chuàng)建一個新的數(shù)組阳惹,創(chuàng)建完成之后谍失,把數(shù)組進行拷貝,本身是線程非安全的穆端,不要一邊遍歷袱贮,一邊刪除代碼。擴容的時候存在i++体啰,之前也說過i++的情況下很容易存在高并發(fā)問題攒巍。

(二)CopyOnWriteArrayList

  • ① 介紹

List 接口的大小可變數(shù)組的實現(xiàn)。實現(xiàn)了所有可選列表操作荒勇,并允許包括 null 在內(nèi)的所有元素柒莉。

  • ② 內(nèi)部的存儲方式

內(nèi)部持有一個ReentrantLock lock = new ReentrantLock();底層是用volatile transient聲明的數(shù)組 array
讀寫分離,寫時復制出一個新的數(shù)組沽翔,完成插入兢孝。修改或者移除操作后將新數(shù)組賦值給array

(三)ArrayList 和 CopyOnWriteArrayList

CopyOnWriteArrayList容器即寫時復制的容器和ArrayList比較,優(yōu)點是并發(fā)安全仅偎,缺點有兩個

  1. 多了內(nèi)存占用跨蟹,寫數(shù)據(jù)是copy一份完成的數(shù)據(jù),單獨進行操作橘沥,占用兩份內(nèi)存窗轩。
  2. 數(shù)據(jù)一致性:數(shù)據(jù)寫完之后,其他線程不一定是馬上讀取到最新內(nèi)容座咆。

(四)Set

  • ① 介紹

無序(沒有下標) 集合中的元素不重復痢艺。

  • ② Set集合
  • ③ HashSet

因為是基于HashMap的仓洼,只要保證key不重復,其實內(nèi)部就不會重復堤舒。

  • ④ CopyOnWriteArraySet

CopyOnWriteArrayList中允許有重復的元素色建;但是,CopyOnWriteArraySet是一個集合舌缤,所以它不能有重復集合箕戳,因此,CopyOnWriteArrayList額外提供了addIfAbsent()和addAllAbsent()這兩個添加元素的API友驮,通過這些API來添加元素時漂羊,只有當元素不存在時才執(zhí)行添加操作!

  • ⑤ ConcurrentSkipListSet

所有操作都是無阻塞的卸留,所有操作都可以并行,包括寫椭豫,實現(xiàn)了ConcurrentMap接口耻瑟,直接支持一些原子復合操作(與ConcurrentHashMap類似),可排序(與TreeMap一樣)赏酥,默認按鍵自然有序喳整,可以傳遞比較器自定義排序,實現(xiàn)了SortedMap和NavigableMap接口裸扶。

(五)Queue

  • ① 介紹

基本上框都,一個隊列就是一個先入先出(FIFO)的數(shù)據(jù)結(jié)構(gòu)。

  • ② 阻塞和非阻塞
  1. 我去買一本書呵晨,立即買到了魏保,或者沒有就走了,這就是非阻塞摸屠;(編程中設(shè)置IO成非阻塞谓罗,返回后再去檢查描述符,或者等待通知季二,然后再去讀取檩咱。相當于老板告訴我可以先忙點別的,過一會再來問問胯舷,或者老板通知我刻蚯。但期間這個窗口(文件描述符)別人是用不了的)("立即買到了"在IO中也需要等待,不能算非阻塞IO)
  2. 如果恰好書店沒有桑嘶,我就等一直等到書店有了這本書買到了才走炊汹,這就是阻塞;而排在我后面的人呢只有我買到了書后才能再買書了不翩。
  3. 如果書店恰好沒有兵扬,我就告訴書店老板麻裳,書來了告訴我一聲讓我來取或者直接送到我家,然后我就走了器钟,去做別的事了津坑,這就是異步。這時候如果很多人來買書傲霸,都是老板登記一下完事疆瑰。 (從IO角度來說,“告訴我來取”昙啄,這個近似于信號驅(qū)動IO穆役,不能算異步IO。必須書送到我家才算是異步梳凛,如果不送到我家耿币,我想看這本書之前,終究還是需要我跑一趟)
  4. 前面兩種情況韧拒,非阻塞和阻塞都可以稱為同步淹接。
  • ③ 常用方法
  • ④ ArrayBlockingQueue

ArrayBlockingQueue是采用數(shù)組實現(xiàn)的有界阻塞線程安全隊列。如果向已滿的隊列繼續(xù)塞入元素叛溢,將導致當前的線程阻塞塑悼。如果向空隊列獲取元素,那么將導致當前線程阻塞楷掉。

import java.util.concurrent.ArrayBlockingQueue;


// 它是基于數(shù)組的阻塞循環(huán)隊列厢蒜, 此隊列按 FIFO(先進先出)原則對元素進行排序。
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 構(gòu)造時需要指定容量(量力而行),可以選擇是否需要公平(最先進入阻塞的烹植,先操作)
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3, false);
        // 1秒消費數(shù)據(jù)一個
        new Thread(() -> {
            while (true) {
                try {
                    // queue.task();
                    System.out.println("取到數(shù)據(jù):" + queue.poll()); // poll非阻塞
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
            }
        }).start();

        Thread.sleep(3000L); // 讓前面的線程跑起來斑鸦,上邊是消費者,下面的方法是生產(chǎn)者刊橘。

        // 三個線程塞數(shù)據(jù)
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                     queue.put(Thread.currentThread().getName()); // put阻塞(如果當前的隊列已經(jīng)塞滿了數(shù)據(jù)鄙才,線程不會繼續(xù)往下執(zhí)行,等待其他線程把
                    // 隊列的數(shù)據(jù)拿出去// )
//                    queue.offer(Thread.currentThread().getName()); // offer非阻塞促绵,滿了返回false
                    System.out.println(Thread.currentThread() + "塞入完成");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

前面三秒是沒有數(shù)據(jù)的攒庵,等數(shù)據(jù)存儲完畢后才可以讀取。用了6個線程來完成败晴。隊列對于線程池浓冒,連接池都會有隊列的使用,存放一些對象和數(shù)據(jù)尖坤。
存放:里面設(shè)置了lock稳懒,拿到一把鎖,然后進行入隊列慢味,數(shù)量++场梆,索引自行維護putIndex墅冷。
移除:數(shù)量--,找到對應的節(jié)點或油,lock設(shè)置了一把鎖寞忿。索引自行維護takeIndex(記錄下一個要獲取的)。

  • ⑤ DelayQueue

DelayQueue是一個無界阻塞隊列顶岸,只有在延遲期滿時才能從中提取元素腔彰。該隊列的頭部是延遲期滿后保存時間最長的Delayed 元素。
DelayQueue是一個用來延時處理的隊列辖佣,所謂延時處理就是說可以為隊列中元素設(shè)定一個過期時間霹抛,相關(guān)的操作受到這個設(shè)定時間的控制。

import java.util.Date;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

// (基于PriorityQueue來實現(xiàn)的)是一個存放Delayed 元素的無界阻塞隊列卷谈,
// 只有在延遲期滿時才能從中提取元素杯拐。該隊列的頭部是延遲期滿后保存時間最長的 Delayed 元素。
// 如果延遲都還沒有期滿世蔗,則隊列沒有頭部藕施,并且poll將返回null。
// 當一個元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個小于或等于零的值時凸郑,
// 則出現(xiàn)期滿,poll就以移除這個元素了矛市。此隊列不允許使用 null 元素芙沥。
public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<Message> delayQueue = new DelayQueue<Message>();
        // 這條消息5秒后發(fā)送
        Message message = new Message("message - 00001", new Date(System.currentTimeMillis() + 5000L));
        delayQueue.add(message);

        while (true) {
            System.out.println(delayQueue.poll());
            Thread.sleep(1000L);
        }
        // 線程池中的定時調(diào)度就是這樣實現(xiàn)的
    }
}

// 實現(xiàn)Delayed接口的元素才能存到DelayQueue
class Message implements Delayed {

    // 判斷當前這個元素,是不是已經(jīng)到了需要被拿出來的時間
    @Override
    public long getDelay(TimeUnit unit) {
        // 默認納秒
        long duration = sendTime.getTime() - System.currentTimeMillis();
        return TimeUnit.NANOSECONDS.convert(duration, TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return o.getDelay(TimeUnit.NANOSECONDS) > this.getDelay(TimeUnit.NANOSECONDS) ? 1 : -1;
    }

    String content;
    Date sendTime;

    /**
     * @param content  消息內(nèi)容
     * @param sendTime 定時發(fā)送
     */
    public Message(String content, Date sendTime) {
        this.content = content;
        this.sendTime = sendTime;
    }

    @Override
    public String toString() {
        return "Message{" +
                "content='" + content + '\'' +
                ", sendTime=" + sendTime +
                '}';
    }
}

延遲的排序是根據(jù)誰快浊吏,誰的位置最靠前而昨。

  • ⑥ LinkedBlockingQueue
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

// 它是基于鏈表的隊列,此隊列按 FIFO(先進先出)排序元素找田。
// 如果有阻塞需求歌憨,用這個。類似生產(chǎn)者消費者場景
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 構(gòu)造時可以指定容量墩衙,默認Integer.MAX_VALUE
        LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(3);
        // 1秒消費數(shù)據(jù)一個
        new Thread(() -> {
            while (true) {
                try {
                    System.out.println("取到數(shù)據(jù):" + queue.poll()); // poll非阻塞
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                }
            }
        }).start();

        Thread.sleep(3000L); // 讓前面的線程跑起來

        // 三個線程阻塞數(shù)據(jù)
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    // queue.put(Thread.currentThread().getName()); // put阻塞
                    queue.offer(Thread.currentThread().getName()); // offer非阻塞务嫡,滿了返回false
                    System.out.println(Thread.currentThread() + "塞入完成");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

存放數(shù)據(jù)的方式:鏈表的形式。
入隊列用了Atomic的形式漆改,原子性心铃。
LinkedBlockingQueue 和 ConcurrentBlockingQueue 兩個的區(qū)別是 LinkedBlockingQueue 是通過lock加鎖的方式,ConcurrentBlockingQueue 是通過cas的方式挫剑。

  • ⑦ PriorityBlockingQueue
import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

// 包裝了 PriorityQueue
// 是一個帶優(yōu)先級的 隊列去扣,而不是先進先出隊列。
// 元素按優(yōu)先級順序被移除樊破,該隊列也沒有上限
// 沒有容量限制的愉棱,自動擴容
// 雖然此隊列邏輯上是無界的唆铐,但是由于資源被耗盡,所以試圖執(zhí)行添加操作可能會導致 OutOfMemoryError)奔滑,
// 但是如果隊列為空艾岂,
// 那么取元素的操作take就會阻塞,所以它的檢索操作take是受阻的档押。另外澳盐,
// 入該隊列中的元素要具有比較能力
public class PriorityBlockingQueueDemo {
    public static void main(String[] args) {
        // 可以設(shè)置比對方式
        PriorityBlockingQueue<String> priorityQueue = new PriorityBlockingQueue<>(2);
        priorityQueue.add("c");
        priorityQueue.add("a");
        priorityQueue.add("b");

        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
    }
}
  • ⑧ PriorityQueueDemo
import java.util.Comparator;
import java.util.PriorityQueue;

public class PriorityQueueDemo {
    public static void main(String[] args) {
        // 可以設(shè)置比對方式
        PriorityQueue<String> priorityQueue = new PriorityQueue<>(new Comparator<String>() {
            @Override //
            public int compare(String o1, String o2) {
                // 實際就是 元素之間的 比對。
                return 0;
            }
        });
        priorityQueue.add("c");
        priorityQueue.add("a");
        priorityQueue.add("b");

        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());

        PriorityQueue<MessageObject> MessageObjectQueue = new PriorityQueue<>(new Comparator<MessageObject>() {
            @Override
            public int compare(MessageObject o1, MessageObject o2) {
                return o1.order > o2.order ? -1 : 1;
            }
        });
    }
}

class MessageObject {
    String content;
    int order;
}

一個帶優(yōu)先級的 隊列令宿,而不是先進先出隊列叼耙。
元素按優(yōu)先級順序被移除,該隊列也沒有上限粒没, 沒有容量限制的筛婉,自動擴容。
雖然此隊列邏輯上是無界的癞松,但是由于資源被耗盡爽撒,所以試圖執(zhí)行添加操作可能會導致 OutOfMemoryError), 但是如果隊列為空响蓉, 那么取元素的操作take就會阻塞硕勿,所以它的檢索操作take是受阻的。另外枫甲, 入該隊列中的元素要具有比較能力
new Comparator<MessageObject>() 比較器

  • ⑨ SynchronousQueue
import java.util.concurrent.SynchronousQueue;

// 這是一個神奇的隊列源武, 因為他不存數(shù)據(jù)。 手把手的交互數(shù)據(jù)
public class SynchronousQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
        // synchronousQueue.add("a"); // IllegalStateException
        // synchronousQueue.offer("a");
        System.out.println(synchronousQueue.poll()); // 非阻塞

        // 阻塞式的用法
        new Thread(() -> {
            try {
                System.out.println("等數(shù)據(jù)....");
                System.out.println(synchronousQueue.take());
                System.out.println("執(zhí)行完畢....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(1000L);
        System.out.println("準備賽數(shù)據(jù)了");
        synchronousQueue.put("a");// 等待有人取走他
    }
}

SynchronousQueue 就是不存隊列的想幻,里面是不存數(shù)據(jù)的粱栖。源碼里面不存在存儲單位。
這個方法就是等待脏毯,

PS:ArrayList鏈表闹究,Set不重復鏈表,Queue隊列食店,90%的可能都是使用現(xiàn)有JDK已經(jīng)提供的方法渣淤,很少自己去實現(xiàn)這些功能。針對這幾個源碼可以好好的看下叛买,嘗試畫下圖砂代,其實花不了多少時間的,JVM率挣,JDK都是很基礎(chǔ)的東西刻伊,翻過這座山就比別人強,一定要手把手的摸過,手把手的爬過去的捶箱,好記性不如爛筆頭智什,一定要實戰(zhàn)才能出真知,磨刀不誤砍柴工丁屎,就是從哪個階段過來的荠锭,理解都渴望學習新框架,新技術(shù)的心情晨川,相信厚積薄發(fā)吧证九。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市共虑,隨后出現(xiàn)的幾起案子愧怜,更是在濱河造成了極大的恐慌,老刑警劉巖妈拌,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拥坛,死亡現(xiàn)場離奇詭異,居然都是意外死亡尘分,警方通過查閱死者的電腦和手機猜惋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來培愁,“玉大人著摔,你說我怎么就攤上這事《ㄐ” “怎么了梨撞?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長香罐。 經(jīng)常有香客問我,道長时肿,這世上最難降的妖魔是什么庇茫? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮螃成,結(jié)果婚禮上旦签,老公的妹妹穿的比我還像新娘。我一直安慰自己寸宏,他們只是感情好宁炫,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著氮凝,像睡著了一般羔巢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天竿秆,我揣著相機與錄音启摄,去河邊找鬼。 笑死幽钢,一個胖子當著我的面吹牛歉备,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匪燕,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蕾羊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帽驯?” 一聲冷哼從身側(cè)響起龟再,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎界拦,沒想到半個月后吸申,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡享甸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年截碴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蛉威。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡日丹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蚯嫌,到底是詐尸還是另有隱情哲虾,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布择示,位于F島的核電站束凑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏栅盲。R本人自食惡果不足惜汪诉,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谈秫。 院中可真熱鬧扒寄,春花似錦、人聲如沸拟烫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽硕淑。三九已至课竣,卻和暖如春嘉赎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稠氮。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工曹阔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隔披。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓赃份,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奢米。 傳聞我的和親對象是個殘疾皇子抓韩,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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