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