Java高併發系列4-併發容器
接上一篇Java高併發系列3-再寫單例我們繼續,
併發容器在高併發中有這舉足輕重的地步,這一篇我們主要看併發容器。
1、併發List
在List下中有ArrayList 、LinkedList 、Vector 三種數據結構,其中Vector屬於線程安全的。
在List下還有CopyOnWriteArrayList類實現的List接口,它也是線程安全的。
CopyOnWriteArrayList與Vector進行對比:
(1)鎖的位置
CopyOnWriteArrayList的實現是在讀操作中去除鎖,而寫中有鎖並且多了複製操作。
Vector在讀操作和寫操作中都添加了鎖。
(2)速度比較
CopyOnWriteArrayList因爲在讀操作中去除了鎖,所以其速度比Vector快。但是CopyOnWriteArrayList中多了複製操作,所以其寫的速度要比Vect慢。
個人推薦:在讀特別多寫少時用CopyOnWriteArrayList , 如果讀少寫多時,用Vector
看一條程序
```
public class CopyOnWriteList {
public static void main(String[] args) {
List lists =
//new ArrayList<>(); //這個會出併發問題!
//new Vector();
new CopyOnWriteArrayList<>(); /// 寫的情況少,讀的情況特別多。
Random r = new Random();
Thread[] ths = new Thread[100];
for(int i=0; i<ths.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));
}
};
ths[i] = new Thread(task);
}
runAndComputeTime(ths);
System.out.println(lists.size());
}
static void runAndComputeTime(Thread[] ths) {
long s1 = System.currentTimeMillis();
Arrays.asList(ths).forEach(t->t.start());
Arrays.asList(ths).forEach(t->{
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long s2 = System.currentTimeMillis();
System.out.println(s2 - s1);
}
}
```
當然也可以使用Collections.sync xxx 進行封裝加鎖。
2、併發Set
與List類似,Set也有一個CopyOnWriteArraySet,它實現了Set接口,線程安全。
CopyOnWriteArraySet,其內部實現完全依賴於CopyOnWriteArrayList,所以CopyOnWriteArrayList所具有的特性,CopyOnWriteArraySet全具有。
個人推薦:在讀多寫少時,用CopyOnWriteArraySet。
3、併發Map
看一條程序,
```
public class ConcurrentMap {
public static void main(String[] args) {
//Map<String, String> map = new ConcurrentHashMap<>();
Map<String, String> map = new ConcurrentSkipListMap<>(); //高併發並且排序
//Map<String, String> map = new Hashtable<>();
//Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX
//TreeMap
Random r = new Random();
Thread[] ths = new Thread[100];
CountDownLatch latch = new CountDownLatch(ths.length);
long start = System.currentTimeMillis();
for(int i=0; i<ths.length; i++) {
ths[i] = new Thread(()->{
for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));
latch.countDown();
});
}
Arrays.asList(ths).forEach(t->t.start());
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
```
運行結果就不粘貼了,
Map下有HashMap、HashTable(子類LinkedHashMap)、TreeMap、ConcurrentHashMap。其中線程安全的是HashTable和ConcurrentHashMap, ConcurrentSkipListMap 。
ConcurrentHashMap與HashTable進行對比:
ConcurrentHashMap 的get()中不加鎖,put()中又使用減少鎖粒度的方式來進行同步的,而不是像HashTable使用synchronized簡單的進行同步,所以其效率比HashTable高.
ConcurrentSkipListMap 的具體原理 參考 https://blog.csdn.net/sunxianghuang/article/details/52221913
鎖粒度:
拿ConcurrentHashMap來說,他不是將整個HashMap進行加鎖,而是將HashMap分成16段 ,需要put()操作時,根據其hashcode獲取
該段,對該段進行加特定的鎖,其他段可以被其他線程繼續使用加鎖。 所以實現上是ConcurrentHashMap 的鎖的顆粒度更細, 從而更高效 並不是沒有上鎖。
個人推薦:如果不考慮安全性,使用HashMap。考慮安全性使用ConcurrentHashMap, 如果併發量比較大 並且要求是排好序的使用ConcurrentSkipListMap 。
4、併發Queue
看一條程序
```
public class ConcurrentQueue {
public static void main(String[] args) {
Queue<String> strs = new ConcurrentLinkedQueue<>();
for(int i=0; i<10; i++) {
/// offer 添加到隊尾 返回添加結果 ,是否成功。
strs.offer("a" + i); //add 添加沒結果
}
System.out.println(strs);
System.out.println(strs.size());
System.out.println(strs.poll()); /// 讀取隊列頭部,並且移除
System.out.println(strs.size());
System.out.println(strs.peek()); //讀取隊列頭部,不移除
System.out.println(strs.size());
}
}
```
再看一條程序
```
public class T05_LinkedBlockingQueue {
static BlockingQueue<String> strs = new LinkedBlockingQueue<>();
static Random r = new Random();
public static void main(String[] args) {
/// 生產者
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
strs.put("a" + i); //如果滿了,就會等待
TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "p1").start();
/// 消費者
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (;;) {
try {
System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //如果空了,就會等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "c" + i).start();
}
}
}
```
再看一條
```
public class ArrayBlockingQueue {
/// 有界阻塞隊列 ,容量爲10
static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);
static Random r = new Random();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
strs.put("a" + i);
}
strs.put("aaa"); //滿了就會等待,程序阻塞
//strs.add("aaa");
//strs.offer("aaa");
//strs.offer("aaa", 1, TimeUnit.SECONDS);
System.out.println(strs);
}
}
```
併發隊列有兩類,一類支持高併發的ConcurrentLinkedQueue,另一類是阻塞隊列BlockingQueue。
詳解:
ConcurrentLinkedQueue採用的是無鎖的方式,所以其性能在高併發中很好。
BlockingQueue採用的是生產者消費者模式的方式進行加鎖。
Blocking的具體實現有ArrayBlockingQueue 有界隊列 和LinkedBlockingQueue 無界隊列
再來一條程序
我們來看一條TransferQueue的測試
public class T08_TransferQueue {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
/*
new Thread(() -> {
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();*/
// strs.transfer("aaa");
strs.put("aaa");
new Thread(() -> {
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
再來個SynchronousQueue測試
public class SynchronusQueue { //容量爲0
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> strs = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(strs.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
strs.put("aaa"); //阻塞等待消費者消費
//strs.add("aaa");
System.out.println(strs.size());
}
}
TransferQueue 和 SynchronusQueue 是用於高併發 ,需要及時轉發的隊列, 隊列的容量爲0 。 transfer , put
個人推薦:如果需要高併發下有高性能,使用ConcurrentLinkedQueue。如果想要實現數據在多線程中共享,使用BlockingQueue。
5、併發Dueue(雙端隊列)
Dueue的具體實現有LinkedBlockingDueue。Dueue與Queue相比,Dueue繼承了Queue,所以它的功能更 多。但是LinkedBlockingDueue的性能遠遠低於LinkedBlockingQueue,更低於ConcurrenLinkedQueue。
6、定時隊列 DelayQueue執行定時任務
看一條程序
```
public class DelayQueue {
static BlockingQueue<MyTask> tasks = new DelayQueue<>();
static Random r = new Random();
static class MyTask implements Delayed {
long runningTime;
MyTask(long rt) {
this.runningTime = rt;
}
@Override
public int compareTo(Delayed o) {
if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
return -1;
else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS))
return 1;
else
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public String toString() {
return "" + runningTime;
}
}
public static void main(String[] args) throws InterruptedException {
long now = System.currentTimeMillis();
MyTask t1 = new MyTask(now + 1000);
MyTask t2 = new MyTask(now + 2000);
MyTask t3 = new MyTask(now + 1500);
MyTask t4 = new MyTask(now + 2500);
MyTask t5 = new MyTask(now + 500);
tasks.put(t1);
tasks.put(t2);
tasks.put(t3);
tasks.put(t4);
tasks.put(t5);
System.out.println(tasks);
for(int i=0; i<5; i++) {
System.out.println(tasks.take());
}
}
}
```
好了, 囉裏囉嗦,說了一大通,在開發中如果不確定使用哪一種,可以根據併發容器各自的特點選擇適合自己的應用場景。 當然還有系統封裝好的併發容器, 使用起來可靠性更好,畢竟經過了長時間的市場測試和磨礪。
東西比較多,如果有什麼不對的,請批評指正。 這篇就先說到這裏,下篇我們再見。