9. unblocking concurrentqueue queue, concurrentqueue list
Jdk1.7.0 _ 79
A queue is a very common data structure, one-in-one, first-out.
Two types of queues are provided in Java concurrent packages: non-blocking queues and blocking queues. Of course, they are thread-safe, there is no need to worry about unpredictable issues in the multi-threaded concurrency environment. Why are there non-blocking and blocking? The non-blocking and blocking here are bounded or not, that is, it is not given a default capacity during initialization. For blocked bounded queues, if the queue is full, if the queue is empty, no thread can be queued. For non-blocking unbounded queues, the queue is not full or the queue is empty. Both of them ensure thread security, that is, there cannot be more than one thread simultaneously queuing or queuing operations.
Non-blocking queue: concurrent1_queue
Blocking queue: ArrayBlockingQueue, LinkedBlockingQueue ,......
This topic describes the non-blocking queue-concurentincluqueue.
First, check the default concurrentincluqueue constructor and observe what operations it performs during initialization.
//ConcurrentLinkedQueue public ConcurrentLinkedQueue() { head = tail = new Node<E>(null); }
We can see that concurrentincluqueue has a head node and a tail node inside it, pointing to a node during initialization.
The following two methods are provided for the queue (insert) operation (actually ONE ):
|
|
|
|
|
Join (insert) |
Add (e) (which calls the offer method internally ,) |
Offer (e) (inserted to the end of the queue. If the queue is unbounded, true is always returned) |
1 // concurrent1_queue # offer 2 public boolean offer (E) {3 checkNotNull (e); // whether the team-entry element is empty, null values are not allowed to join 4 final nodes <E> newNode = new Node <E> (e ); // construct the element into a Node. 5/* tail points to the End Node of the queue, but sometimes tail. next is the final Node */6 for (Node <E> t = tail, p = t;) {7 Node <E> q = p. next; 8 if (q = null) {// at this time, p points to the true end node of the queue 9 if (p. casNext (null, newNode) {// cas algorithm, p. next = newNode10 if (p! = Tail) // point the tail to the End Node of the queue 11 casTail (t, newNode); 12 return true; 13} 14} 15 else if (p = q) 16 p = (t! = (T = tail ))? T: head; 17 else18 p = (p! = T & t! = (T = tail) t: q; 19} 20}
Shows the offer team-up process:
① There are no elements in the queue. The first time you enter the queue:
Enter the loop body:
T = tail;
P = tail;
Q = p. next = null;
Determine whether reference p of the End Node points to the End Node (if (q = null)-> yes:
CAS algorithm sets the nodes to the next node (p. casNext (null, newNode) of the last node ))
Judge whether reference p of tail node pointer is greater than or equal to 1 next node (if (p! = T)-> NO
Returns true.
② There are elements in the queue for the queue operation:
1) first cycle:
T = tail;
P = tail;
Q = p. next = Node1;
Judge whether reference p of tail node Pointer Points to tail node (if (q = null)-> NO
Determine whether the reference p of the tail node Pointer Points to the tail node (else if (p = q)-> NO
Move the reference p of the tail node pointer backward (p = (p! = T & t! = (T = tail ))? T: q;)-> p = Node1
2) The second cycle:
T = tail;
P = Node1;
Q = p. next = null;
Determine whether the reference p of the tail node Pointer Points to the real tail node (if (q = null)-> yes:
CAS algorithm sets the nodes to the next node (p. casNext (null, newNode) of the last node ))
Judge whether reference p of tail node pointer is greater than or equal to 1 next node (if (p! = T)-> yes:
Update tail node (casTail (t, nextNode ))
Returns true.
The operations to join the team are completed by the CAS algorithm to ensure its security. The final node should be located first during the entire process of joining the team, and then the next node of the final node should be set using the CAS algorithm. The tail node of the queue must be located first during the entire queue process. Is it better to direct the tail node to the End Node all the time? Each time it is tail-> next = newNode; tail = newNode; in this way, there is no problem in the single-threaded environment. However, thread security must be considered in the multi-threaded concurrency environment, every time you update a tail node, you must use CAS to update the tail node each time. This will inevitably reduce the efficiency of joining the team, therefore, the reason why the tail node of concurrent1_queue does not always point to the End Node of the queue is to reduce the number of tail node updates and improve the efficiency of joining the queue.
The following methods are provided for the release (delete) operation:
1 //ConcurrentLinkecQueue#poll 2 public E poll() { 3 restartFromHead: 4 for (;;) { 5 for (Node<E> h = head, p = h, q;;) { 6 E item = p.item; 7 if (item != null && p.casItem(item, null)) { 8 if (p != h) 9 updateHead(h, ((q = p.next) != null) ? q : p);10 return item;11 } 12 else if ((q = p.next) == null) {13 updateHead(h, p);14 return null;15 }16 else if (p == q)17 continue restartFromHead;18 else19 p = q;20 }21 }22 }
There are two elements in the above queue as an example: (Note that at first, the head points to an empty node)
Release (delete ):
1) first cycle:
H = head;
P = head;
Q = null;
Item = p. item = null;
Judge whether the reference of the head node pointer is not a null node (if (item! = Null)-> NO, that is, an empty node.
Judgment (omitted)
Judgment (omitted)
Move the reference p of the head node pointer backward (p = q)
2) The second cycle:
H = head;
P = q = Node1;
Q = Node1;
Item = p. item = Node1.item;
Determine whether the reference p of the head node pointer is not a null node (if (item! = Null)-> yes, that is, it is not an empty node:
Determine whether the head node pointer and p point to the same node (if (p! = H)-> NO:
Update the header node (updateHead (h, (q = p. next )! = Null )? Q: p ))
Return item
In fact, if you continue to leave the team, you will find that the team is similar to the team, and the head node will not be updated every time you leave the team. The principle is the same as that of tail.
The concurrent1_queue # size method will traverse the entire queue, but it is not very efficient. If you need to call its size method, especially for loop, I suggest writing it as follows:
for (int i = 0, int size = concurrentLinkedQueue.size(); i < size;i++)
This ensures that the size method is called once every cycle to traverse the queue.