Java JDK1.8(20) - 同步(併發)類容器詳解(CopyOnWrite容器、ConcurrentMap容器、Queue隊列容器)

同步(併發)類容器

      同步(併發)類容器都是線程安全的,但在某些場景下可能需要加鎖來保護複合操作,如迭代(反覆訪問元素,遍歷容器所有元素)、跳轉(根據指定的順序找到當前元素的下一個元素),以及條件運算。

      這些複合操作在多線程併發的修改容器時,可能會表現出意外的行爲,最典型的就是之前解析集合源碼時,講到的Fast-Fast機制,會拋出ConcurrentModificationException異常,這是早期迭代器設計的時候並沒有考慮併發修改的問題。

      早期的同步(併發)類容器,如已經棄用的Vector、Hashtable,這些容器的同步功能其實都是用JDK底層的Collections.synchronized*方法去創建實現的。底層的機制無非都是用傳統的synchronized關鍵字對每個方法都進行同步,使得每次只能由一個線程訪問容器的狀態,狀態都是串行化,雖然實現了線程安全,但是嚴重降低了併發性,在多線程環境時,嚴重降低了吞吐量。

這顯然無法滿足現在系統的高併發需求,在保證線程安全的同時,也必須有足夠好的性能。

/**

 * 在之前的集合源碼解析中,可以得知有fail-fast機制,這是早期設計時,沒有考慮併發修改的問題。

 * 所以就提供了VectorHashtable這些容器,底層都是用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容器有兩種:CopyOnWriteArrayListCopyOnWriteArraySet

 *

 * 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關鍵字聲明,目的是第一時間可以獲取修改的內容,從而性能也非常好。

 *

 * 備註:ConcurrentSkipListMapCopyOnWrite(類ListSet)的補充,支持排序功能,默認是升序(正序)。

 */

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;

    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章