多線程與高併發編程六、容器 Map、Collection

容器

 Vector<T>、Hashtable<K,V>  ,自帶加鎖(synchronized),線程安全。

static Map<UUID,UUID> mm = Collections.synchronizedMap(new HashMap<>());

Collections.synchronizedMap 源碼

final Object      mutex;        // Object on which to synchronize
public int size() {
            synchronized (mutex) {return m.size();}
        }

可以看到,Collections.synchronizedMap 鎖的粒度變小了,而 hashtable 的鎖是在 方法上面的。

 

ConcurrentHashMap 插入的效率低於 hashtable、hashmap 。 讀的效率高於 hashtable、hashmap。
static List tick = new ArrayList();
    static {
        for(int i = 0;i < 100;i++){
            tick.add(i);
        }
        System.out.println(" tick end");
    }

    public static void main(String[] args) {

//        StampedLock
//        CountDownLatch

        MyThread m = new MyThread();

//        WeakHashMap

        for(int i = 0;i< 10;i++){
            new Thread(()->{
                while(tick.size() > 0){
                    System.out.println(Thread.currentThread().getName() + " 移除 : " + tick.remove(0));
                }
            },"t" + i).start();
        }
    }

出現移除多餘存儲的情況,相當於超售

解決(效率不高):

    static List tick = new ArrayList<>();
    static {
        for(int i = 0;i < 100;i++){
            tick.add(i);
        }
        System.out.println(" tick end");
    }

    public static void main(String[] args) {

//        StampedLock
//        CountDownLatch

        MyThread m = new MyThread();

//        WeakHashMap

        for(int i = 0;i< 10;i++){
            new Thread(()->{
                while (true){
                    synchronized (tick){
                        if(tick.size() > 0){
                            System.out.println(Thread.currentThread().getName() + " 移除 : " + tick.remove(0));
                        }else{
                            break;
                        }
                    }
                }
            },"t" + i).start();
        }
    }

改成Vector:

static Vector<String> tick = new Vector<>();
     static {
         for(int i = 0;i < 100;i++){
            tick.add(i + "");
         }
         System.out.println(" tick end");
     }

     public static void main(String[] args) {

         //        StampedLock
         //        CountDownLatch

         MyThread m = new MyThread();

         //        WeakHashMap

         for(int i = 0;i< 10;i++){
             new Thread(()->{
                 while(tick.size() > 0){
                     System.out.println(Thread.currentThread().getName() + " 移除 : " + tick.remove(0));
                 }
             },"t" + i).start();
         }
     }

依舊會超售。雖然Vector 是線程安全的,size()、remove() 也都是安全的,但是是單獨的安全,合在一起的時候就不是原子性的了。

所以換成synchronized,可以解決,效率也不高

 new Thread(()->{
                 while(true){
                     synchronized (tick){
                         if(tick.size() > 0){
                             System.out.println(Thread.currentThread().getName() + " 移除 : " + tick.remove(0));
                         }else{
                             break;
                         }
                     }
                 }
             },"t" + i).start();

Queue

static Queue<String> tick = new ConcurrentLinkedQueue<>();
     static {
         for(int i = 0;i < 100;i++){
            tick.add(i + "");
         }
         System.out.println(" tick end");
     }

     public static void main(String[] args) {

         //        StampedLock
         //        CountDownLatch

         MyThread m = new MyThread();

         //        WeakHashMap

         for(int i = 0;i< 10;i++){
             new Thread(()->{
                         while(true){
                             /**
                              * poll
                              * 取出並刪除隊列頭部的值,即第一個。原子操作。
                              * 沒有則返回null
                              */
                             String r = tick.poll();
                             if(r == null) break;
                             System.out.println(Thread.currentThread().getName() + " 移除 : " + r);
                         }
             },"t" + i).start();
         }
     }

 

多線程單個元素: Queue<String> tick = new ConcurrentLinkedQueue<>();   

不需要有重複的可以考慮set:Set<String> strings = new ConcurrentSkipListSet<>();

有重複也可以考慮 list:List<String> list = new CopyOnWriteArrayList<>();

多線程推薦:Queue

poll 源碼

public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    // Successful CAS is the linearization point
                    // for item to be removed from this queue.
                    if (p != h) // hop two nodes at a time
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

CAS 實現原理。

 

一個線程,可以使用hashmap、linklist、arraylist

高併發情況但是持續時間短使用 ConcurrentHashMap、ConcurrentMap、ConcurrentLinkedQueue

代碼運行時間較長、併發不多使用 synchronized

ConcurrentSkipListMap 高併發排序
ConcurrentHashMap  高併發無須

ConcurrentSkipListMap  跳錶map。  最底層存儲的是鏈表數據,數據量大的時候,提出一層,還是很大再提出一層。查找的時候查找最上層的,進行區間 查找。有點像B+Tree

CopyOnWriteArrayList  寫時加鎖,讀時不加鎖。

在寫時,進行數組copy一份,數組長度+1,然後再進行添加。寫完之後將之前的引用指到新的上。

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

final void setArray(Object[] a) {
        array = a;
    }
BlockingQueue 無界阻塞隊列
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
put 進行put。但是會拋出異常。如果傳入的時null 拋出空指針異常。否則進行鎖定,然後進行插入,如果空間不足則會進行阻塞。
/**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

take  往外取,如果是空的,會阻塞

ArrayBlockingQueue 有界阻塞隊列,同 BlockingQueue 差不多,不過這個是有界的。

BlockingQueue<String> strings = new ArrayBlockingQueue<>(100);

 

Queue 和 List 的區別,Queue 添加了很多對線程友好的API: offer、peek、poll

peek 返回隊首的元素

poll: 返回隊首並移除元素

offer: 插入元素到隊尾,如果滿了也不阻塞

BlockingQueue : put、take.  線程阻塞.

 

DelayQueue  實現自 PriorityQueue 

BlockingQueue<Taks> takss = new DelayQueue<Taks>(); 

用處:設計任務調度。

static class Taks implements Delayed{
         String name = "";
         long runningTime;
         Taks(String name,long t){
             this.name = name;
             this.runningTime = t;
         }

         @Override
         public int compareTo(Delayed o) {
             if(this.getDelay(TimeUnit.MICROSECONDS) < o.getDelay(TimeUnit.MICROSECONDS)){
                 return -1;
             }else if(this.getDelay(TimeUnit.MICROSECONDS) > o.getDelay(TimeUnit.MICROSECONDS)){
                 return 1;
             }else{
                 return 0;
             }
         }

         @Override
         public long getDelay(TimeUnit unit) {
             return unit.convert(runningTime - System.currentTimeMillis(),TimeUnit.MICROSECONDS);
         }
     }


main:
BlockingQueue<Taks> takss = new DelayQueue<Taks>();
         long now = System.currentTimeMillis();
         Taks t1 = new Taks("t1",now + 1500);
         Taks t2 = new Taks("t1",now + 2500);
         Taks t3 = new Taks("t1",now + 3500);
         Taks t4 = new Taks("t1",now + 4500);
         Taks t5 = new Taks("t1",now + 500);
         
         takss.put(t1);
         takss.put(t2);
         takss.put(t3);
         takss.put(t4);
         takss.put(t5);

         System.out.println(takss);
         for (int i = 0; i< 5;i++){
             System.out.println(takss.take());
         }
PriorityQueue  排序隊列
PriorityQueue<String> q = new PriorityQueue<>();
         q.add("a");
         q.add("z");
         q.add("k");
         q.add("f");
         q.add("q");

         int size = q.size();
         for(int i = 0; i < size; i++){
             System.out.println(q.poll());
         }

 

SynchronousQueue  進行數據傳遞。

不能add,會報錯。如果沒有put ,則 take 會一直阻塞。size 永遠爲0。

如果沒有 take,只有put,也會一直阻塞。

即只有 put 、take 都存在的情況下,纔不會進行阻塞。

BlockingQueue<String> strs = new SynchronousQueue<>();
         new Thread(()->{
             try {
                 System.out.println(strs.take());
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }).start();

         strs.put("aaaa");
         System.out.println(strs.size());
LinkedTransferQueue  和SynchronousQueue  類似

不過SynchronousQueue  是一個的數據交換。 LinkedTransferQueue 是多個的。

LinkedTransferQueue<String> strings = new LinkedTransferQueue<>();

         new Thread(()->{
             try {
                 System.out.println(strings.take());
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }).start();

         strings.transfer("cccccccccc");
         strings.transfer("aaaa");

         System.out.println("11111111");

 

總結:

 Vector<T>、Hashtable<K,V>  ,自帶加鎖(synchronized),線程安全

Collections.synchronizedMap 是HashMap的升級。鎖的粒度變小了,而 hashtable 的鎖是在 方法上面的.

ConcurrentHashMap 插入的效率低於 hashtable、hashmap 。 讀的效率高於 hashtable、hashmap。

CopyOnWriteArrayList 寫時加鎖,讀時不加鎖。

BlockingQueue 無界阻塞隊列

ArrayBlockingQueue 有界阻塞隊列,同 BlockingQueue 差不多,不過這個是有界的。

BlockingQueue : put、take.  線程阻塞.

DelayQueue  實現自 PriorityQueue 

PriorityQueue 排序隊列

SynchronousQueue 進行數據傳遞。即只有 put 、take 都存在的情況下,纔不會進行阻塞。

LinkedTransferQueue 和SynchronousQueue  類似.不過SynchronousQueue  是一個的數據交換。 LinkedTransferQueue 是多個的。

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