ConcurrentLinkedQueue
線程安全的支持高并發(fā)的隊列,使用鏈表實現(xiàn)。非阻塞,無鎖,無界。該隊列也不允許空元素,而且size方法并不是常量,其需要遍歷鏈表,此時并發(fā)修改鏈表會造成統(tǒng)計size不正確。同樣,bulk操作和equal以及toArray方法不保證原子性。
代碼實現(xiàn):
public class ConcurrentLinkedQueueTest {
public static void main(String[] args) {
final ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
// 往隊列中執(zhí)行添加的任務
Runnable offerTask = () -> {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 10000; i++) {
queue.offer(threadName + i);
}
};
// 往隊列中執(zhí)行移除的任務
Runnable pollTask = () -> {
for (int i = 0; i < 10000; i++) {
queue.poll();
}
};
Thread[] threads = new Thread[100];
// 100個offerTask線程
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(offerTask);
threads[i].start();
}
// 主線程等待生產線程執(zhí)行完畢
for (int i = 0; i < 100; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("當前隊列size:" + queue.size());
Assert.assertEquals(10000 * 100, queue.size());
// 100個pollTask線程
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(pollTask);
threads[i].start();
}
// 主線程等待消費線程執(zhí)行完畢
for (int i = 0; i < 100; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("當前隊列size:" + queue.size());
Assert.assertEquals(0, queue.size());
}
}
ConcurrentLinkedDeque
并發(fā)隊列ConcurrentLinkedDeque,這是一個非阻塞,無鎖,無界,線程安全雙端操作的隊列。簡單說就是ConcurrentLinkedQueue的升級版,在JDK7之后才提供。該隊列也不允許空元素,而且size方法并不是常量,其需要遍歷鏈表,此時并發(fā)修改鏈表會造成統(tǒng)計size不正確。同樣,bulk操作和equal以及toArray方法不保證原子性。
主要API介紹
- 不拋異常,會移除
pollFirst()
和pollLast()
:返回并移除隊列中第一個元素和最后一個元素,如果隊列為空,返回null - 不拋異常,不會移除
peek()
、peekFirst()
和peekLast()
:這些方法將分別返回列表的第一個和最后一個元素。它們不會從列表刪除返回的元素。如果列表為空,這些方法將返回null值。 - 拋異常,會移除
remove()
、removeFirst()
、removeLast()
:這些方法將分別返回列表的第一個和最后一個元素。它們將從列表刪除返回的元素。如果列表為空,這些方法將拋出NoSuchElementExcpetion異常。 - 拋異常,不會移除
getFirst()
和getLast()
:這些方法將分別返回列表的第一個和最后一個元素。它們不會從列表刪除返回的元素。如果列表為空,這些方法將拋出NoSuchElementExcpetion異常。
代碼實現(xiàn)
public class AddTask implements Runnable {
private ConcurrentLinkedDeque<String> deque;
public AddTask(ConcurrentLinkedDeque<String> deque) {
this.deque = deque;
}
// 在列表中存儲10000個正在執(zhí)行任務的線程的名稱和一個數(shù)字的字符串。
@Override
public void run() {
String name = Thread.currentThread().getName();
for(int i = 0; i < 10000; i++) {
deque.add(name + ": Element " + i);
}
}
}
public class PollTask implements Runnable {
private ConcurrentLinkedDeque<String> deque;
public PollTask(ConcurrentLinkedDeque<String> deque) {
this.deque = deque;
}
// 從列表中取出10000個元素(在一個循環(huán)5000次的循環(huán)中,每次取出2個元素)。
@Override
public void run() {
for(int i = 0; i < 5000; i++) {
// 返回并移除隊列中第一個元素,如果隊列為空,返回null
deque.pollFirst();
// 返回并隊列中最后一個元素,如果隊列為空,返回null
deque.pollLast();
}
}
}
public class Main {
public static void main(String[] args) {
final ConcurrentLinkedDeque<String> deque = new ConcurrentLinkedDeque<>();
Thread[] threads = new Thread[100];
// 創(chuàng)建100個AddTask,并啟動線程,每一個task都會在list中加入10000個元素,即最終應該是1000000個元素
for(int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new AddTask(deque));
threads[i].start();
}
System.out.printf("Main: %d AddTask threads have been launched\n", threads.length);
// 讓Main線程等待所有AddTask線程執(zhí)行完畢后才能繼續(xù)執(zhí)行
for(int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Main: Size of the List: %d\n", deque.size());
// 創(chuàng)建100個PollTask,并啟動線程,每一個task會從list中取出10000個元素,即list最終應剩余0個元素
for(int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new PollTask(deque));
threads[i].start();
}
System.out.printf("Main: %d PollTask threads have been launched\n", threads.length);
// 讓Main線程等待所有PollTask線程執(zhí)行完畢后才能繼續(xù)執(zhí)行
for(int i = 0; i < threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Main: Size of the List: %d\n", deque.size());
}
}
DelayQueue
此隊列中的元素必須實現(xiàn)Delayed接口,其實也是一個阻塞隊列。
代碼實現(xiàn)
// 事件類
public class Event implements Delayed {
// 到期時間
private Date startDate;
public Event(Date startDate) {
this.startDate = startDate;
}
// 此方法返回此延遲對象剩余多少時間到期,根據(jù)給定的TmeUnit表示
@Override
public long getDelay(TimeUnit unit) {
Date now = new Date();
long diff = startDate.getTime() - now.getTime();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
// 可以理解為,延遲隊列根據(jù)此方法的返回值,排列延遲對象在延遲隊列中的位置
// 這里表達的意思是: 距離到期時間越遠的延遲對象,放到延遲隊列的越后面
@Override
public int compareTo(Delayed o) {
long result = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
if (result < 0) {
return -1;
} else if (result > 0) {
return 1;
} else {
return 0;
}
}
}
// 任務類
public class Task implements Runnable {
private int id;
private DelayQueue<Event> queue;
public Task(int id, DelayQueue<Event> queue) {
this.id = id;
this.queue = queue;
}
@Override
public void run() {
Date now = new Date();
Date delay = new Date();
delay.setTime(now.getTime() + (id * 4000));
System.out.printf("Thread %s: %s\n", id, delay);
for (int i = 0; i < 100; i++) {
Event event = new Event(delay);
queue.add(event);
}
}
}
// 主類
/*
DelayedQueue類是Java API提供的一種有趣的數(shù)據(jù)結構,并且你可以用在并發(fā)應用程序中。
在這個類中,你可以存儲帶有激活日期的元素。方法返回或抽取隊列的元素將忽略未到期的數(shù)據(jù)元素。它們對這些方法來說是看不見的。
為了獲取這種行為,你想要存儲到DelayedQueue類中的元素必須實現(xiàn)Delayed接口。這個接口允許你處理延遲對象,
所以你將實現(xiàn)存儲在DelayedQueue對象的激活日期,這個激活時期將作為對象的剩余時間,直到激活日期到來。
這個接口強制實現(xiàn)以下兩種方法:
1.compareTo(Delayed o):Delayed接口繼承Comparable接口。
如果執(zhí)行這個方法的對象的延期小于作為參數(shù)傳入的對象時,該方法返回一個小于0的值。
如果執(zhí)行這個方法的對象的延期大于作為參數(shù)傳入的對象時,該方法返回一個大于0的值。
如果這兩個對象有相同的延期,該方法返回0。
2.getDelay(TimeUnit unit):該方法返回與此對象相關的剩余延遲時間,以給定的時間單位表示。
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
// 一個共享延遲隊列
DelayQueue<Event> queue = new DelayQueue<>();
Thread threads[] = new Thread[5];
// 5個線程
for (int i = 0; i < threads.length; i++) {
Task task = new Task(i + 1, queue);
threads[i] = new Thread(task);
}
// 啟動這5個線程,每個線程往延遲隊列中放入100個Event對象,并指定Event對象的StartDate
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
// 等待線程執(zhí)行完畢
for (int i = 0; i < threads.length; i++) {
threads[i].join();
}
System.out.println(queue.size());
// 當延遲隊列中有數(shù)據(jù)時,每500毫秒從延遲隊列中
do {
int counter = 0;
Event event;
do {
// 只有當延遲對象到期后才能從queue中取到
// 注意: 這里如果使用take方法獲取,如果獲取不到會阻塞.而poll方法獲取不到會返回空
event = queue.poll();
if (event != null) counter++;
} while (event != null);
System.out.printf("At %s you have read %d events\n", new Date(), counter);
TimeUnit.MILLISECONDS.sleep(500);
} while (queue.size() > 0);
}
}
ArrayBlockingQueue
有界阻塞隊列,數(shù)組實現(xiàn)。
代碼實現(xiàn)
public class ArrayBlockingQueueTest {
public static void main(String[] args) {
// 用于記錄生產總量
AtomicInteger putCount = new AtomicInteger(0);
// 用于記錄消費總量
AtomicInteger takeCount = new AtomicInteger(0);
final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
// putTask負責生產數(shù)據(jù)放入queue
Runnable putTask = () -> {
String threadName = Thread.currentThread().getName();
for(int i = 0; i < 10000; i++) {
try {
// 向queue中放入數(shù)據(jù)。當queue中的元素數(shù)量已滿,則阻塞等待,等到有人消費掉,再向queue中放入數(shù)據(jù)
queue.put(threadName + i);
putCount.incrementAndGet();
System.out.println(threadName + ":" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// takeTask負責從queue消費數(shù)據(jù)
Runnable takeTask = () -> {
String threadName = Thread.currentThread().getName();
for(int i = 0; i < 10000; i++) {
try {
// 從queue中取出數(shù)據(jù)。當queue中沒有元素時,則阻塞等待,等到有人生產出數(shù)據(jù)放入queue時再進行消費
queue.take();
takeCount.incrementAndGet();
System.out.println(threadName + ":" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread[] putThreads = new Thread[10];
Thread[] takeThreads = new Thread[10];
// 啟動10個生產者線程和10個消費者線程
for(int i = 0; i < 10; i++) {
Thread putThread = new Thread(putTask, "producer" + i);
putThreads[i] = putThread;
putThread.start();
Thread takeThread = new Thread(takeTask, "consumer" + i);
takeThreads[i] = takeThread;
takeThread.start();
}
// 等待生產者線程和消費者線程執(zhí)行完畢
for(int i = 0; i < 10; i++) {
try {
putThreads[i].join();
takeThreads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 輸出生產總量和消費總量
System.out.println(putCount.get());
System.out.println(takeCount.get());
}
}
LinkedBlockingQueue
無界阻塞隊列(也可以指定有界),鏈表實現(xiàn)。和LinkedBlockingDeque差不多,一個單向、一個雙向。
LinkedBlockingDeque
無界阻塞隊列(也可以指定有界),鏈表實現(xiàn)。和LinkedBlockingQueue差不多,一個單向、一個雙向。
主要API介紹
獲取:獲取到并移除/獲取不到就阻塞
takeFirst()
和takeLast()
:這些方法分別返回隊列的第一個和最后一個元素。它們從隊列刪除返回的元素。如果隊列為空,這些方法將阻塞線程,直到隊列有元素。獲取:獲取到并移除/獲取不到返回null
poll()
、pollFirst()
和pollLast()
:這些方法分別返回隊列的第一個和最后一個元素。它們從隊列刪除返回的元素。如果隊列為空,這些方法將返回null值。獲取:獲取到但不移除/獲取不到拋異常
getFirst()
和getLast()
:這些方法分別返回隊列的第一個和最后一個元素。它們不會從隊列刪除返回的元素。如果隊列為空,這些方法將拋出NoSuchElementExcpetion異常。獲取:獲取到但不移除/獲取不到返回null
peek()
、peekFirst()
,和peekLast()
:這些方法分別返回隊列的第一個和最后一個元素。它們不會從隊列刪除返回的元素。如果隊列為空,這些方法將返回null值。添加:超過容量的添加會拋出異常
add()
、addFirst()
、addLast()
:這些方法分別在第一個位置和最后一個位置上添加元素(add是在最后添加)。如果隊列已滿(你已使用固定大小創(chuàng)建它),這些方法將拋出IllegalStateException異常。
代碼實現(xiàn)
public class Client implements Runnable {
private LinkedBlockingDeque<String> deque;
public Client(LinkedBlockingDeque<String> deque) {
this.deque = deque;
}
@Override
public void run() {
// 每2秒往隊列中放入5個字符串,重復3次
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
StringBuilder request = new StringBuilder();
request.append(i);
request.append(":");
request.append(j);
try {
deque.put(request.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Client: %s at %s.\n", request, new Date());
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.printf("Client: End.\n");
}
}
public class Main {
/*
Client類使用put()方法添加字符串到隊列中。
如果隊列已滿(因為你已使用固定大小來創(chuàng)建它),這個方法阻塞線程的執(zhí)行,直到隊列有可用空間。
Main類使用take()方法從隊列中獲取字符串,
如果隊列為空,這個方法將阻塞線程的執(zhí)行,直到隊列中有元素。
在這個例子中,使用LinkedBlockingDeque類的這兩個方法,
如果它們在阻塞時被中斷,將拋出InterruptedException異常。
所以,你必須包含必要的代碼來捕捉這個異常。
*/
public static void main(String[] args) throws InterruptedException {
// 創(chuàng)建阻塞雙端隊列
LinkedBlockingDeque<String> deque = new LinkedBlockingDeque<>(3);
// Client每2秒會往這個雙端隊列中放入5個字符串,重復3次
Thread thread = new Thread(new Client(deque));
// 啟動線程
thread.start();
// 每300毫秒從隊列中取走3個元素,重復5次
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 3; j++) {
String request = deque.take();
System.out.printf("Main: Request: %s at %s. Size:%d\n", request, new Date(), deque.size());
}
TimeUnit.MILLISECONDS.sleep(300);
}
System.out.printf("Main: End of the program.\n");
}
}
PriorityBlockingQueue
優(yōu)先級隊列,所有存儲在PriorityBlockingQueue的元素必須實現(xiàn)Comparable接口。在使用add方法加入元素時,并不會進行排序,而是在第一次調用take或poll等方法時才會將隊列元素排序(根據(jù)compareTo方法作升序排列),然后取出優(yōu)先級最高的元素。
代碼實現(xiàn)
public class Event implements Comparable<Event> {
// 用來存儲已創(chuàng)建事件的線程數(shù)。
private int thread;
// 用來存儲事件的優(yōu)先級。
private int priority;
public Event(int thread, int priority) {
this.thread = thread;
this.priority = priority;
}
public int getThread() {
return thread;
}
public int getPriority() {
return priority;
}
// 實現(xiàn)compareTo()方法。它接收Event作為參數(shù),并且比較當前事件與參數(shù)的優(yōu)先級。
// 如果當前事件的優(yōu)先級更大,則返回-1,如果這兩個優(yōu)先級相等,則返回0,如果當前事件的優(yōu)先級更小,則返回1。
// 注意,這與大多數(shù)Comparator.compareTo()的實現(xiàn)是相反的。
// 即當前對象的優(yōu)先級越高,越應該排在隊列的最前方,可以理解為隊列中的排序是根據(jù)此方法作升序排列的
@Override
public int compareTo(Event e) {
if (this.priority > e.getPriority()) {
return -1;
} else if (this.priority < e.getPriority()) {
return 1;
} else {
return 0;
}
}
}
SynchronousQueue
同步阻塞隊列,直接隊列中add會拋異常,除非此時有一個線程正調用take向此隊列索取元素,那么隊列會將add進來的元素直接交給正在take的線程。
代碼實現(xiàn)
public class SynchronousQueueTest {
public static void main(String[] args) {
// 可以理解為這個阻塞隊列的大小為0,生產者想要往這個隊列中放數(shù)據(jù)是放不進的,
// 除非此時有線程在從此隊列中取數(shù)據(jù),那么此隊列會將生產者會生產出的數(shù)據(jù)直接交給消費者
// 當沒有消費者取數(shù)據(jù)時,各類添加操作的行為:
// put 阻塞
// offer 返回false
// add 拋出異常
final SynchronousQueue<Integer> queue = new SynchronousQueue<>();
// 生產者任務
new Thread(() -> {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
try {
queue.put(i);
// 永遠為0
System.out.println(threadName + ":" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消費者任務
new Thread(() -> {
String threadName = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
try {
queue.take();
// 永遠為0
System.out.println(threadName + ":" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
LinkedTransferQueue
TransferQueue也具有SynchronousQueue的所有功能,但是TransferQueue的功能更強大。
主要API介紹:
-
transfer(E e)
若當前存在一個正在等待獲取的消費者線程,即立刻將e移交之;否則將元素e插入到隊列尾部,并且當前線程進入阻塞狀態(tài),直到有消費者線程取走該元素。 -
tryTransfer(E e)
若當前存在一個正在等待獲取的消費者線程,則該方法會即刻轉移e,并返回true;若不存在則返回false,但是并不會將e插入到隊列中。這個方法不會阻塞當前線程,要么快速返回true,要么快速返回false。 -
hasWaitingConsumer()
和getWaitingConsumerCount()
用來判斷當前正在等待消費的消費者線程個數(shù)。 -
tryTransfer(E e, long timeout, TimeUnit unit)
若當前存在一個正在等待獲取的消費者線程,會立即傳輸給它; 否則將元素e插入到隊列尾部,并且等待被消費者線程獲取消費掉。若在指定的時間內元素e無法被消費者線程獲取,則返回false,同時該元素從隊列中移除。
代碼實現(xiàn)
public class TransferQueueTest1 {
public static void main(String[] args) throws InterruptedException {
final TransferQueue<String> queue = new LinkedTransferQueue<>();
// 1
new Thread(() -> {
try {
// 向空隊列中獲取數(shù)據(jù),這里會阻塞
System.out.println(Thread.currentThread().getName() + " : queue.take() = " + queue.take());
System.out.println(Thread.currentThread().getName() + " : queue.size() = " + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
// 2
new Thread(() -> {
try {
// transfer(E e):
// 若當前存在一個正在等待獲取的消費者線程,即立刻將元素移交給消費者線程,
// 否則將元素插入到隊列尾部,并且當前線程進入阻塞狀態(tài),直到有消費者線程取走該元素
// A元素直接移交成功,因為在此之前有一個消費者線程正在等待獲取
queue.transfer("A");
// B元素會移交不成功,所以會添加到隊列尾部
queue.transfer("B");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(3);
// 這里會獲取到B元素,B元素在之前無法直接移交,會放到隊列中,這里使用take方法直接獲取到
System.out.println(Thread.currentThread().getName() + " : queue.take() = " + queue.take());
// 此時隊列容量為0,隊列中所有元素均被取走
System.out.println(Thread.currentThread().getName() + " : queue.size() = " + queue.size());
}
}
public class TransferQueueTest2 {
public static void main(String[] args) throws InterruptedException {
final TransferQueue<String> queue = new LinkedTransferQueue<>();
// 1
new Thread(() -> {
try {
// 向空隊列中獲取數(shù)據(jù),這里會阻塞
System.out.println(Thread.currentThread().getName()
+ " : queue.take() = " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
TimeUnit.SECONDS.sleep(1);
// 2
new Thread(() -> {
// tryTransfer(E e):
// 若當前存在一個正在等待獲取的消費者線程,則該方法會即刻轉移e,并返回true;
// 若不存在則返回false,但是并不會將e插入到隊列中。
// 這個方法不會阻塞當前線程,要么快速返回true,要么快速返回false。
// A元素直接移交成功,因為在此之前有一個消費者線程正在等待獲取
System.out.println(Thread.currentThread().getName() +
" : queue.tryTransfer(\"A\") = " + queue.tryTransfer("A"));
// B元素會移交不成功,直接返回false,也不會將B放入隊列中
System.out.println(Thread.currentThread().getName() +
" : queue.tryTransfer(\"B\") = " + queue.tryTransfer("B"));
}).start();
TimeUnit.SECONDS.sleep(1);
// 此時隊列大小為0,因為tryTransfer不會將未成功移交的元素放入隊列
System.out.println(Thread.currentThread().getName() + " : queue.size() = " + queue.size());
}
}
ConcurrentSkipListMap
ConcurrentSkipListMap是ConcurrentNavigableMap的實現(xiàn)類,在內部實現(xiàn)中,它使用Skip List來存儲數(shù)據(jù)。Skip List是基于并行列表的數(shù)據(jù)結構,它允許我們獲取類似二叉樹的效率。使用它,你可以得到一個排序的數(shù)據(jù)結構,這比排序數(shù)列使用更短的訪問時間來插入、搜索和刪除元素。
主要API介紹
-
headMap(K toKey)
:K是參數(shù)化ConcurrentSkipListMap對象的Key值的類。返回此映射的部分視圖,其鍵值小于 toKey。 -
tailMap(K fromKey)
:K是參數(shù)化ConcurrentSkipListMap對象的Key值的類。返回此映射的部分視圖,其鍵大于等于 fromKey。 -
putIfAbsent(K key, V Value)
:如果key不存在map中,則這個方法插入指定的key和value。 -
pollLastEntry()
:這個方法返回并刪除map中最后一個元素的Map.Entry對象。 -
replace(K key, V Value)
:如果這個key存在map中,則這個方法將指定key的value替換成新的value。
代碼實現(xiàn)
public class Main {
public static void main(String[] args) throws InterruptedException {
ConcurrentSkipListMap<String, Contact> map = new ConcurrentSkipListMap<>();
Thread threads[] = new Thread[25];
int counter = 0;
// 創(chuàng)建并啟動25個線程,每個線程的任務就是往map中放入1000條數(shù)據(jù)
// key:A1001 value:new Contact(A, 1001)
for (char i = 'A'; i < 'Z'; i++) {
// Task (ConcurrentSkipListMap<String, Contact> map, String id)
Task task = new Task(map, String.valueOf(i));
threads[counter] = new Thread(task);
threads[counter].start();
counter++;
}
// 等待線程創(chuàng)建完畢
for (int i = 0; i < 25; i++) {
threads[i].join();
}
// 輸出map當前容量,即25000
System.out.printf("Main: Size of the map: %d\n", map.size());
Map.Entry<String, Contact> element;
Contact contact;
// 獲取第一個Entry
element = map.firstEntry();
contact = element.getValue();
System.out.printf("Main: First Entry: %s: %s\n", contact.getName(), contact.getPhone());
// 獲取最后一個Entry
element = map.lastEntry();
contact = element.getValue();
System.out.printf("Main: Last Entry: %s: %s\n", contact.getName(), contact.getPhone());
// 獲取key為A1996-B1002之間的數(shù)據(jù)作為子map,實際上取出的是A1996-B1001,即包左不包右的原則
System.out.printf("Main: Submap from A1996 to B1002: \n");
ConcurrentNavigableMap<String, Contact> submap = map.subMap("A1996", "B1002");
do {
// 獲取并移除第一個Entry
element = submap.pollFirstEntry();
if (element != null) {
contact = element.getValue();
System.out.printf("%s: %s\n", contact.getName(), contact.getPhone());
}
} while (element != null);
// 輸出map當前容量,即24994
System.out.printf("Main: Size of the map: %d\n", map.size());
}
}
ConcurrentHashMap
以下內容的原文地址:ConcurrentHashMap總結
并發(fā)編程實踐中,ConcurrentHashMap是一個經(jīng)常被使用的數(shù)據(jù)結構,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在線程安全的基礎上提供了更好的寫并發(fā)能力,但同時降低了對讀一致性的要求。ConcurrentHashMap的設計與實現(xiàn)非常精巧,大量的利用了volatile,final,CAS等lock-free技術來減少鎖競爭對于性能的影響。ConcurrentHashMap在JDK6,7,8中實現(xiàn)都不同。
JDK6與JDK7中的實現(xiàn)
ConcurrentHashMap采用了分段鎖的設計,只有在同一個分段內才存在競態(tài)關系,不同的分段鎖之間沒有鎖競爭。相比于對整個Map加鎖的設計,分段鎖大大的提高了高并發(fā)環(huán)境下的處理能力。但同時,由于不是對整個Map加鎖,導致一些需要掃描整個Map的方法(如size(), containsValue())需要使用特殊的實現(xiàn),另外一些方法(如clear())甚至放棄了對一致性的要求(ConcurrentHashMap是弱一致性的,具體請查看ConcurrentHashMap能完全替代HashTable嗎?)。
JDK8中的實現(xiàn)
ConcurrentHashMap在JDK8中進行了巨大改動,很需要通過源碼來再次學習下Doug Lea的實現(xiàn)方法。
它摒棄了Segment(鎖段)的概念,而是啟用了一種全新的方式實現(xiàn),利用CAS算法。它沿用了與它同時期的HashMap版本的思想,底層依然由“數(shù)組”+鏈表+紅黑樹的方式思想(JDK7與JDK8中HashMap的實現(xiàn)),但是為了做到并發(fā),又增加了很多輔助的類,例如TreeBin,Traverser等對象內部類。
代碼實現(xiàn)
public class ConcurrentHashMapTest {
public static long execute(Map<String, String> map) {
Random random = new Random();
Thread[] threads = new Thread[100];
long start = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
final int x = i;
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
map.put(String.valueOf(random.nextInt(100000)), x + "" + j);
}
});
}
for (int i = 0; i < 100; i++) {
threads[i].start();
}
for (int i = 0; i < 100; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return System.currentTimeMillis() - start;
}
public static void main(String[] args) {
Map<String, String> hashtable = new Hashtable<>();
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
Map<String, String> concurrentSkipListMap = new ConcurrentSkipListMap<>();
long total = 0;
for (int i = 0; i < 10; i++) {
total += execute(hashtable);
}
System.out.println("hashtable: " + total); // 3942
total = 0;
for (int i = 0; i < 10; i++) {
total += execute(concurrentHashMap);
}
System.out.println("concurrentHashMap: " + total); // 1258
total = 0;
for (int i = 0; i < 10; i++) {
total += execute(concurrentSkipListMap);
}
System.out.println("concurrentSkipListMap: " + total); // 2525
}
}
CopyOnWrite
以下內容的原文地址:聊聊并發(fā)-Java中的Copy-On-Write容器
Copy-On-Write簡稱COW,是一種用于程序設計中的優(yōu)化策略。其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,才會真正把內容Copy出去形成一個新的內容然后再改,這是一種延時懶惰策略。從JDK1.5開始Java并發(fā)包里提供了兩個使用CopyOnWrite機制實現(xiàn)的并發(fā)容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并發(fā)場景中使用到。
什么是CopyOnWrite容器?
CopyOnWrite容器即寫時復制的容器。通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行并發(fā)的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
實現(xiàn)原理
以下是CopyOnWriteArrayList的源碼,可以發(fā)現(xiàn)在添加的時候是需要加鎖的,否則多線程寫的時候會Copy出N個副本出來。讀的時候不需要加鎖,如果讀的時候有多個線程正在向list添加數(shù)據(jù),讀還是會讀到舊的數(shù)據(jù),因為寫的時候不會鎖住舊的list。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public E get(int index) {
return get(getArray(), index);
}
代碼實現(xiàn)
public class CopyOnWriteArrayListTest {
public static void main(String[] args) {
final List<String> list =
// Collections.synchronizedList(new ArrayList<>());
// new Vector<>();
// 并發(fā)寫時,CopyOnWriteArrayList性能遠不如synchronizedList和Vector
new CopyOnWriteArrayList<>();
Random random = new Random();
// 創(chuàng)建100個線程,每個線程的任務就是往list中放入1000條數(shù)據(jù)
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
Runnable task = () -> {
for (int j = 0; j < 1000; j++) {
list.add("a" + random.nextInt(10000));
}
};
threads[i] = new Thread(task);
}
long start = System.currentTimeMillis();
// 啟動所有線程
for (Thread thread : Arrays.asList(threads)) {
thread.start();
}
// 等待這些線程執(zhí)行完畢
for (Thread thread : Arrays.asList(threads)) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(System.currentTimeMillis() - start);
System.out.println(list.size());
}
}