目錄
jdk-api中文手冊
常用的併發工具類
CountDownLatch:允許一個或多個線程等待直到在其他線程中執行的一組操作完成的同步輔助。
可以理解爲:統計xx-x的航班票數,必須等到多個航空公司(多線程)都統計完成後才能彙總展示給用戶。
用法:CountDownLatch latch=new CountDownLatch(number),先初始化一個number;再多線程情況下
每調用一次countDown()方法,計數-1直到number爲0時
,再配合await()使用;如果當前計數大於零,
則當前線程將被禁用以進行線程調度,並處於休眠狀態。
常用方法:
-
public void countDown();
如果當前計數大於零,則它將遞減。 如果新計數爲零,則所有等待的線程都將被重新啓用以進行線程調度。
-
getCount
public long getCount();
返回當前計數。
該方法通常用於調試和測試。
-
await()
public void await() throws InterruptedException;
public boolean await(long timeout,TimeUnit unit) throws InterruptedException;
導致當前線程等到鎖存器計數到零,除非線程是interrupted 。
如果當前計數爲零,則此方法立即返回。
如果當前計數大於零,則當前線程將被禁用以進行線程調度,並處於休眠狀態,直至發生兩件事情之一:
- 由於
countDown()
方法的調用,計數達到零; 要麼一些其他線程interrupts當前線程。
示例:
public class CountDownLatchDemo {
private static CountDownLatch cdl = new CountDownLatch(3);
private static List<String> tasks = new ArrayList<String>();
public static void main(String[] args) throws InterruptedException {
final Thread[] threads = new Thread[3];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
int val = new Random().nextInt(5);
TimeUnit.SECONDS.sleep(val);
System.out.println(Thread.currentThread().getName() + " :完成任務【 " + val + "個】");
tasks.add(Thread.currentThread().getName() + " : " + val);
cdl.countDown();
}
});
threads[i].start();
threads[i].join();
}
//await()方法調用會校驗cdl.getCount() ==0,爲0則返回true,否則線程進入休眠.
cdl.await();
tasks.forEach(System.out::println);
}
}
CyclicBarrier:允許一組線程全部等待彼此達到共同屏障點的同步輔助。
可以理解爲:比如馬拉松比賽,必須等到所有參賽選手(多線程)都準備好,才能開始跑。
用法:CyclicBarrier barrier = new CyclicBarrier(10); 初始化一個屏障值number=10,初始值=0,每當線程調用一次await()則初始值+1、線程進入休眠狀態;當初始值=10時,此刻屏障破損,所有線程再一起執行。
常用方法:
public int await() throws
等待所有parties已經在這個障礙上調用了await
。
如果當前線程不是最後一個線程,那麼它被禁用以進行線程調度,並且處於休眠狀態,直到發生下列事情之一:
-
最後一個線程到達; 要麼一些其他線程當前線程爲interrupts ;
-
要麼一些其他線程interrupts其他等待線程之一;
-
要麼一些其他線程在等待屏障時超時;
-
要麼其他一些線程在這個屏障上調用
reset()
。
public int await(long timeout,
TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException
等待所有parties已經在此屏障上調用await
,或指定的等待時間過去。
如果當前線程不是最後一個線程,那麼它被禁用以進行線程調度,並且處於休眠狀態,直到發生下列事情之一:
- 最後一個線程到達; 要麼
- 超過指定的超時 要麼
- 一些其他線程當前線程interrupts ; 要麼
- 其他一些線程interrupts其他等待線程; 要麼
- 一些其他線程在等待屏障時超時; 要麼
- 其他一些線程在這個障礙上調用
reset()
。
-
isBroken
public boolean isBroken()
查詢這個障礙是否處於破碎狀態。
結果
true
如果一個或多個參與方因施工或最後一次重置而導致中斷或超時,或由於異常而導致屏障動作失敗,則從此出現障礙;false
否則。
-
reset
public void reset()
將屏障重置爲初始狀態。 如果任何一方正在等待屏障,他們將返回
BrokenBarrierException
。 注意,由於其他原因,發生斷線後的復位可能會複雜化; 線程需要以其他方式重新同步,並選擇一個執行重置。 可能更好地爲後續使用創建新的屏障。
-
getNumberWaiting
public int getNumberWaiting()
返回目前正在等待障礙的各方的數量。 此方法主要用於調試和斷言。
結果
目前受阻於各方的數量
await()
示例:
public static void main(String[] args) {
//模擬馬拉松跑步
CyclicBarrier barrier = new CyclicBarrier(10);
Thread[] player = new Thread[10];
for (int i = 0; i < player.length; i++) {
player[i] = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
System.out.println(Thread.currentThread().getName() + " ok ");
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("player:" + Thread.currentThread().getName() + " running ");
}, "player[" + i + "]");
player[i].start();
}
}
Semaphore:一個計數信號量。 在概念上,信號量維持一組許可證。 如果有必要,每個acquire()
都會阻塞,
直到許可證可用,然後才能使用它。 每個release()
添加許可證,潛在地釋放阻塞獲取方。 但是沒有使用實際的
許可證對象; Semaphore
只保留可用數量的計數,並相應地執行。
可以理解:停車場業務,比如停車場只有10個車位,每輛車進入停車場則獲取一個許可證,當許可證(信號量+1)=10時,不允許停車了(阻塞),當出去一輛車,則釋放一個許可證(信號量-1),此刻通知還可以停一輛車,有效的控制停車流量。
使用方法:先初始化一個計數信號量count : Semaphore sp=new Semaphore(5);當線程獲得調用acquire()獲得許可憑證,則count+1,直到count = 5 /此刻如果再有線程調用則不能獲取許可進入休眠;當獲取許可的線程調用release()則釋放資源,其他等待的線程繼續獲取許可。
常用方法:
-
Semaphore
public Semaphore(int permits)
創建一個
Semaphore
與給定數量的許可證和非公平公平設置。參數
permits
-permits
的初始許可證。 該值可能爲負數,在這種情況下,必須在任何獲取被授予之前發佈釋放。
如果當前線程:
然後InterruptedException
被關上,當前線程的中斷狀態被清除。
-
Semaphore
public void acquire()
throws InterruptedException從此信號量獲取許可證,阻止直到可用,否則線程爲interrupted 。
獲得許可證,如果有可用並立即返回,則將可用許可證數量減少一個。
如果沒有可用的許可證,那麼當前線程將被禁用以進行線程調度,並且處於休眠狀態,直至發生兩件事情之一:
-
一些其他線程調用此信號量的
release()
方法,當前線程旁邊將分配一個許可證; 要麼 -
一些其他線程interrupts當前線程。
-
在進入該方法時設置了中斷狀態; 要麼
-
是interrupted等候許可證,
常用併發容器
ConcurrentLinkedDeque:非阻塞式集合(Non-Blocking Collection),這類集合也包括添加和移除數據的方法。如果方法不能立即被執行,則返回null或拋出異常,但是調用這個方法的線程不會被阻塞。
Constructor and Description |
---|
ConcurrentLinkedDeque()
構造一個空的德克。 |
ConcurrentLinkedDeque(Collection<? extends E> c)
構造最初包含給定集合的元素的deque,以集合的迭代器的遍歷順序添加。 |
boolean |
add(E e)
在此deque的尾部插入指定的元素。 |
boolean |
addAll(Collection<? extends E> c)
按指定集合的迭代器返回的順序將指定集合中的所有元素追加到該deque的末尾。 |
void |
addFirst(E e)
在此deque前面插入指定的元素。 |
void |
addLast(E e)
在此deque的末尾插入指定的元素。 |
void |
clear()
從這個deque中刪除所有的元素。 |
boolean |
contains(Object o)
返回 |
E |
pop()
從這個deque表示的堆棧中彈出一個元素。 |
void |
push(E e)
將元素推送到由此deque代表的堆棧(換句話說,在該deque的頭部),如果可以立即執行,而不違反容量限制,則拋出 |
E |
remove()
檢索並刪除由此deque表示的隊列的頭(換句話說,該deque的第一個元素)。 |
boolean |
remove(Object o)
刪除第一個元素 |
示例:
public static void main(String[] args) throws InterruptedException {
ConcurrentLinkedDeque<String> cld = new ConcurrentLinkedDeque();
//添加數據
Thread[] add = new Thread[100];
for (int i = 0; i < 100; i++) {
add[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
cld.add(Thread.currentThread().getName() + ":Element " + j);
}
});
add[i].start();
add[i].join();
}
System.out.println("after add size:" + cld.size());
//移除數據
Thread[] poll = new Thread[100];
for (int i = 0; i < 100; i++) {
poll[i] = new Thread(() -> {
for (int j = 0; j < 500; j++) {
cld.pollLast();
cld.pollFirst();
}
});
poll[i].start();
poll[i].join();
}
System.out.println("after poll size:" + cld.size());
}
LinkedBlockingDeque:阻塞式集合(Blocking Collection),這類集合包括添加和移除數據的方法。當集合已滿或爲空時,被調用的添加或者移除方法就不能立即被執行,那麼調用這個方法的線程將被阻塞,一直到該方法可以被成功執行。
LinkedBlockingDeque()
創建一個 |
LinkedBlockingDeque(Collection<? extends E> c)
創建一個 |
LinkedBlockingDeque(int capacity)
創建一個具有給定(固定)容量的 |
public static void main(String[] args) throws InterruptedException {
LinkedBlockingDeque<String> list = new LinkedBlockingDeque();
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
String str = new String(i + ":" + j);
try {
list.put(str.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("put: " + str + (new Date()));
}
}
});
thread.start();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 5; j++) {
String str = list.take();
System.out.println("take: " + str + " size:" + list.size());
TimeUnit.SECONDS.sleep(2);
}
}
}
併發容器有哪些分類
1.ConcurrentHashMap
對應的非併發容器:HashMap
目標:代替Hashtable、synchronizedMap,支持複合操作
原理:JDK6中採用一種更加細粒度的加鎖機制Segment“分段鎖”,JDK8中採用CAS無鎖算法。
2.CopyOnWriteArrayList
對應的非併發容器:ArrayList
目標:代替Vector、synchronizedList
原理:利用高併發往往是讀多寫少的特性,對讀操作不加鎖,對寫操作,先複製一份新的集合,在新的集合上面修改,然後將新集合賦值給舊的引用,並通過volatile 保證其可見性,當然寫操作的鎖是必不可少的了。
3.CopyOnWriteArraySet
對應的非併發容器:HashSet
目標:代替synchronizedSet
原理:基於CopyOnWriteArrayList實現,其唯一的不同是在add時調用的是CopyOnWriteArrayList的addIfAbsent方法,其遍歷當前Object數組,如Object數組中已有了當前元素,則直接返回,如果沒有則放入Object數組的尾部,並返回。
4.ConcurrentSkipListMap
對應的非併發容器:TreeMap
目標:代替synchronizedSortedMap(TreeMap)
原理:Skip list(跳錶)是一種可以代替平衡樹的數據結構,默認是按照Key值升序的。
5.ConcurrentSkipListSet
對應的非併發容器:TreeSet
目標:代替synchronizedSortedSet
原理:內部基於ConcurrentSkipListMap實現
6.ConcurrentLinkedQueue
不會阻塞的隊列
對應的非併發容器:Queue
原理:基於鏈表實現的FIFO隊列(LinkedList的併發版本)
7.LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue
對應的非併發容器:BlockingQueue
特點:拓展了Queue,增加了可阻塞的插入和獲取等操作
原理:通過ReentrantLock實現線程安全,通過Condition實現阻塞和喚醒
實現類:
- LinkedBlockingQueue:基於鏈表實現的可阻塞的FIFO隊列
- ArrayBlockingQueue:基於數組實現的可阻塞的FIFO隊列
- PriorityBlockingQueue:按優先級排序的隊列