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

上節(jié)說了ConcurrentHashMap,之前的知識會映射到今天的內(nèi)容點上面,學(xué)了這些方法到底怎么用,更多List,Set,Queue要去看源碼的時候,掌握現(xiàn)有知識點,源碼對你難度不太大了,里面的變量命名比較麻煩。本次說說List,重要的說里面的原理,使用這塊老鐵們應(yīng)該都明白。

(一)ArrayList

  • ① 介紹

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

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

ArrayList默認(rèn)有一個空的數(shù)組, 數(shù)據(jù)的順序插入,如果當(dāng)前的數(shù)組長度不夠存儲的時候,進(jìn)行擴(kuò)容處理,直接去創(chuàng)建一個新的數(shù)組,創(chuàng)建完成之后,把數(shù)組進(jìn)行拷貝,本身是線程非安全的,不要一邊遍歷,一邊刪除代碼。擴(kuò)容的時候存在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
讀寫分離,寫時復(fù)制出一個新的數(shù)組,完成插入。修改或者移除操作后將新數(shù)組賦值給array

(三)ArrayList 和 CopyOnWriteArrayList

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

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

(四)Set

  • ① 介紹

無序(沒有下標(biāo)) 集合中的元素不重復(fù)。

  • ② Set集合
  • ③ HashSet

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

  • ④ CopyOnWriteArraySet

CopyOnWriteArrayList中允許有重復(fù)的元素;但是,CopyOnWriteArraySet是一個集合,所以它不能有重復(fù)集合,因此,CopyOnWriteArrayList額外提供了addIfAbsent()和addAllAbsent()這兩個添加元素的API,通過這些API來添加元素時,只有當(dāng)元素不存在時才執(zhí)行添加操作!

  • ⑤ ConcurrentSkipListSet

所有操作都是無阻塞的,所有操作都可以并行,包括寫,實現(xiàn)了ConcurrentMap接口,直接支持一些原子復(fù)合操作(與ConcurrentHashMap類似),可排序(與TreeMap一樣),默認(rèn)按鍵自然有序,可以傳遞比較器自定義排序,實現(xiàn)了SortedMap和NavigableMap接口。

(五)Queue

  • ① 介紹

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

  • ② 阻塞和非阻塞
  1. 我去買一本書,立即買到了,或者沒有就走了,這就是非阻塞;(編程中設(shè)置IO成非阻塞,返回后再去檢查描述符,或者等待通知,然后再去讀取。相當(dāng)于老板告訴我可以先忙點別的,過一會再來問問,或者老板通知我。但期間這個窗口(文件描述符)別人是用不了的)("立即買到了"在IO中也需要等待,不能算非阻塞IO)
  2. 如果恰好書店沒有,我就等一直等到書店有了這本書買到了才走,這就是阻塞;而排在我后面的人呢只有我買到了書后才能再買書了。
  3. 如果書店恰好沒有,我就告訴書店老板,書來了告訴我一聲讓我來取或者直接送到我家,然后我就走了,去做別的事了,這就是異步。這時候如果很多人來買書,都是老板登記一下完事。 (從IO角度來說,“告訴我來取”,這個近似于信號驅(qū)動IO,不能算異步IO。必須書送到我家才算是異步,如果不送到我家,我想看這本書之前,終究還是需要我跑一趟)
  4. 前面兩種情況,非阻塞和阻塞都可以稱為同步。
  • ③ 常用方法
  • ④ ArrayBlockingQueue

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

import java.util.concurrent.ArrayBlockingQueue;


// 它是基于數(shù)組的阻塞循環(huán)隊列, 此隊列按 FIFO(先進(jìn)先出)原則對元素進(jìn)行排序。
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 構(gòu)造時需要指定容量(量力而行),可以選擇是否需要公平(最先進(jìn)入阻塞的,先操作)
        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阻塞(如果當(dāng)前的隊列已經(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,拿到一把鎖,然后進(jìn)行入隊列,數(shù)量++,索引自行維護(hù)putIndex。
移除:數(shù)量--,找到對應(yīng)的節(jié)點,lock設(shè)置了一把鎖。索引自行維護(hù)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。
// 當(dāng)一個元素的 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 {

    // 判斷當(dāng)前這個元素,是不是已經(jīng)到了需要被拿出來的時間
    @Override
    public long getDelay(TimeUnit unit) {
        // 默認(rèn)納秒
        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(先進(jìn)先出)排序元素。
// 如果有阻塞需求,用這個。類似生產(chǎn)者消費者場景
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 構(gòu)造時可以指定容量,默認(rèn)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)先級的 隊列,而不是先進(jìn)先出隊列。
// 元素按優(yōu)先級順序被移除,該隊列也沒有上限
// 沒有容量限制的,自動擴(kuò)容
// 雖然此隊列邏輯上是無界的,但是由于資源被耗盡,所以試圖執(zhí)行添加操作可能會導(dǎo)致 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)先級的 隊列,而不是先進(jìn)先出隊列。
元素按優(yōu)先級順序被移除,該隊列也沒有上限, 沒有容量限制的,自動擴(kuò)容。
雖然此隊列邏輯上是無界的,但是由于資源被耗盡,所以試圖執(zhí)行添加操作可能會導(dǎo)致 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("準(zhǔn)備賽數(shù)據(jù)了");
        synchronousQueue.put("a");// 等待有人取走他
    }
}

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,746評論 2 370

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