[Java concurrent programming] 22. Exchanger source code parsing and exchanger source code

Source: Internet
Author: User

[Java concurrent programming] 22. Exchanger source code parsing and exchanger source code

Exchanger is a two-way data transmission. Two threads exchange data at a synchronization point. The first thread will wait for the second thread to execute exchange
SynchronousQueue is a one-way data transmission between two threads, one put and one take.

Here is an example of how to use

Public class ExchangerDemo {public static void main (String [] args) {Exchanger <List <Integer> exchanger = new Exchanger <> (); new Consumer (exchanger ). start (); // facilitates debugging and enables consumer to execute exchange try {Thread first. sleep (1000*5);} catch (InterruptedException e) {e. printStackTrace ();} new Producer (exchanger ). start ();} static class Consumer extends Thread {List <Integer> list = new ArrayList <> (); Exchanger <List <Integer> exchanger = null; public Consumer (Exchanger <List <Integer> exchanger) {super (); this. exchanger = exchanger;} @ Override public void run () {for (int I = 0; I <1; I ++) {try {list = exchanger. exchange (list);} catch (InterruptedException e) {e. printStackTrace ();} System. out. print (list. get (0) + ","); System. out. print (list. get (1) + ","); System. out. print (list. get (2) + ","); System. out. print (list. get (3) + ","); System. out. println (list. get (4) + ",") ;}} static class Producer extends Thread {List <Integer> list = new ArrayList <> (); exchanger <List <Integer> exchanger = null; public Producer (Exchanger <List <Integer> exchanger) {super (); this. exchanger = exchanger;} @ Override public void run () {Random rand = new Random (); for (int I = 0; I <1; I ++) {list. clear (); list. add (rand. nextInt (10000); list. add (rand. nextInt (10000); list. add (rand. nextInt (10000); list. add (rand. nextInt (10000); list. add (rand. nextInt (10000); try {list = exchanger. exchange (list);} catch (InterruptedException e) {e. printStackTrace ();}}}}}

Let's look at the internal structure.

Private static final class Node extends AtomicReference <Object> {/** the data provided by the thread that creates the Node for exchange. */Public final Object item;/** waiting Thread */public volatile Thread waiter;/*** Creates node with given item and empty hole. * @ param item the item */public Node (Object item) {this. item = item ;}}/*** a Slot is the place where a pair of threads exchange data. * The Slot is filled with cache lines to avoid pseudo-sharing issues. * Although filling leads to a waste of space, slots are created on demand, which is generally no problem. */Private static final class Slot extends AtomicReference <Object >{// Improve likelihood of isolation on <= 64 byte cache lines long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;}/*** Slot array, which is initialized only when necessary. * Modified with volatile, because it can be safely constructed using the dual lock detection method. */Private volatile Slot [] arena = new Slot [CAPACITY];/*** arena (Slot Array) CAPACITY. Set this value to avoid competition. */Private static final int CAPACITY = 32;/*** maximum value of the slot in use. After a thread has undergone multiple CAS competitions, * This value will increase progressively. When a thread's spin wait times out, this value will decrease. */Private final AtomicInteger max = new AtomicInteger ();
Key Technical Points 1: CacheLine Filling

The place where data is exchanged is the Slot. Each thread for data exchange is represented by a Node internally. The Slot is actually a AtomicReference.

Slot is actually a AtomicReference. The q0, q1, And. qd variables in it are redundant and unnecessary. It plays a role in cache line filling and avoids pseudo-sharing; Pseudo-sharing Description: Assume that two mutually independent attributes a and B of a Class are continuous in the memory address (for example, the first and last pointers of a FIFO queue ), they are usually loaded into the same cpu cache line. In the case of concurrency, if a thread modifies a, the entire cache line will become invalid (including B). When another thread reads B, it needs to be loaded from the memory again, in the case that multiple threads frequently modify AB, although a and B seem independent, they interfere with each other and affect performance very much.Key Technical Points 2: Lock Separation

For the same ConcurrentHashMap type, Exchange does not define only one slot, but an array of slots. In this way, when multiple threads call exchange, they can be matched in different slots.

The basic idea of exchange is as follows:
(1) Calculate its own slot index by hash Based on the thread id of each thread;
(2) If you are lucky, this slot will be occupied (the slot contains nodes), and someone is waiting for exchange, then exchange with it;
(3) If the slot is empty (there is no node in the slot), it is occupied by itself and exchanged among others. No one exchanged. Move the slot forward, cancel the content in the current slot, and halved the index. Then, check whether there is any exchange;
(4) If there is no interaction in the position 0, it will be blocked and will remain waiting. Other threads will also keep moving until 0.

So the position 0 is the "endpoint" of a transaction! The transaction cannot be found in other locations, and the transaction will eventually reach 0.

/*** Wait for other threads to reach the switching point and exchange data with them. ** If other threads arrive, data is exchanged and a result is returned. ** If other threads do not arrive, the current thread waits and knows the following situation: * 1. There are other threads for data exchange. * 2. The current thread is interrupted. */Public V exchange (V x) throws InterruptedException {if (! Thread. interrupted () {// checks whether the current Thread is interrupted. // Exchange data. Object v = doExchange (x = null? NULL_ITEM: x, false, 0); if (v = NULL_ITEM) return null; // check whether the result is null. If (v! = CANCEL) // check whether it is canceled. Return (V) v; Thread. interrupted (); // clear the interrupt mark.} Throw new InterruptedException ();}/*** wait for other threads to reach the switching point and exchange data with them. ** If other threads arrive, data is exchanged and a result is returned. ** If other threads do not arrive, the current thread waits and knows the following situation: * 1. There are other threads for data exchange. * 2. The current thread is interrupted. * 3. Timeout. */Public V exchange (V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {if (! Thread. interrupted () {Object v = doExchange (x = null? NULL_ITEM: x, true, unit. toNanos (timeout); if (v = NULL_ITEM) return null; if (v! = CANCEL) return (V) v; if (! Thread. interrupted () throw new TimeoutException ();} throw new InterruptedException ();}

The above methods call the doExchange method. The main logic is in this method. Analyze this method:

Private Object doExchange (Object item, boolean timed, long nanos) {Node me = new Node (item); int index = hashIndex (); // calculate the transaction location (slot) int fails = 0; for (;) {Object y; Slot slot = arena [index] based on the thread id. if (slot = null) createSlot (index); // slot = null, create a slot, return to the for loop, and start else if (y = slot. get ())! = Null. compareAndSet (y, null) {// Key Point 1: slot is cleared, Node is taken out, and the two interact in the Node. Give the Slot to the people behind the Slot and make the interaction location Node you = (Node) y; if (you. compareAndSet (null, item) {// Replace the items in Node with your own LockSupport. unpark (you. waiter); // wake the other party to return you. item; // take the other party's items by yourself} // key aspect 2: If you are not lucky, you will be snatched by another thread in the Node to switch and return to the for loop, start again} else if (y = null & // The slot is empty (no Node), the slot is occupied by itself. compareAndSet (null, me) {if (index = 0) // if it is 0, block it by yourself and wait for others to exchange return timed? AwaitNanos (me, slot, nanos): await (me, slot); Object v = spinWait (me, slot); // if (v! = CANCEL) // when the spin waits, It's a good luck. Some people exchange it and return v; me = new Node (item); // when the spin is done, no one exchanges it. Run the following command to cut the index by half and move it to another location to start the for loop int m = max. get (); if (m> (index >>> = 1) max. compareAndSet (m, m-1);} else if (++ fails> 1) {// failure case1: no one in the slot, and the slot is occupied by others. int m = max. get (); if (fails> 3 & m <FULL & max. compareAndSet (m, m + 1) index = m + 1; // if the matching fails three times, extend the index and start the for loop else if (-- index <0) again) index = m ;}}}
The image here is actually "me" and "you" (there may be multiple "me" and multiple "you ") make a transaction in a place called Slot (hand-paid, first-hand delivery), the process is divided into the following steps: 1. when you arrive at the transaction location (Slot), I will try to call you for a transaction. If you respond to me and decide to trade with me, you will enter Step 2; if someone calls you one step ahead, I can only find someone else and go to step 2. 2. if I give you the money, you may receive the money and give it to me. The transaction is over. It may also be too slow (timeout) or answer a call (interrupted). If TM is not sold, I can only find someone else to buy the goods (from the beginning ). 3. when I arrive at the transaction location, if you are not there, I will try to occupy this transaction point first (one-buttered stool ...), if I have successfully preemptible a single room (transaction point), I will sit here and wait for you to get the goods for the transaction and go to step 2. If someone else grabs the seat, I can only find somewhere else, go to step 2. 4. you took the goods, called me for a transaction, and completed the transaction. Maybe you haven't come for a long time. I'm not waiting. Continue to look for other people to trade, I took a look when I left. There were not many people in total. It was a waste of things to get so many single rooms (transaction slots). I shouted to the transaction location administrator: there are not a few people in total. Let's get rid of so many single rooms and give it to brother !. Then I try to find someone else to buy the goods (from the beginning); or my boss called me to prevent me from buying the goods (interrupted ). 5. if I failed to make the transaction twice before, I would like to see if the position (Slot subscript) selected by TM is not good for Feng Shui. Change the location to continue (from the beginning ); if I have tried to make a transaction four times before, I am angry. I shouted to the administrator of the transaction location: open another single room (Slot) for my brother and add a stool, who can use so many people with such broken stools! Take a look at the awaitNanos method:
/*** Wait for the Slot marked as 0 to get the value filled by other threads. * If the Slot times out or is interrupted before it is filled, the operation fails. */Private Object awaitNanos (Node node, Slot slot, long nanos) {int spins = TIMED_SPINS; long lastTime = 0; Thread w = null; (;;) {Object v = node. get (); if (v! = Null) // if the value has been filled by another thread, this value is returned. Return v; long now = System. nanoTime (); if (w = null) w = Thread. currentThread (); else nanos-= now-lastTime; lastTime = now; if (nanos> 0) {if (spins> 0) -- spins; // spin several times first. Else if (node. waiter = null) node. waiter = w; // After the spin stage is complete, set the current thread to the node's waiter domain. Else if (w. isInterrupted () tryCancel (node, slot); // if the current thread is interrupted, try to cancel the node. Else LockSupport. parkNanos (node, nanos); // block the specified time.} Else if (tryCancel (node, slot )&&! W. isInterrupted () // After timeout, if the current thread is not interrupted, check other Slot Array locations to see if the node is waiting for data exchange return scanOnTimeout (node );}}

The number of spin times in awaitNanos is TIMED_SPINS. Here we describe the number of spin times:

/*** When the number of spin operations in a single-core processor is 0 *, this value is set to the average value of context switching time in most systems. */Private static final int SPINS = (NCPU = 1 )? 0: 2000;/*** the number of times the previous spin is blocked in case of timeout .. * The less spin times of timeout wait are because the detection time also takes time. * The value here is an experience value. */Private static final int TIMED_SPINS = SPINS/20;

Finally, let's take a look at arena (Slot Array), the default capacity and the actual maximum subscript values:

private static final int CAPACITY = 32;  /**  * The value of "max" that will hold all threads without  * contention.  When this value is less than CAPACITY, some  * otherwise wasted expansion can be avoided.  */  private static final int FULL =      Math.max(0, Math.min(CAPACITY, NCPU / 2) - 1);  

As mentioned above, the default arena capacity is 32, which aims to reduce thread competition. However, in fact, arena usage does not exceed the FULL value (to avoid some space waste ). This value is set to 32 (default CAPACITY) and half of the number of CPU cores. The smaller values of these two numbers are less than 1 and the smaller values of 0 .... that is to say, if there are many CPU cores, the maximum value is 31. If it is a single-core or dual-core CPU, this value is 0, that is, arena [0] can only be used. This is also why the (approximate) modulo operation in the previous hashIndex method is complicated, because the actually usable Slot Array range may not be the power of 2.

 

Source:

Http://blog.csdn.net/chunlongyu/article/details/52504895
Http://brokendreams.iteye.com/blog/2253956

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.