11

This article catalog :

  • What are the common collections?
  • The difference between List, Set and Map
  • Do you understand ArrayList?
  • What is the expansion mechanism of ArrayList?
  • How to remove an element while traversing the ArrayList?
  • The difference between Arraylist and Vector
  • The difference between Arraylist and LinkedList
  • HashMap

    • What are the ways to resolve hash conflicts? What kind of HashMap is used?
    • The hash algorithm used?
    • Expansion process?
    • Put method flow?
    • The characteristics of red-black trees?
    • Why use red-black trees instead of AVL trees?
    • When resolving hash conflicts, why choose to use the linked list first, and then switch to the red-black tree?
    • Why is the length of HashMap a power of 2?
    • What is the default load factor of HashMap? Why is it 0.75?
    • What is generally used as the key of HashMap?
    • Why is HashMap thread unsafe?
    • The difference between HashMap and HashTable?
  • The underlying principle of LinkedHashMap?
  • Tell me about TreeMap?
  • The underlying principle of HashSet?
  • What is the difference between HashSet, LinkedHashSet and TreeSet?
  • What is fail fast?
  • What is fail safe?
  • Tell me about ArrayDeque?
  • Which collection classes are thread safe? What is unsafe?
  • What is Iterator?
  • What is the difference between Iterator and ListIterator?
  • Concurrent container

    • ConcurrentHashMap

      • Put execution flow?
      • How to expand?
      • The difference between ConcurrentHashMap and Hashtable?
    • CopyOnWrite
    • ConcurrentLinkedQueue
    • Blocking queue

      • Blocking queue provided by JDK
      • principle

In addition, I will share with you a well-organized PDF of the high-frequency

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

What are the common collections?

The Java collection class is mainly derived from two interfaces Collection and Map . Collection has three sub-interfaces: List, Set, and Queue.

The framework diagram of the Java collection is as follows:

List represents an ordered and repeatable collection, which can be accessed directly according to the index of the element; Set represents an unordered and non-repeatable collection, which can only be accessed according to the element itself; Queue is a queue collection. Map represents a collection of stored key-value pairs, and the value can be accessed according to the key of the element.

The commonly used implementation classes in the collection system include ArrayList, LinkedList, HashSet, TreeSet, HashMap, TreeMap and other implementation classes.

The difference between List, Set and Map

  • List accesses elements by index, in order, elements are allowed to be repeated, and multiple nulls can be inserted;
  • Set cannot store repeated elements, unordered, only one null is allowed;
  • Map saves the key-value pair mapping;
  • The bottom layer of List is implemented in two ways: array and linked list; Set and Map containers are implemented in two ways, based on hash storage and red-black tree;
  • Set is implemented based on Map, and the element value in Set is the key value of Map.

Do you understand ArrayList?

ArrayList is a dynamic array, and its capacity can grow dynamically. Before adding a large number of elements, the application can use the ensureCapacity operation to increase the capacity of the ArrayList ArrayList inherits AbstractList and implements the List interface.

What is the expansion mechanism of ArrayList?

The essence of ArrayList expansion is to calculate the size of the new expanded array and instantiate it, and copy the contents of the original array to the new array. By default, the new capacity will be 1.5 times the original capacity . Take JDK1.8 as an example:

public boolean add(E e) {
    //判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将e添加到数组末尾
    elementData[size++] = e;
    return true;
    }

// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部

private void ensureCapacityInternal(int minCapacity) {
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


private void grow(int minCapacity) {
        // 获取elementData数组的内存空间长度
        int oldCapacity = elementData.length;
        // 扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //校验容量是否够
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //若预设值大于默认的最大值,检查是否溢出
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 调用Arrays.copyOf方法将elementData数组指向新的内存空间
         //并将elementData的数据复制到新的内存空间
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

How to remove an element while traversing the ArrayList?

Foreach delete will cause fast failure problem, you can use the remove() method of the iterator.

Iterator itr = list.iterator();
while(itr.hasNext()) {
      if(itr.next().equals("jay") {
        itr.remove();
      }
}

The difference between Arraylist and Vector

  1. When the memory is insufficient, ArrayList is expanded by 50% + 1 by default, and Vector is expanded by 1 times by default.
  2. Vector belongs to the thread-safe level, but in most cases, Vector is not used because it is relatively inefficient to operate on Vector.

The difference between Arraylist and LinkedList

  1. ArrayList is implemented based on a dynamic array; LinkedList is implemented based on a linked list.
  2. For the get and set methods of random index access, ArrayList is faster than LinkedList. Because ArrayList finds elements directly through the array subscript; LinkedList moves the pointer to traverse each element until it finds it.
  3. Adding and deleting elements, LinkedList is faster than ArrayList. Because ArrayList may expand and copy the array when adding and deleting elements; LinkedList needs time to instantiate objects, and only needs to modify the pointer.

HashMap

HashMap is implemented using array + linked list + red-black tree (JDK1.8 adds the red-black tree part). When the length of the linked list is greater than 8 (TREEIFY_THRESHOLD), the linked list will be converted to a red-black tree, and the number of red-black tree nodes is less than 6 ( UNTREEIFY_THRESHOLD) is converted to a linked list to prevent frequent conversion.

What are the ways to resolve hash conflicts? What kind of HashMap is used?

Methods to resolve Hash conflicts include: open addressing, rehashing, and chain addressing. The chain address method is used in HashMap.

  • The basic idea of open addressing method is that if p=H(key) event of a conflict, places p basis, hash again, p1=H(p) , if p1 conflict again, places p1-based, and so on, until you find a hash address that does not conflict pi . Therefore, the length of the hash table required by the open addressing method must be greater than or equal to the elements that need to be stored, and because there is another hash, can only mark the deleted node, but cannot actually delete the node.
  • And then hashing a plurality of different hash function, when R1=H1(key1) conflict occurs, then calculate R2=H2(key1) , until there is no conflict. Although this is not easy to produce a pile, but it increases the calculation time.
  • The chain address method composes a singly linked list of synonyms with the same hash value, and stores the head pointer of the singly linked list in the i-th unit of the hash table. The search, insertion and deletion are mainly performed in the synonym linked list. The linked list method is suitable for frequent insertions and deletions.

The hash algorithm used?

Hash algorithm: take the hashCode value of the key, high-order operation, and modulo operation.

h=key.hashCode() //第一步 取hashCode值
h^(h>>>16)  //第二步 高位参与运算,减少冲突
return h&(length-1);  //第三步 取模运算

In the implementation of JDK1.8, the algorithm for high-order operations is optimized, which is implemented by the high-order 16-bit XOR of hashCode(): this can be done when the array is relatively small, and it can also ensure that both high and low bits are taken into account. In the calculation of Hash, conflicts can be reduced without too much overhead.

Expansion process?

1.8 Expansion mechanism: When the number of elements is greater than the threshold, expansion will be carried out, and an array with twice the capacity will be used instead of the original array. Use tail insertion to copy the original array elements to the new array. The relative position of the linked list elements remains unchanged after 1.8 expansion, while the linked list elements will be inverted after 1.7 expansion.

1.7 The new node of the linked list uses the head interpolation method, so when the thread expands and migrates the elements, the order of the elements will be changed, resulting in the mutual pointing of the elements in the two threads to form a circular linked list. 1.8 uses the tail interpolation method to avoid This happens.

After recalculating the hash for the elements of the original array, because the array capacity n is doubled, the mask range of n-1 is 1 bit more high. In the process of element copying, you don’t need to recalculate the position of the element in the array. You only need to see whether the bit added by the original hash value is 1 or 0. If it is 0, the index has not changed. If it is 1, the index becomes "original index + oldCap" ( e.hash & (oldCap - 1) == 0 by 061b573d1af70f). This saves the time to recalculate the hash value, and since the newly added 1bit is 0 or 1 can be considered random, so the resize process will evenly distribute the previous conflicting nodes to the new bucket.

Put method flow?

  1. If the table is not initialized, the initialization process is performed first
  2. Use the hash algorithm to calculate the index of the key
  3. Determine whether there is an element at the index, and insert it directly if not
  4. If there is an element at the index, then traverse the insertion. There are two cases. One is the linked list form and the insertion is directly traversed to the end, and the other is the red-black tree and inserts according to the red-black tree structure.
  5. If the number of linked lists is greater than the threshold 8, it must be converted into a red-black tree structure
  6. After the addition is successful, it will check whether it needs expansion

The characteristics of red-black trees?

  • Each node is either black or red.
  • The root node is black.
  • Each leaf node (NIL) is black.
  • If a node is red, its child nodes must be black.
  • All paths from a node to the descendants of that node contain the same number of black nodes.

Why use red-black trees instead of AVL trees?

ConcurrentHashMap will be locked when put, using the red-black tree to insert faster, which can reduce the time waiting for the lock to be released. The red-black tree is an optimization of the AVL tree. It only requires partial balance. The non-strict balance is used to reduce the number of rotations when adding and deleting nodes, which improves the performance of insertion and deletion.

When resolving hash conflicts, why choose to use the linked list first, and then switch to the red-black tree?

Because red-black trees need to perform left-handed, right-handed, and color-changing operations to maintain balance, singly linked lists do not. When the number of elements is less than 8, the linked list structure can guarantee query performance. When there are more than 8 elements, the search time complexity of the red-black tree is O(logn), while the linked list is O(n). At this time, the red-black tree is needed to speed up the query, but the efficiency of inserting and deleting nodes becomes slower. . If the red-black tree structure is used at the beginning, there are too few elements, and the efficiency of inserting and deleting nodes is slow, and performance is wasted.

Why is the length of HashMap a power of 2?

The Hash value has a relatively large range value. Before using it, you need to perform a modulo operation on the length of the array, and the remainder obtained is the location where the element is stored, which is the corresponding array subscript. The calculation method for the subscript of this array is (n - 1) & hash . Set the length of HashMap to the power of 2, so that you can use (n - 1)&hash bit operation instead of% remainder operation to improve performance.

What is the default load factor of HashMap? Why is it 0.75?

First look at the default constructor of HashMap:

int threshold;             // 容纳键值对的最大值
final float loadFactor;    // 负载因子
int modCount;  
int size;  

The initial length of the Node[] table is 16, and the default loadFactor is 0.75. 0.75 is a balanced choice for space and time efficiency. According to the Poisson distribution, loadFactor 0.75 is the smallest collision. Generally, it will not be modified, except under special circumstances in time and space:

  • If there is a lot of memory space and time efficiency is very high, you can reduce the value of Load factor.
  • If the memory space is tight and the time efficiency is not high, you can increase the value of the load factor loadFactor, which can be greater than 1.

What is generally used as the key of HashMap?

Generally, immutable classes such as Integer and String are used as HashMap as the key. The String class is more commonly used.

  • Because String is immutable, the hashcode is cached when it is created and does not need to be recalculated. This is why the keys in HashMap often use strings.
  • The equals() and hashCode() methods are used when obtaining objects, and the Integer and String classes have already rewritten the hashCode() and equals() methods, so you don’t need to rewrite these two methods yourself.

Why is HashMap thread unsafe?

  • Infinite loop of expansion under multi-threading. The HashMap in JDK1.7 uses the header insertion method to insert elements. In a multi-threaded environment, the circular linked list may appear when the capacity is expanded, forming an endless loop.
  • In JDK1.8, in a multithreaded environment, data will overwrite .

The difference between HashMap and HashTable?

Both HashMap and Hashtable implement the Map interface.

  1. HashMap can accept null keys and values. Key-value pairs with null keys are placed in the linked list of the head node with subscript 0, while Hashtable cannot.
  2. HashMap is not thread-safe, HashTable is thread-safe. Jdk1.5 provides ConcurrentHashMap, which is a replacement for HashTable.
  3. Many methods of Hashtable are synchronous methods, which are slower than HashMap in a single-threaded environment.
  4. The use of hash value is different, HashTable directly uses the hashCode of the object. The HashMap recalculates the hash value.

The underlying principle of LinkedHashMap?

HashMap is unordered, and the order of the elements obtained by iterating the HashMap is not the order in which they were originally placed in the HashMap, that is, their insertion order cannot be maintained.

LinkedHashMap inherits from HashMap and is a fusion of HashMap and LinkedList, with the characteristics of both. Each put operation will insert the entry into the end of the doubly linked list.

linkedhashmap

Tell me about TreeMap?

TreeMap is a Map collection that can compare the size of elements, and sorts the incoming keys by size. You can use the natural order of the elements, or you can use a custom comparator in the collection to sort.

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable {
}

Inheritance structure of TreeMap:

TreeMap features:

  1. TreeMap is an ordered set of key-values, implemented through red-black trees. Sort according to the natural order of the keys or according to the provided Comparator.
  2. TreeMap inherits AbstractMap, implements the NavigableMap interface, and supports a series of navigation methods. Given a specific search target, it can return the closest match. For example, floorEntry() and ceilingEntry() return Map.Entry() objects that are less than or equal to or greater than or equal to a given key, respectively, and return null if they do not exist. lowerKey(), floorKey, ceilingKey, and higherKey() only return the associated key.

The underlying principle of HashSet?

HashSet is implemented based on HashMap. The elements put into the HashSet are actually stored by the key of the HashMap, and the value of the HashMap stores a static Object object.

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map; //基于HashMap实现
    //...
}

What is the difference between HashSet, LinkedHashSet and TreeSet?

HashSet is Set main interface implementation class, HashSet bottom is HashMap , thread-safe, a null value may be stored;

LinkedHashSet is HashSet , which can be traversed in the order of addition;

TreeSet uses a red-black tree at the bottom layer, which can be traversed in the order of adding elements, and the sorting method can be customized.

What is fail fast?

Fast-fail is an error mechanism of Java collections. When multiple threads operate on the same collection, fast-fail events may occur. For example: when thread a is traversing the collection through iterator, another thread b modifies the content of the collection. At this time modCount (the number of modifications to the collection operation process) will increase by 1, which is not equal to expectedModCount, then when thread a accesses the collection, ConcurrentModificationException will be thrown, resulting in a fast-fail event. Modifying the collection while traversing will also generate fast-fail events.

Solution:

  • Use the Colletions.synchronizedList method or add synchronized to the place where the content of the collection is modified. In this case, the synchronization lock that adds or deletes the content of the collection will block the traversal operation and affect performance.
  • Use CopyOnWriteArrayList to replace ArrayList. When the CopyOnWriteArrayList is modified, a new array is copied, the new array is operated, and the reference is moved to the new array after the operation is completed.

What is fail safe?

The collection container that adopts the security failure mechanism is not directly accessed on the collection content during traversal, but first copies the original collection content and traverses on the copied collection. The containers under the java.util.concurrent package are safe to fail, and can be used and modified concurrently in multiple threads.

Principle : Since the copy of the original collection is traversed during iteration, the modification of the original collection during the traversal process cannot be detected by the iterator, so Concurrent Modification Exception will not be triggered.

Disadvantages : The advantage of copying content is to avoid Concurrent Modification Exception, but similarly, the iterator cannot access the modified content, that is: the iterator traverses the copy of the collection obtained at the moment of traversal. During this period, the modification iterator of the original collection is not known.

Tell me about ArrayDeque?

ArrayDeque implements a double-ended queue, using a circular array internally, and the default size is 16. Its characteristics are:

  1. It is more efficient to add and delete elements at both ends
  2. The efficiency of searching and deleting based on element content is relatively low.
  3. There is no concept of index position, and operations cannot be performed based on index position.

Both ArrayDeque and LinkedList implement the Deque interface. If you only need to operate from both ends, ArrayDeque is more efficient. If you need to operate according to the index position at the same time, or often need to insert and delete in the middle (LinkedList has corresponding api, such as add(int index, E e)), you should choose LinkedList.

Both ArrayDeque and LinkedList are not thread-safe and can be converted to thread synchronization using synchronizedXxx() in the Collections tool class.

Which collection classes are thread safe? What is unsafe?

Linear safe collection class:

  • Vector: It has more synchronization mechanism than ArrayList.
  • Hashtable。
  • ConcurrentHashMap: is an efficient and thread-safe collection.
  • Stack: Stack is also thread-safe, inherited from Vector.

Linear insecure collection class:

  • Hashmap
  • Arraylist
  • LinkedList
  • HashSet
  • TreeSet
  • TreeMap

What is Iterator?

Iterator mode uses the same logic to traverse the collection. It can abstract the access logic from different types of collection classes. You can traverse the collection elements without knowing the internal implementation of the collection, and use the interface provided by Iterator to traverse uniformly. Its characteristic is more secure, because it can guarantee that ConcurrentModificationException will be thrown when the currently traversed collection element is changed.

public interface Collection<E> extends Iterable<E> {
    Iterator<E> iterator();
}

There are three main methods: hasNext(), next() and remove().

What is the difference between Iterator and ListIterator?

ListIterator is an enhanced version of Iterator.

  • ListIterator traversal can be reversed, because there are previous() and hasPrevious() methods, but Iterator cannot.
  • ListIterator has add() method, which can add objects to List, but Iterator cannot.
  • ListIterator can locate the current index position because of the nextIndex() and previousIndex() methods, but Iterator cannot.
  • ListIterator can realize the modification of the object, and the set() method can be realized. Iierator can only be traversed and cannot be modified.
  • ListIterator can only be used to traverse List and its subclasses, and Iterator can be used to traverse all collections.

Concurrent container

Most of these containers provided by the JDK are in the java.util.concurrent package.

  • ConcurrentHashMap: thread-safe HashMap
  • CopyOnWriteArrayList: thread-safe List that performs very well in situations where more reads and less writes are performed, far better than Vector.
  • ConcurrentLinkedQueue: efficient concurrent queue, implemented using a linked list. It can be seen as a thread-safe LinkedList, which is a non-blocking queue.
  • BlockingQueue: blocking queue interface, which is implemented in the JDK through linked lists, arrays, etc. Very suitable for use as a data sharing channel.
  • ConcurrentSkipListMap: The jump table. Use the data structure of the jump table for quick search.

ConcurrentHashMap

In a multi-threaded environment, using Hashmap for put operations will cause an endless loop, and ConcurrentHashMap that supports multi-threading should be used.

JDK1.8 ConcurrentHashMap cancels the segment lock, and uses CAS and synchronized to ensure concurrency safety. The data structure adopts array + linked list/red-black binary tree. Synchronized only locks the first node of the current linked list or red-black binary tree. Compared with 1.7 locking the HashEntry array, the lock granularity is smaller and supports a higher amount of concurrency. When the length of the linked list is too long, Node will be converted to TreeNode to improve the search speed.

Put execution flow?

The segment needs to be locked during put to ensure concurrency safety. When calling get, do not lock, because the node array member val and pointer next are modified with volatile, the changed value will be immediately refreshed in the main memory to ensure visibility, and the node array table is also modified with volatile to ensure that it is running The process has visibility to other threads.

transient volatile Node<K,V>[] table;

static class Node<K,V> implements Map.Entry<K,V> {
    volatile V val;
    volatile Node<K,V> next;
}

Put operation process:

  1. If the table is not initialized, the initialization process is performed first
  2. Use the hash algorithm to calculate the location of the key
  3. If this position is empty, CAS is inserted directly, if it is not empty, then this node is taken out
  4. If the hash value of the retrieved node is MOVED(-1), it means that the array is currently being expanded and copied to the new array, and the current thread will also help copy
  5. If this node is not empty or expanding, it will be locked through synchronized to perform the addition operation. There are two cases here, one is that the linked list is directly traversed to the end to insert or overwrite the same key, the other is If it is a red-black tree, insert it according to the red-black tree structure
  6. If the number of linked lists is greater than the threshold 8, it will be converted into a red-black tree structure or expanded (table length is less than 64)
  7. After the addition is successful, it will check whether it needs expansion

How to expand?

In the array expansion transfer method, a step size is set to indicate the length of the array processed by a thread, and the minimum value is 16. Only one thread will copy and move it in a step range.

The difference between ConcurrentHashMap and Hashtable?

  1. Hashtable achieves multi-thread synchronization by using the synchronized modification method. Therefore, the synchronization of Hashtable will lock the entire array. In the case of high concurrency, the performance will be very poor. ConcurrentHashMap uses more fine-grained locks to improve efficiency in concurrent situations. Note: Synchronized container (synchronized container) also achieves thread safety through the synchronized keyword, and locks all data when in use.
  2. The default size of Hashtable is 11. When the threshold is reached, the capacity is expanded according to the following formula each time: newCapacity = oldCapacity * 2 + 1. The default size of ConcurrentHashMap is 16, and the capacity is doubled when expanding.

CopyOnWrite

Copy-on-write. When we add elements to the container, we do not directly add to the container, but first copy the current container, copy out a new container, and then add elements to the new container, after adding the elements, then point the reference to the original container New container. The advantage of this is that the CopyOnWrite container can be read concurrently without locking, because the current container will not be modified.

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock(); //add方法需要加锁
        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();
        }
    }

Starting from JDK1.5, the Java Concurrency Package provides two concurrent containers implemented using the CopyOnWrite mechanism. They are CopyOnWriteArrayList and CopyOnWriteArraySet.

When the add method in CopyOnWriteArrayList is added, it needs to be locked to ensure synchronization and avoid copying multiple copies when writing in multiple threads. There is no need to lock when reading. If other threads are adding data to CopyOnWriteArrayList when reading, the old data can still be read.

Disadvantages:

  • Memory usage problem. Due to the copy-on-write mechanism of CopyOnWrite, the memory of two objects resides in the memory at the same time when the write operation is performed.
  • The CopyOnWrite container cannot guarantee the real-time consistency of the data, and old data may be read.

ConcurrentLinkedQueue

Non-blocking queue. Efficient concurrent queues are implemented using linked lists. It can be seen as a thread-safe LinkedList, implemented through CAS operations.

If the cost of locking the queue is high, it is suitable to use a lock-free ConcurrentLinkedQueue instead. It is suitable for scenarios where the performance requirements are relatively high and there are multiple threads to read and write to the queue at the same time.

non-blocking queue:
add(E e): Insert element e to the end of the queue. If the insertion is successful, it returns true; if the insertion fails (ie, the queue is full), an exception will be thrown;
remove(): Remove the element at the head of the team. If the removal is successful, it will return true; if the removal fails (the queue is empty), an exception will be thrown;
offer(E e): Insert the element e at the end of the queue, if the insertion is successful, it returns true; if the insertion fails (that is, the queue is full), it returns false;
poll(): Remove and get the first element of the team, if successful, return the first element of the team; otherwise, return null;
peek(): Get the first element of the team, if successful, return the first element of the team; otherwise, return null

For non-blocking queues, it is generally recommended to use the three methods of offer, poll and peek, and it is not recommended to use the add and remove methods. Because the three methods of offer, poll, and peek can be used to determine whether the operation is successful or not through the return value, but the use of add and remove methods cannot achieve this effect.

Blocking queue

The blocking queue is an important data structure under the java.util.concurrent package. BlockingQueue provides a thread-safe queue access method: when the blocking queue is inserting data, if the queue is full, the thread will block and wait until the queue is not full; from blocking When the queue fetches data, if the queue is empty, the thread will block and wait until the queue is not empty. The implementation of many advanced synchronization classes under concurrent packages is based on BlockingQueue. BlockingQueue is suitable for use as a data sharing channel.

The queue using the blocking algorithm can be implemented with one lock (the same lock is used for enqueue and dequeue) or two locks (different locks are used for enqueue and dequeue).

The difference between a blocking queue and a general queue is:

  1. Multi-thread support, multiple threads can safely access the queue
  2. Blocking operation. When the queue is empty, the consumer thread will block and wait for the queue to be not empty; when the queue is full, the production thread will block until the queue is not full.

Method

Method\Processing MethodThrow an exceptionReturn special valueKeeps blockingTimeout exit
Insert methodadd(e)offer(e)put(e)offer(e,time,unit)
Removal methodremove()poll()take()poll(time,unit)
Inspection Methodelement()peek()unavailableunavailable

Blocking queue provided by JDK

JDK 7 provides 7 blocking queues, as follows

1、ArrayBlockingQueue

Bounded blocking queue, the bottom layer is implemented by array. Once the ArrayBlockingQueue is created, the capacity cannot be changed. The concurrency control adopts reentrant locks to control, whether it is an insert operation or a read operation, a lock must be acquired before the operation can be performed. This queue sorts the elements according to the first-in-first-out (FIFO) principle. By default, the fairness of thread access to the queue is not guaranteed. The parameter fair can be used to set whether the thread accesses the queue fairly. In order to ensure fairness, throughput is usually reduced.

private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10,true);//fair

2、LinkedBlockingQueue

LinkedBlockingQueue is a bounded blocking queue implemented with a singly linked list, which can be used as an unbounded queue or a bounded queue. Usually when creating a LinkedBlockingQueue object, the maximum capacity of the queue is specified. The default and maximum length of this queue is Integer.MAX_VALUE . This queue sorts the elements according to the first-in-first-out principle. Compared with ArrayBlockingQueue, it has higher throughput.

3、PriorityBlockingQueue

Support priority unbounded blocking queue. By default, the elements are arranged in ascending order in natural order. You can also customize the class to implement the compareTo() method to specify the element sorting rules, or when you initialize the PriorityBlockingQueue, specify the construction parameter Comparator for sorting.

PriorityBlockingQueue can only specify the initial queue size. When inserting elements later, if there is not enough space, will automatically expand to .

A thread-safe version of PriorityQueue. No null value can be inserted. At the same time, the object inserted into the queue must be comparable in size (comparable), otherwise a ClassCastException will be reported. Its insert operation put method will not block, because it is an unbounded queue (the take method will block when the queue is empty).

4、DelayQueue

Unbounded blocking queue that supports delayed acquisition of elements. The queue is implemented using PriorityBlockingQueue. The elements in the queue must implement the Delayed interface. When creating the element, you can specify how long it takes to get the current element from the queue. Only when the delay expires can elements be extracted from the queue.

5、SynchronousQueue

In a blocking queue that does not store elements, each put must wait for a take operation, otherwise it cannot continue to add elements. Support fair access queue.

SynchronousQueue can be regarded as a passer, responsible for passing the data processed by the producer thread directly to the consumer thread. The queue itself does not store any elements, which is very suitable for transitive scenarios. The throughput of SynchronousQueue is higher than that of LinkedBlockingQueue and ArrayBlockingQueue.

6、LinkedTransferQueue

An unbounded blocking TransferQueue queue composed of a linked list structure. Compared with other blocking queues, there are more tryTransfer and transfer methods.

Transfer method: If there is currently a consumer waiting to receive an element (take or time-limited poll method), transfer can immediately transfer the element passed by the producer to the consumer. If there is no consumer waiting to receive the element, put the element in the tail node of the queue, and wait until the element is consumed by the consumer before returning.

tryTransfer method: used to test whether the elements passed by the producer can be directly passed to the consumer. If no consumers are waiting, return false. The difference with the above method is that the method returns immediately regardless of whether the consumer receives it or not. The transfer method must wait until the consumer has consumed it before returning.

principle

JDK uses the notification mode to implement blocking queues. The so-called notification mode is that when the producer adds elements to the full queue, the producer will be blocked. When the consumer consumes the elements in a queue, the producer will be notified that the current queue is available.

ArrayBlockingQueue uses Condition to achieve:

private final Condition notEmpty;
private final Condition notFull;

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0) // 队列为空时,阻塞当前消费者
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    final Object[] items = this.items;
    items[putIndex] = x;
    if (++putIndex == items.length)
          putIndex = 0;
     count++;
     notEmpty.signal(); // 队列不为空时,通知消费者获取元素
}

This article has been included in the github repository. This repository is used to share high-frequency interview questions from major Internet companies and summary of Java core knowledge, including Java basics, concurrency, MySQL, Springboot, MyBatis, Redis, RabbitMQ, etc., a must for interviews! Welcome everyone star!
github address: https://github.com/Tyson0314/Java-learning
If github is not accessible, you can visit the gitee repository.
gitee address: https://gitee.com/tysondai/Java-learning


程序员大彬
468 声望489 粉丝

非科班转码,个人网站:topjavaer.cn