同步(併發)類容器
同步(併發)類容器都是線程安全的,但在某些場景下可能需要加鎖來保護複合操作,如迭代(反覆訪問元素,遍歷容器所有元素)、跳轉(根據指定的順序找到當前元素的下一個元素),以及條件運算。
這些複合操作在多線程併發的修改容器時,可能會表現出意外的行爲,最典型的就是之前解析集合源碼時,講到的Fast-Fast機制,會拋出ConcurrentModificationException異常,這是早期迭代器設計的時候並沒有考慮併發修改的問題。
早期的同步(併發)類容器,如已經棄用的Vector、Hashtable,這些容器的同步功能其實都是用JDK底層的Collections.synchronized*方法去創建實現的。底層的機制無非都是用傳統的synchronized關鍵字對每個方法都進行同步,使得每次只能由一個線程訪問容器的狀態,狀態都是串行化,雖然實現了線程安全,但是嚴重降低了併發性,在多線程環境時,嚴重降低了吞吐量。
這顯然無法滿足現在系統的高併發需求,在保證線程安全的同時,也必須有足夠好的性能。
/** * 在之前的集合源碼解析中,可以得知有fail-fast機制,這是早期設計時,沒有考慮併發修改的問題。 * 所以就提供了Vector、Hashtable這些容器,底層都是用synchronized關鍵字對每個公用方法進行同步的。 */ public class C01Old { public static void main(String[] args) { Vector<String> vector = new Vector<String>(); for (int i = 0; i < 100; i++) { //這個方法底層就是加了synchronized vector.add("Test " + i); }
//fail-fast機制(複合型操作) // for (Iterator iterator = vector.iterator(); iterator.hasNext();) { // Object next = iterator.next(); // vector.remove(10); // }
//不存在併發問題 for (int i = 0; i < 10; i++) { new Thread("Thread " + i) { @Override public void run() { while (true) { if (vector.isEmpty()) { break; } System.out.println(Thread.currentThread().getName() + "---" + vector.remove(0)); } } }.start(); }
//包裝成線程安全 Map<String, Integer> synchronizedMap = Collections.synchronizedMap(new HashMap<String, Integer>()); } } |
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */ public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } |
在JDK1.5,提供了多種同步(併發)類容器是專門針對併發設計的,如ConcurrentHashMap替代基於散列的傳統的Hashtable,而且ConcurrentHashMap中,添加了一些常用的複合操作支持。以及CopyOnWriteArayList代替Vector,和CopyWriteArraySet。還有併發的Queue(隊列),ConcurrentLinkedQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。
CopyOnWrite同步(併發)容器
/** * CopyOnWrite容器:這是一種用於程序設計中的優化策略。 * CopyOnWrite容器有兩種:CopyOnWriteArrayList和CopyOnWriteArraySet。 * * CopyOnWrite容器是即寫時複製的容器,當往容器添加元素時,不是直接往容器添加的, * 而是先將當前容器進行Copy,複製出一個新的容器,然後往新的容器內添加元素,添加完元素之後,在將原容器的引用指向新的容器。 * 這樣做的好處是我們可以對CopyConWrite容器進行併發的讀,而不需要加鎖。 * 這是因爲當前容器不會添加任何容器,所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫是不同的容器。 * 寫時,是複製出來新的容器,在新容器內操作,而舊容器依然還是可以多線程的併發讀,在執行完寫的操作就把指針指向新的容器,舊容器就進行回收。 * 寫都是加鎖的,不會出現數據不一致的情況下,即讀無鎖,寫有鎖。 * 場景:讀多寫少。 */ public class C02CopyOnWrite { public static void main(String[] args) { //替代Vector CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>(); copyOnWriteArrayList.add("123"); copyOnWriteArrayList.add("323"); copyOnWriteArrayList.add("123"); System.out.println(copyOnWriteArrayList);
//去重 CopyOnWriteArraySet<String> copyOnWriteArraySet = new CopyOnWriteArraySet<String>(); copyOnWriteArraySet.add("333"); copyOnWriteArraySet.add("222"); copyOnWriteArraySet.add("333");
System.out.println(copyOnWriteArraySet); } } |
ConcurrentMap同步(併發)容器
/** * ConcurrentMap接口有兩個重要的實現: * ConcurrentHashMap:替代Hashtable。 * ConcurrentSkipListMap:支持併發排序功能,彌補了ConcurrentHashMap的功能,類似TreeMap。 * * ConcurrentMap:內部使用段(segment)來表示這些不同的部分,每個段其實就是一個小的Hashtable,它們有自己的鎖。 * 只要修改操作發生在不同的段上,就可以併發進行,就是把一個整體分爲16個段(segment),即最高支持16個線程併發修改操作。 * 這也是多線程的優化,減少鎖的粒度,從而降低鎖競爭的一種方案,並且代碼中大多共享變量使用volatile關鍵字聲明,目的是第一時間可以獲取修改的內容,從而性能也非常好。 * * 備註:ConcurrentSkipListMap是CopyOnWrite(類List和Set)的補充,支持排序功能,默認是升序(正序)。 */ public class C03ConcurrentMap { public static void main(String[] args) { //替代Hashtable ConcurrentHashMap<String,Integer> concurrentHashMap = new ConcurrentHashMap<>(); concurrentHashMap.put("c1", 1); concurrentHashMap.put("c2", 2); concurrentHashMap.putIfAbsent("c3", 3);
System.out.println(concurrentHashMap.size()); System.out.println(concurrentHashMap.get("c3"));
for (Entry<String, Integer> entry : concurrentHashMap.entrySet()) { System.out.println(entry.getKey() + " - " + entry.getValue()); }
System.out.println("==========================");
//支持排序,默認是升序 ConcurrentSkipListMap<Integer, String> concurrentSkipListMap = new ConcurrentSkipListMap<>(); concurrentSkipListMap.put(3, "b"); concurrentSkipListMap.put(6, "c"); concurrentSkipListMap.put(1, "a"); for (Entry<Integer, String> entry : concurrentSkipListMap.entrySet()) { System.out.println(entry.getKey() + " - " + entry.getValue()); }
ConcurrentSkipListSet<Integer> concurrentSkipListSet = new ConcurrentSkipListSet<>(); concurrentSkipListSet.add(3); concurrentSkipListSet.add(1); concurrentSkipListSet.add(2); System.out.println(concurrentSkipListSet); } } |
Queue同步(併發)隊列
/** * 在併發隊列上,JDK提供了兩套實現: * 一是ConcurrentLinkedQueue(非阻塞)爲代表的高性能隊列。 * 二是BlockingQueue(阻塞)爲代表的隊列。 * 無論哪一種隊列都是繼承自Queue。 * * 備註:無界隊列是指沒有設置固定大小的隊列,特點是可以直接入列,直到溢出。有界隊列是指有長度限制的,即固定大小的隊列。 */ public class C04UseQueue { public static void main(String[] args) throws InterruptedException { //=============================== 非阻塞隊列 =============================== /* * ConcurrentLinkedQueue(非阻塞、無界): * 是一種適用於高併發場景下的隊列,通過無鎖的方式,實現了高併發狀態下的高性能, * 通常ConcurrentLinkedQueue性能浩宇BlockingQueue, * 它是一個基於鏈接節點的無界線程安全隊列,該隊列的元素遵循先進先出的原則。 * 頭是最先加入的,尾是最近加入的,該隊列不允許null元素存在,類似壓棧。 */ ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>(); concurrentLinkedQueue.offer("1"); concurrentLinkedQueue.offer("2"); concurrentLinkedQueue.add("3");
System.out.println(concurrentLinkedQueue.size()); System.out.println(concurrentLinkedQueue.poll());//取出首個元素並從隊列中刪除 System.out.println(concurrentLinkedQueue.size()); System.out.println(concurrentLinkedQueue.peek());//取出首個元素,不從隊列中移除。 System.out.println(concurrentLinkedQueue.size());
System.out.println("----------------------------------");
//=============================== 阻塞隊列 =============================== /* * ArrayBlockingQueue(阻塞、有界):基於數組的阻塞隊列實現的,在ArrayBlockingQueue內部維護了一個定長數組, * 以便緩存隊列中的數據對象,其內部沒實現讀寫分離,也意味着生產和消費不能完全並行,長度是需要定義的,可以指定先進先出或先進後出。 * 在很多場合下非常適合使用。 */ ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<String>(2); arrayBlockingQueue.put("1"); //沒有可用空間,會根據maxTime進行等待隊列的位置 arrayBlockingQueue.add("2"); //沒有可用空間,直接報錯 boolean offer = arrayBlockingQueue.offer("3"); //有可用空間返回true,無可用空間返回false // boolean offer = arrayBlockingQueue.offer("3",3,TimeUnit.SECONDS); //等待N時後執行添加,有可用空間返回true,無可用空間返回false
System.out.println(offer); System.out.println(arrayBlockingQueue.size());
System.out.println("----------------------------------");
/* * LinkedBlockingQueue(阻塞、無界):基於鏈表的阻塞隊列,和ArrayBlockingQueue類似,在其內部維持着一個數據緩衝隊列(鏈表構成)。 * LinkedBlockingQueue之所以能夠高性能的處理數據是因爲其內部實現了分離鎖(讀寫分離鎖),從而實現生產者和消費者操作的完全並行運行。 */ LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<String>(); linkedBlockingQueue.offer("1"); linkedBlockingQueue.offer("2"); linkedBlockingQueue.add("3"); System.out.println(linkedBlockingQueue.size());
for (Iterator iterator = linkedBlockingQueue.iterator();iterator.hasNext();) { System.out.println(iterator.next()); }
System.out.println("----------------------------------");
//取出元素放到新的集合中 ArrayList<String> arrayList = new ArrayList<String>(); System.out.println(linkedBlockingQueue.drainTo(arrayList, 2)); System.out.println(arrayList);
System.out.println("----------------------------------");
/* * SynchronousQueue(阻塞、無界):沒有緩衝的隊列,生產者產生的數據直接被消費者獲取並消費。 */ SynchronousQueue<String> synchronousQueue = new SynchronousQueue<String>();
//獲取元素 new Thread(new Runnable() { @Override public void run() { try { System.out.println(synchronousQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
//必須要有先有個線程獲取元素,如果先add會報錯 new Thread(new Runnable() { @Override public void run() { synchronousQueue.add("6"); } }).start();
System.out.println("----------------------------------");
/* * PriorityBlockingQueue(阻塞、無界):基於優先級的阻塞隊列,優先級判斷通過構造函數傳入的Compator對象來決定, * 也就是說傳入隊列的對象必須實現Comaparable接口,在實現PriorityBlockingQueue時,內部控制線程同步的鎖採用的是公平鎖。 */ PriorityBlockingQueue<Person> priorityBlockingQueue = new PriorityBlockingQueue<Person>(); priorityBlockingQueue.add(new Person("A", 1)); priorityBlockingQueue.add(new Person("C", 3)); priorityBlockingQueue.add(new Person("B", 2));
//可以看到是每次取元素時才進行排序,而不是每天add進去時就排序 System.out.println(priorityBlockingQueue); System.out.println(priorityBlockingQueue.take());
System.out.println("----------------------------------");
/* * LinkedBlockingDeque(阻塞、無界):鏈表結構組成的雙向阻塞隊列,可以從隊列兩端插入和移除, * 由於多了一個入口,在多線程同時入隊時,也就減少了一半競爭。 */ LinkedBlockingDeque<String> linkedBlockingDeque = new LinkedBlockingDeque<String>(); linkedBlockingDeque.addFirst("1"); linkedBlockingDeque.addFirst("2"); linkedBlockingDeque.addFirst("3"); linkedBlockingDeque.addLast("a"); linkedBlockingDeque.addLast("b"); linkedBlockingDeque.addLast("c"); System.out.println(linkedBlockingDeque.peekFirst()); System.out.println(linkedBlockingDeque.pollLast());
System.out.println("=========="); Object[] array = linkedBlockingDeque.toArray(); for (int i = 0; i < array.length; i++) { System.out.println(array[i]); } } }
class Person implements Comparable<Person> {
private String name;
private Integer age;
public Person(String name, Integer age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
@Override public int compareTo(Person o) { return this.age > o.age ? 1 : (this.age < o.age ? -1 : 0); }
@Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } |
/** * DelayQueue(阻塞、無界):是帶有延遲的Queue,其中元素只有其指定的延遲時間到了,才能夠從隊列中獲取到該元素。 * DelayQueue中的元素必須實現Delayed接口,DelayedQueue是一個沒有大小限制的隊列。 * 應用場景很多,如對緩存超時的數據進行移除、任務超時處理、空閒鏈接的關閉等。 */ public class C05DelayQueue { public static void main(String[] args) { try{ System.out.println("開業..."); WangBa siyu = new WangBa(); Thread shangwang = new Thread(siyu); shangwang.start();
// siyu.shangji("大", 10); siyu.shangji("大", 1); siyu.shangji("中", 10); siyu.shangji("小", 5); } catch(Exception e){ e.printStackTrace(); } } }
class WangBa implements Runnable {
/** * 每一個上網的隊列 */ private DelayQueue<WangMin> delayQueue = new DelayQueue<WangMin>();
public boolean yinye = true;
public void shangji(String name,int money) { long endTime = 1000 * money + System.currentTimeMillis(); long timeSize = endTime - System.currentTimeMillis(); WangMin wangMin = new WangMin(name,endTime); System.out.println(name + ":" + "交了" + money + "塊錢,上" + timeSize / 1000 + "秒"); this.delayQueue.add(wangMin); }
public void xiaji(WangMin wangMin) { System.out.println(wangMin.getName() + ":下機了..."); }
@Override public void run() { while (yinye) { try { WangMin wangMin = delayQueue.take(); xiaji(wangMin); } catch (InterruptedException e) { e.printStackTrace(); } } } }
class WangMin implements Delayed {
private String name;
private long endTime;
private TimeUnit timeUnit = TimeUnit.SECONDS;
public WangMin(String name, long endTime) { super(); this.name = name; this.endTime = endTime; }
public String getName() { return name; }
/* * 判斷是否到了截止時間 */ @Override public long getDelay(TimeUnit unit) { long timeSize = endTime - System.currentTimeMillis(); return timeSize; }
/* * 相互比較排序 */ @Override public int compareTo(Delayed o) { WangMin wangMin = (WangMin) o; return this.getDelay(this.timeUnit) - wangMin.getDelay(this.timeUnit) > 0 ? 1 : 0; } } |