是另一种栅栏,它是一种两方two-party栅栏,各方在栅栏位置上交换数据。

当两方执行不对称的操作时,exchanger会非常有用。

场景例子:

当一个线程向缓冲区写入数据,而另一个线程从缓冲区中读取数据。这些线程可以使用Exchanger来汇合,并将满的缓冲区与空的缓冲区交换。当两个线程通过Exchanger交换对象时,这种交换就把这两个对象安全地发布给另一方。

数据交换的时机取决于应用程序的相应需求。最简单的方案是当缓冲区被填满时,由填充任务进行交换,当缓冲区为空时,由清空任务进行交换。这样会把需要交换的次数降至最低,但如果新数据的到达率不可预测,那么一些数据的处理过程就将延迟。另一个方法是不仅当缓冲被填满时进行交换,并且当缓冲被填充到一定程度并保持一定时间后,也进行交换。

例子:

exchange使用场景 java exchanger java_System

exchange使用场景 java exchanger java_System_02

1 package com.citi.test.mutiplethread.demo5;
 2 
 3 import java.util.concurrent.Exchanger;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 public class ExchangerTest {
 8     public static void main(String[] args) {
 9         ExecutorService executor=Executors.newCachedThreadPool();
10         final Exchanger exchanger=new Exchanger();
11         executor.execute(new Runnable() {
12             String data1="Ling";
13             @Override
14             public void run() {
15                 doExchangerWork(data1, exchanger);
16             }
17         });
18         executor.execute(new Runnable() {
19             String data1="huhx";
20             @Override
21             public void run() {
22                 doExchangerWork(data1, exchanger);
23             }
24         });
25         executor.shutdown();
26     }
27     private static void doExchangerWork(String data1,Exchanger exchanger){
28         try {
29             System.out.println(Thread.currentThread().getName()+" 正在把数据"+data1+"交换出去");
30             Thread.sleep((long)(Math.random()*1000));
31             
32             String data2=(String)exchanger.exchange(data1);
33             System.out.println(Thread.currentThread().getName()+" 交换数据到"+data2);
34         } catch (Exception e) {
35             e.printStackTrace();
36         }
37     }
38 }

View Code

如果我们一直买东西,而不邮寄东西,那么Exchanger类其实就变成了简化版本的生产者和消费者的模型。快递员就是生产者,我们本身就是消费者,而柜子就成为了我们媒介容器,看下面的一个例子:

exchange使用场景 java exchanger java_System

exchange使用场景 java exchanger java_System_02

1 package com.citi.test.mutiplethread.demo5;
 2 
 3 import java.util.concurrent.Exchanger;
 4 import java.util.concurrent.TimeUnit;
 5 import java.util.concurrent.atomic.AtomicInteger;
 6 
 7 public class ExchangerTest1 {
 8     private static Exchanger<DataBuffer<Integer>> exchanger=new Exchanger<>();
 9     static DataBuffer<Integer> initialEmptyBuffer=new DataBuffer<Integer>();
10     static DataBuffer<Integer> initialFullBuffer=new DataBuffer<Integer>();
11     static AtomicInteger countDown=new AtomicInteger(5);
12     static class ProducerWorker implements Runnable{
13         long sleep;
14         public ProducerWorker(long sleep) {
15             this.sleep=sleep;
16         }
17         @Override
18         public void run() {
19             DataBuffer<Integer> currentBuffer=initialEmptyBuffer;
20             while(currentBuffer!=null&&countDown.get()>0){
21                 try {
22                     TimeUnit.SECONDS.sleep(sleep);
23                 } catch (InterruptedException e) {
24                     // TODO Auto-generated catch block
25                     e.printStackTrace();
26                 }
27                 currentBuffer.put(countDown.get());//每次放入数据
28                 if(currentBuffer.isFull()){
29                     try {
30                         System.out.println(Thread.currentThread().getName()+" 放入了快递"+countDown.get());
31                         currentBuffer=exchanger.exchange(currentBuffer);
32                     } catch (InterruptedException e) {
33                         // TODO Auto-generated catch block
34                         e.printStackTrace();
35                     }//交换后得到null
36                 }
37                 countDown.getAndDecrement();
38             }
39         }
40     }
41     
42     static class ConsumerWorker implements Runnable{
43         long sleep;
44         public ConsumerWorker(long sleep) {
45             this.sleep=sleep;
46         }
47         @Override
48         public void run() {
49             DataBuffer<Integer> currentBuffer=initialFullBuffer;
50             while(currentBuffer!=null&&countDown.get()>0){
51                 try {
52                     TimeUnit.SECONDS.sleep(sleep);
53                 } catch (InterruptedException e) {
54                     // TODO Auto-generated catch block
55                     e.printStackTrace();
56                 }
57                 //如果为空就进行交换
58                 if(currentBuffer.isEmpty()){
59                     try {
60                         currentBuffer=exchanger.exchange(currentBuffer);//交换数据
61                         Integer value=currentBuffer.get();
62                         System.out.println(Thread.currentThread().getName()+" 拿走了快递"+value);
63                         System.out.println();
64                     } catch (Exception e) {
65                         e.printStackTrace();
66                     }
67                 }
68             }
69         }
70     }
71     public static void main(String[] args) {
72         new Thread(new ProducerWorker(1),"快递员").start();
73         new Thread(new ConsumerWorker(3),"我      ").start();
74     }
75     
76     private static class DataBuffer<T>{
77         T data;
78         public boolean isFull(){
79             return data!=null;
80         }
81         public boolean isEmpty(){
82             return data==null;
83         }
84         public T get(){
85             T d=data;
86             data=null;
87             return d;
88         }
89         public void put(T data){
90             this.data=data;
91         }
92     }
93 }

View Code

下面是主要的内部类,属性和方法。

exchange使用场景 java exchanger java_System

exchange使用场景 java exchanger java_System_02

1 /**
 2  * Nodes hold partially exchanged data.  This class
 3  * opportunistically subclasses AtomicReference to represent the
 4  * hole.  So get() returns hole, and compareAndSet CAS'es value
 5  * into hole.  This class cannot be parameterized as "V" because
 6  * of the use of non-V CANCEL sentinels.
 7  
 8     Node 持有部分交换数据。这个类继承AtomicReference适时地代表那个洞。
 9     所以get()返回洞,并且用CAS来将值填充进洞。
10     
11  
12  */
13 private static final class Node extends AtomicReference<Object> {
14     /** The element offered by the Thread creating this node. */
15     public final Object item;
16 
17     /** The Thread waiting to be signalled; null until waiting. */
18     public volatile Thread waiter;
19 
20     /**
21      * Creates node with given item and empty hole.
22      * @param item the item
23      */
24     public Node(Object item) {
25         this.item = item;
26     }
27 }
28 
29 /**
30  * A Slot is an AtomicReference with heuristic padding to lessen
31  * cache effects of this heavily CAS'ed location.  While the
32  * padding adds noticeable space, all slots are created only on
33  * demand, and there will be more than one of them only when it
34  * would improve throughput more than enough to outweigh using
35  * extra space.
36  */
37 private static final class Slot extends AtomicReference<Object> {
38     // Improve likelihood of isolation on <= 64 byte cache lines
39     long q0, q1, q2, q3, q4, q5, q6, q7, q8, q9, qa, qb, qc, qd, qe;
40 }
41 
42 /**
43  * Main exchange function, handling the different policy variants.
44  * Uses Object, not "V" as argument and return value to simplify
45  * handling of sentinel values.  Callers from public methods decode
46  * and cast accordingly.
47  *
48  * @param item the (non-null) item to exchange
49  * @param timed true if the wait is timed
50  * @param nanos if timed, the maximum wait time
51  * @return the other thread's item, or CANCEL if interrupted or timed out
52  */
53 private Object doExchange(Object item, boolean timed, long nanos) {
54     Node me = new Node(item);                 // Create in case occupying
55     int index = hashIndex();                  // Index of current slot
56     int fails = 0;                            // Number of CAS failures
57 
58     for (;;) {
59         Object y;                             // Contents of current slot
60         Slot slot = arena[index];
61         if (slot == null)                     // Lazily initialize slots
62             createSlot(index);                // Continue loop to reread
63         else if ((y = slot.get()) != null &&  // Try to fulfill
64                  slot.compareAndSet(y, null)) {
65             Node you = (Node)y;               // Transfer item
66             if (you.compareAndSet(null, item)) {
67                 LockSupport.unpark(you.waiter);
68                 return you.item;
69             }                                 // Else cancelled; continue
70         }
71         else if (y == null &&                 // Try to occupy
72                  slot.compareAndSet(null, me)) {
73             if (index == 0)                   // Blocking wait for slot 0
74                 return timed ?
75                     awaitNanos(me, slot, nanos) :
76                     await(me, slot);
77             Object v = spinWait(me, slot);    // Spin wait for non-0
78             if (v != CANCEL)
79                 return v;
80             me = new Node(item);              // Throw away cancelled node
81             int m = max.get();
82             if (m > (index >>>= 1))           // Decrease index
83                 max.compareAndSet(m, m - 1);  // Maybe shrink table
84         }
85         else if (++fails > 1) {               // Allow 2 fails on 1st slot
86             int m = max.get();
87             if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))
88                 index = m + 1;                // Grow on 3rd failed slot
89             else if (--index < 0)
90                 index = m;                    // Circularly traverse
91         }
92     }
93 }

View Code

底层原理分析:

用到的关键技术是

1.使用CAS自旋来进行数据交换。

2.使用LockSupport的park方法使交换线程进入休眠等待,用unpartk方法使线程唤醒。

3.此外还声明了一个Node对象来存储交换数据,该类继承了AtomicReference.

使用exchanger可以轻松的实现两个线程交换数据。如果超过两个线程,很可能会有问题。

 

 

参考资料:

https://cloud.tencent.com/developer/article/1350850