High concurrent programming in real-world java Chapter 3

3. JDK Concurrent Container

  • Concurrent collection
    Concurrent HashMap: This is an efficient concurrent HashMap. You can understand it as a thread-safe HashMap.
    CopyOnWriteArrayList: This is a List, which is a family of ArrayLists by name. In the case of reading more and writing less, this List performs very well, far better than Vector.
    Concurrent LinkedQueue: An efficient concurrent queue, implemented with linked lists. Think of it as a thread-safe LinkedList.
    BlockingQueue: This is an interface that JDK implements internally through linked lists, arrays, etc. Represents a blocked queue that is well suited for data sharing channels.
    Concurrent SkipListMap: Implementation of jump tables. This is a Map, which uses the data structure of the jump table for fast lookup.
  • Thread-safe HashMap
    The Collections class can be used to convert a normal HashMap to a thread-safe map
Collections.synchronizedMap(new HashMap())
    private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;
        private final Map<K,V> m;     // Input map
        final Object      mutex;        // Lock the resource object, which will be locked by any operation on the map
        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }
        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }
        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }
        .......  //ellipsis
  }
  • List thread security
Collections.synchronizedList(new LinkedList<String>())
  • Concurrent LinkedQueue Class for Efficient Read-Write Queue

The best performance queues in high concurrent environments are mainly non-blocking queues and lock-free operations using CAS.
First, let's look at its Node node:

    private static class Node<E> {
        volatile E item;            //Current object
        volatile Node<E> next;      //Next object to build the list
        Node(E item) {
            UNSAFE.putObject(this, itemOffset, item);
        }

        boolean casItem(E cmp, E val) {        //(Expected value, set target value), cas operation
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }

        void lazySetNext(Node<E> val) {
            UNSAFE.putOrderedObject(this, nextOffset, val);
        }

        boolean casNext(Node<E> cmp, Node<E> val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }
        private static final sun.misc.Unsafe UNSAFE;
        private static final long itemOffset;
        private static final long nextOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> k = Node.class;
                itemOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("item"));
                nextOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

tail pointer updates within the ConcurrentLinkedQueue class are not real-time, and there may be delays. Each update jumps two elements, as shown below:

Then look at the new node offer() method:

public boolean offer(E e) {
        checkNotNull(e);        //Non-empty check
        final Node<E> newNode = new Node<E>(e);
        for (Node<E> t = tail, p = t;;) {    //for loop has no exit and knows the setup is successful
            Node<E> q = p.next;        //Get the next object of tail node
            if (q == null) {        //For the first insert, the p.next object is empty
                // p is the last node
                if (p.casNext(null, newNode)) {     //Insert a new element, p=t
                    //Update tail twice
                    if (p != t)         
                        casTail(t, newNode);  
                    return true;
                }
                // cas Competition Fails, Cycle Again
            }
            else if (p == q)    //Meeting Sentinels
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q; //t!=(t=tail)!= It's not an atomic operation. First, take the value of T on the left and then t=tail on the right.
        }
    }
  • Efficient reading: CopyOnWriteArrayList class in invariant mode

Use scenario: Read operation is much larger than write operation. The faster the read operation is, the better. It's OK to write slowly.
Features: read without lock, write will not block the read operation, only write and write need to wait synchronously, read performance greatly improved
Principle: Write in a self-replication, modify the content to write in the copy, after writing, then replace the original data with the copy content.

    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);  //Duplicate
            newElements[len] = e;    //New arrays instead of old ones
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

4. JMH performance testing

Keywords: Java JDK less

Added by JonathanAnon on Wed, 14 Aug 2019 08:08:16 +0300