"Practical Java High Concurrency Programming 1" Pointers in Java: unsafe class
"Practical Java High Concurrency Programming 2" lock-free object reference: Atomicreference
"Practical Java High Concurrency Programming 3" object reference with timestamp: atomicstampedreference
"Practical Java High Concurrency Programming 4" Array can also be unlocked: Atomicintegerarray
"Practical Java High Concurrency Programming 5" Lets ordinary variables also enjoy atomic manipulation
"Practical Java High Concurrency Programming 6" challenge lock-free algorithm: vector without lock implementation
In the introduction to the thread pool, a very special waiting queue Synchronousqueue is mentioned. Synchronousqueue has a capacity of 0, any write to synchronousqueue needs to wait for a read to Synchronousqueue, and vice versa. Therefore, Synchronousqueue is more of a data exchange channel than a queue. How did that Synchronousqueue's wonderful function be realized?
Now that I'm going to introduce it in this section, Synchronousqueue like a lock-free operation. In fact, the synchronousqueue inside is also a lot of use of the lock-free tool.
For Synchronousqueue, it abstracts the actions of the put () and take () two distinct functions into a common method Transferer.transfer (). Literally, this is the meaning of data transfer. Its full signature is as follows:
Boolean Long Nanos)
When parameter e is non-null, it means that the current operation is passed to a consumer and, if empty, that the current operation requires a data request. The timed parameter determines whether a timeout time exists, and Nanos determines the length of timeout. If the return value is non-null, the data is represented as well as accepted or normally provided, and if empty, it indicates a failure (timeout or interruption).
A thread waiting queue is maintained internally by the Synchronousqueue. The wait queue holds information about the waiting thread and related data. For example, when a producer puts data into Synchronousqueue, if no consumer accepts it, the data itself and the thread object are packaged in the queue for waiting (because the Synchronousqueue volume is 0 and no data can be placed properly).
The implementation of the Transferer.transfer () function is the core of the Synchronousqueue, which is broadly divided into three steps:
1, if the waiting queue is empty, or the type of node in the queue and this operation is consistent, then the current operation is pressed into the queue waiting. For example, waiting in the queue is read thread Wait, this operation is also read, so these 2 read all need to wait. Threads that enter the waiting queue may be suspended, and they will wait for a "match" operation.
2, if the waiting queue of elements and this operation is complementary (such as waiting for the operation is read, and this operation is write), then insert a "complete" State of the node, and let him "match" to a waiting node. The 2 nodes are then ejected, and the corresponding 2 threads continue to execute.
3. If the thread discovers that the node waiting for the queue is the "complete" node. Then help this node to complete the task. Its process and step 2 are consistent.
The implementation of Step 1 is as follows (code reference JDK 7u60):
SNode h =Head; if(H = =NULL|| H.mode = = mode) {//if the queue is empty or the mode is the same if(timed && nanos <= 0) {//do not wait if(H! =NULL&&h.iscancelled ()) Cashead (h, H.next); //Handling Cancellation Behavior Else return NULL; } Else if(Cashead (h, s =Snode (S, E, H, mode))) {SNode m= Awaitfulfill (s, timed, Nanos);//wait until a matching operation appears if(m = = s) {//waiting to be canceledClean (s); return NULL; } if((h = head)! =NULL&& H.next = =s) cashead (h, S.next); //Fulfiller to help s return(mode = = REQUEST)?M.item:s.item; } }
In the preceding code, line 1th Snode represents the nodes in the wait queue. Internally encapsulates information such as current thread, next node, matching node, data content, and so on. Line 2nd, the current waiting queue is empty, or the pattern of elements in the queue is the same as this operation (for example, all read operations, then all must wait). Line 8th, generate a new node and place it on the head of the queue, which represents the current thread. If the queue succeeds, the 9th row of the Awaitfulfill () function is executed. The function spins the spin wait and eventually suspends the current thread. Until a corresponding operation is generated, it wakes up. After the thread is awakened (indicating that the data has been read or the data itself has been read by another thread), the 14~15 line attempts to help the corresponding thread to complete the two head node's outbound operations (this is just friendship help). And at the end, it returns the data read or written (line 16th).
the implementation of Step 2 is as follows:
}Else if(!isfulfilling (H.mode)) {//is in fulfill state if(H.iscancelled ())//if it was canceled beforeCashead (H, H.next);//Eject and try again Else if(Cashead (H, S=snode (S, E, H, fulfilling|mode))) { for(;;) {//loop until match or no waiting.SNode m = s.next;//m is the matching of S (match) if(M = =NULL) {//there's no waiting.Cashead (s),NULL);//Eject Fulfill nodes =NULL;//next time you use a new node Break;//start the main loop again} SNode mn=M.next; if(M.trymatch (s)) {Cashead (S, MN); //eject S and M return(mode = = REQUEST)?M.item:s.item; } Else //Match failedS.casnext (M, MN);//Help removing Nodes } } }
In the above code, first determine if the head node is in fulfill mode. If it is, you need to go to step 3. Otherwise, you will try the corresponding fulfill thread. Line 4th, generate a Snode element, set to fulfill mode, and press it into the queue header. Next, set m (the original queue head) to be the matching node of S (line 13th), the Trymatch () operation will activate a waiting thread and pass m to that thread. If the setting is successful, the data delivery is completed, and the S and m two nodes are ejected (line 14th). If Trymatch () fails, it means that there are already other threads that have done the work for me, so it is easy to delete the M node (line 17th), because the node's data has been posted, does not need to be processed again, and then jumps to the 5th line of the loop body, the next waiting thread matching and data delivery, Until there are no waiting threads in the queue.
Step 3: If the thread is executing and discovers that the head element happens to be fulfill mode, it will help the fulfill node to be executed as soon as possible:
}Else{//Help a FulfillerSNode m =h.next;//M is the match of H if(M = =NULL)//There are no waiting personsCashead (H,NULL);//Eject Fulfill node Else{SNode mn=M.next; if(M.trymatch (h))//try MatchCashead (H, MN);//eject H and M Else //Match failedH.casnext (M,MN);//Help removing Nodes } }
The above code is executed in exactly the same principle as step 2. The only difference is that step 3 does not return, because the work done in step 3 is to help other threads deliver their data as soon as possible. Instead of completing the corresponding operation, the thread enters step 3 again into the cycle body (not given in the code), starting with step 1 to re-judge the condition and post the data.
As you can see from the entire data delivery process, in Synchronousqueue, all the threads involved in the work are not just the relationships of competing resources. More importantly, they help each other. Within a thread, it may help other threads to do their work. This model can reduce the likelihood of starvation to a greater extent, and improve the overall parallelism of the system.
From the "Practical Java High concurrency program design"
"Practical Java High Concurrency Programming 7" enables threads to help each other--synchronousqueue implementation