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;

    }

}

 

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