Java multi-threading series - "JUC lock" principle and examples Exchanger exchanger 12 of

Exchanger Profile

Exchanger is a suite of tools from jdk1.5 provided from the beginning, it is generally used to exchange data between the two worker threads. 

 Constant introduction

     private static final int ASHIFT = 7; // 两个有效槽(slot -> Node)之间的字节地址长度(内存地址,以字节为单位),1 << 7至少为缓存行的大小,防止伪共享
     private static final int MMASK = 0xff; // 场地(一排槽,arena -> Node[])的可支持的最大索引,可分配的大小为 MMASK + 1
     private static final int SEQ = MMASK + 1; // bound的递增单元,确立其唯一性
     private static final int NCPU = Runtime.getRuntime().availableProcessors(); // CPU的个数,用于场地大小和自旋控制
     static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1; // 最大的arena索引
     private static final int SPINS = 1 << 10; // 自旋次数,NCPU = 1时,禁用
     private static final Object NULL_ITEM = new Object();// 空对象,对应null
     private static final Object TIMED_OUT = new Object();// 超时对象,对应timeout

Ashift, an effective address length between two grooves 7 is << 1 (at least the size of a cache line, to avoid false sharing problems, see below)

Mmask, multi-slot exchange can support a maximum index, size MMASK + 1 (index starts from 0)

SEQ, bound increment unit, which determines uniqueness (high)

Number NCPU, CPU's

FULL, the largest arena index, not more than MMASK; arena, a row of slot, in order to obtain good flexibility, avoiding all the thread contention with a slot.

On SPINS, spin frequency to spin-wait, wait is lightest, followed by spin -> yield -> block

 

Between the dummy is shared, cache memory and the cache line units to exchange data, according to the principle of locality, adjacent data address space is loaded into the cache the same data block (cache line), and the array is continuous (logical relates to virtual memory) memory address space, and therefore, a plurality of slot will be loaded onto the same cache line, when a slot change, causes all of the data on the slot where the cache line (including other slot) is invalid, need to reload from memory, affect performance.

So, to avoid this situation, the data needs to be filled, so that the effective slot is not loaded to the same cache line. The size of the filler is, 1 << 7, as shown in FIG.

 

Data Structure Node 

    static final class Node {
        int index; // arena的索引
         int bound; // 记录上次的bound
         int collides; // 当前bound下CAS失败的次数
         int hash; // 伪随机,用于自旋
         Object item; // 当前线程携带的数据
         volatile Object match; // 存放释放线程携带的数据
         volatile Thread parked; // 挂在此结点上阻塞着的线程
     }

index, arena Index

bound, bound last record

collides, the current number of bound under CAS failure, a maximum of m, m (bound & MMASK) of maximum effective index of the current lower bound, traversing from right to left, until collides == m, the effective index has traversed over the slot , time slots need to grow, the growth mode is reset bound (SEQ update version-dependent, high; +1, low), while the reset collides

the hash, pseudo-random, for spin

item, the data carried by the current thread

match, storage release thread (to exchange thread) to carry data

Parked, hanging on this junction blocking the thread waiting to be released

See below

Participant data structure

     // 每个线程携带一个Node
     static final class Participant extends ThreadLocal<Node> {
         public Node initialValue() {
             return new Node();
         }
     }

Participant directly inherited from ThreadLocal save the current thread to carry Node, Node exchange operations rely mainly on the behavior of

Introduction property

     private final Participant participant;// 每个线程携带一个Node
     private volatile Node[] arena; // 场地,Node数组
     private volatile Node slot;// 槽,单个Node
     private volatile int bound;// 当前最大有效arena索引,高8位+SEQ确立其唯一性,低8位记录有效索引

bound, the index recorded maximum effective arena, the dynamic changes, when competition (slots are full) increase, decrease time slot open. bound + SEQ +/- 1, its upper + 1 (SEQ, oxff + 1) determines which version of the unique properties (for example, after +1, and -1, is actually two versions of the bound, collides to reset, and from right to left traversal of the index should be updated, in general, to the left than the right slot fierce competition slot, so from right to left to find, in order to quickly find an empty place, and try to capture it, when bound plus one and minus one after the other, traversing the index should be on the right side of the slot vacated, because we are closer to the left, to the right so to update most, if not bound uniqueness version, there is no index update, has been left traverse highly competitive slot, but also miscarriage of justice, would have been bound to be reduced, but it has increased, so the impact will be very efficient.), +/- 1 low real effective index (& mMASK)

As shown below

exchange method

     public V exchange(V x) throws InterruptedException {
         Object v;
         Object item = (x == null) ? NULL_ITEM : x; // 转换成空对象
         // arena == null, 路由到slotExchange(单槽交换), 如果arena != null或者单槽交换失败,且线程没有被中断,则路由到arenaExchange(多槽交换),返回null,则抛出中断异常
         if ((arena != null || (v = slotExchange(item, false, 0L)) == null)
                 && ((Thread.interrupted() || (v = arenaExchange(item, false, 0L)) == null)))
             throw new InterruptedException();
         return (v == NULL_ITEM) ? null : (V) v;
     }

First, determine whether the arena is null, if null, then call slotExchange method, if the arena does not return null is null, or slotExchange method, and then determine whether the current thread is interrupted (interrupt flag), interrupt exception is thrown there, did not continue call arenaExchange method, if the method returns null, throws an exception interrupt, and returns the result.

Exchange method with a timeout

     public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
         Object v;
         Object item = (x == null) ? NULL_ITEM : x;// 转换成空对象
         long ns = unit.toNanos(timeout);
         // arena == null, 路由到slotExchange(单槽交换), 如果arena != null或者单槽交换失败,且线程没有被中断,则路由到arenaExchange(多槽交换),返回null,则抛出中断异常
         if ((arena != null || (v = slotExchange(item, true, ns)) == null)
                 && ((Thread.interrupted() || (v = arenaExchange(item, true, ns)) == null)))
             throw new InterruptedException();
         if (v == TIMED_OUT)// 超时
             throw new TimeoutException();
         return (v == NULL_ITEM) ? null : (V) v;
     }

Ditto, plus the judge timeout.

slotExchange method

     private final Object slotExchange(Object item, boolean timed, long ns) {
         Node p = participant.get(); // 获取当前线程携带的Node
         Thread t = Thread.currentThread(); // 当前线程
         if (t.isInterrupted()) // 保留中断状态,以便调用者可以重新检查,Thread.interrupted() 会清除中断状态标记
             return null;
         for (Node q;;) {
             if ((q = slot) != null) { // slot不为null, 说明已经有线程在这里等待了
                 if (U.compareAndSwapObject(this, SLOT, q, null)) { // 将slot重新设置为null, CAS操作
                     Object v = q.item; // 取出等待线程携带的数据
                     q.match = item; // 将当前线程的携带的数据交给等待线程
                     Thread w = q.parked; // 可能存在的等待线程(可能中断,不等了)
                     if (w != null)
                         U.unpark(w); // 唤醒等待线程
                     return v; // 返回结果,交易成功
                 }
                 // CPU的个数多于1个,并且bound为0时创建 arena,并将bound设置为SEQ大小
                 if (NCPU > 1 && bound == 0 && U.compareAndSwapInt(this, BOUND, 0, SEQ))
                     arena = new Node[(FULL + 2) << ASHIFT]; // 根据CPU的个数估计Node的数量
             } else if (arena != null)
                 return null; // 如果slot为null, 但arena不为null, 则转而路由到arenaExchange方法
             else { // 最后一种情况,说明当前线程先到,则占用此slot
                 p.item = item; // 将携带的数据卸下,等待别的线程来交易
                 if (U.compareAndSwapObject(this, SLOT, null, p)) // 将slot的设为当前线程携带的Node
                     break; // 成功则跳出循环
                 p.item = null; // 失败,将数据清除,继续循环
             }
         }
         // 当前线程等待被释放, spin -> yield -> block/cancel
         int h = p.hash; // 伪随机,用于自旋
         long end = timed ? System.nanoTime() + ns : 0L; // 如果timed为true,等待超时的时间点; 0表示没有设置超时
         int spins = (NCPU > 1) ? SPINS : 1; // 自旋次数
         Object v;
         while ((v = p.match) == null) { // 一直循环,直到有线程来交易
             if (spins > 0) { // 自旋,直至spins不大于0
                 h ^= h << 1; // 伪随机算法, 目的是等h小于0(随机的)
                 h ^= h >>> 3;
                 h ^= h << 10;
                 if (h == 0) // 初始值
                     h = SPINS | (int) t.getId();
                 else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                     Thread.yield(); // 等到h < 0, 而spins的低9位也为0(防止spins过大,CPU空转过久),让出CPU时间片,每一次等待有两次让出CPU的时机(SPINS >>> 1)
             } else if (slot != p) // 别的线程已经到来,正在准备数据,自旋等待一会儿,马上就好
                 spins = SPINS;
             // 如果线程没被中断,且arena还没被创建,并且没有超时
             else if (!t.isInterrupted() && arena == null && (!timed || (ns = end - System.nanoTime()) > 0L)) {
                 U.putObject(t, BLOCKER, this); // 设置当前线程将阻塞在当前对象上
                 p.parked = t; // 挂在此结点上的阻塞着的线程
                 if (slot == p)
                     U.park(false, ns); // 阻塞, 等着被唤醒或中断
                 p.parked = null; // 醒来后,解除与结点的联系
                 U.putObject(t, BLOCKER, null); // 解除阻塞对象
             } else if (U.compareAndSwapObject(this, SLOT, p, null)) { // 超时或其他(取消),给其他线程腾出slot
                 v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                 break;
             }
         }
         // 归位
         U.putOrderedObject(p, MATCH, null);
         p.item = null;
         p.hash = h;
         return v;
     }

to sum up

1. Check whether the slot is empty (null), not empty, indicating that there is already a thread on this waiting, try occupying the slot, if the success of the occupation, and wait for the thread to exchange data, and wake up waiting threads, the end of the transaction, return.

2. If the slot occupied fails, create arena, but to continue [Step 1] to try to seize the slot, until the slot is empty, or seize successful closing of the transaction is returned.

 

3. If the slot is empty, it is determined whether the arena is empty, if the arena is not empty, return null, rerouted to the method arenaExchange

4. If the arena is empty, indicating that the current thread is to arrive, try to have slot, if successful, will mark slot for their own occupation, out of the loop, continue to step [5], if that fails, continue to step [1]

5 current thread waiting to be released, waiting for the order is the first spin (spin), if unsuccessful give up CPU time slice (yield), and finally not enough to blocking (block), spin -> yield -> block

6. If the timeout (if set timeout) or is interrupted, then exit the loop.

7. Finally, reset the data, the next reuse, return a result, the end.

See below

arenaExchange method

     private final Object arenaExchange(Object item, boolean timed, long ns) {
         Node[] a = arena; // 交换场地,一排slot
         Node p = participant.get(); // 获取当前线程携带的Node
         for (int i = p.index;;) { // arena的索引,数组下标
             int b, m, c;
             long j; // 原数组偏移量,包括填充值
             // 从场地中选出偏移地址为(i << ASHIFT) + ABASE的内存值,也即真正可用的Node
             Node q = (Node) U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
             if (q != null && U.compareAndSwapObject(a, j, q, null)) { // 此槽位不为null, 说明已经有线程在这里等了,重新将其设置为null, CAS操作
                 Object v = q.item; // 取出等待线程携带的数据
                 q.match = item; // 将当前线程携带的数据交给等待线程
                 Thread w = q.parked; // 可能存在的等待线程
                 if (w != null)
                     U.unpark(w); // 唤醒等待线程
                 return v; // 返回结果, 交易成功
             } else if (i <= (m = (b = bound) & MMASK) && q == null) { // 有效交换位置,且槽位为空
                 p.item = item; // 将携带的数据卸下,等待别的线程来交易
                 if (U.compareAndSwapObject(a, j, null, p)) { // 槽位占领成功
                     long end = (timed && m == 0) ? System.nanoTime() + ns : 0L; // 计算出超时结束时间点
                     Thread t = Thread.currentThread(); // 当前线程
                     for (int h = p.hash, spins = SPINS;;) { // 一直循环,直到有别的线程来交易,或超时,或中断
                         Object v = p.match; // 检查是否有别的线程来交换数据
                         if (v != null) { // 有则返回
                             U.putOrderedObject(p, MATCH, null); // match重置,等着下次使用
                             p.item = null; // 清空,下次接着使用
                             p.hash = h;
                             return v; // 返回结果,交易结束
                         } else if (spins > 0) { // 自旋
                             h ^= h << 1;
                             h ^= h >>> 3;
                             h ^= h << 10; // 移位加异或,伪随机
                             if (h == 0) // 初始值
                                 h = SPINS | (int) t.getId();
                             else if (h < 0 && // SPINS >>> 1, 一半的概率
                                     (--spins & ((SPINS >>> 1) - 1)) == 0)
                                 Thread.yield(); // 每一次等待有两次让出CPU的时机
                         } else if (U.getObjectVolatile(a, j) != p)
                             spins = SPINS; // 别的线程已经到来,正在准备数据,自旋等待一会儿,马上就好
                         else if (!t.isInterrupted() && m == 0 && (!timed || (ns = end - System.nanoTime()) > 0L)) {
                             U.putObject(t, BLOCKER, this); // 设置当前线程将阻塞在当前对象上
                             p.parked = t; // 挂在此结点上的阻塞着的线程
                             if (U.getObjectVolatile(a, j) == p)
                                 U.park(false, ns); // 阻塞, 等着被唤醒或中断
                             p.parked = null; // 醒来后,解除与结点的联系
                             U.putObject(t, BLOCKER, null); // 解除阻塞对象
                         } else if (U.getObjectVolatile(a, j) == p && U.compareAndSwapObject(a, j, p, null)) {
                             if (m != 0) // 尝试缩减
                                 U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1); // 更新bound, 高位递增,低位 -1
                             p.item = null; // 重置
                             p.hash = h;
                             i = p.index >>>= 1; // 索引减半,为的是快速找到汇合点(最左侧)
                             if (Thread.interrupted())// 保留中断状态,以便调用者可以重新检查,Thread.interrupted() 会清除中断状态标记
                                 return null;
                             if (timed && m == 0 && ns <= 0L) // 超时
                                 return TIMED_OUT;
                             break; // 重新开始
                         }
                     }
                 } else
                     p.item = null; // 重置
             } else {
                 if (p.bound != b) { // 别的线程更改了bound,重置collides为0, i的情况如下:当i != m, 或者m = 0时,i = m; 否则,i = m-1; 从右往左遍历
                     p.bound = b;
                     p.collides = 0;
                     i = (i != m || m == 0) ? m : m - 1; // index 左移
                 } else if ((c = p.collides) < m || m == FULL || !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) { // 更新bound, 高位递增,低位 +1
                     p.collides = c + 1;
                     i = (i == 0) ? m : i - 1; // 左移,遍历槽位,m == FULL时,i == 0(最左侧),重置i = m, 重新从右往左循环遍历
                 } else
                     i = m + 1; // 槽位增长
                 p.index = i;
             }
         }
     }

to sum up

1. selected from the offset address field (i << ASHIFT) + ABASE memory values, i.e. the i-th real available Node, it is determined whether the slot is empty, empty, into the step [2]; is not empty, indicating that there is a thread in this waiting, trying to seize the slot, seize success, exchange data, and wakes up the waiting thread returns, ending; not seize success, to step into the [9]

2. Check the index (i vs m) out of bounds, out of bounds, into the [step 9]; not at the limit, the next step.

3. Try to occupy the slot, grab fails, enter [Step 1]; seize success, the next step.

4. Check the match, if there is data to exchange thread, if any, to exchange data, end; if not, the next step.

5. Check spin is greater than 0, if not greater than zero, the next step; if more than 0, check hash is less than 0, and is 0 or spin half, if not, entering step [4]; if it is, so that the CPU time, then, to enter [step 4]

6. Check the interrupt, m reaches a minimum value, whether a timeout, without interruption, not timed, and reaches a minimum value m, blocked, then, into the step [4]; otherwise, the next step.

7. No thread to exchange data, trying to discard the original slot to start again, discarding failed to enter [Step 4]; otherwise, the next step.

8. bound minus 1 (m> 0), indexing half; check whether an interrupt or a timeout, and if not, entering step [1]; otherwise, end.

9. Check if bound changed, if changed, the reset collides, the index m is reset or left, the steering step [1]; otherwise, the next step.

10. Check collides reaches a maximum value, and if not, entering step [13], otherwise the next step.

11. m reaches FULL, is entered in step [13]; otherwise, the next step.

12. CAS bound plus one is successful, and if successful, I is set to m + 1, slot growth proceeds step [1]; otherwise, the next step.

13. collides plus 1, left index, enter [Step 1]

See below (see picture mouse on the picture above, right] [-?> [Open Image (I) in a new tab] -> [click (+) vector to enlarge])

Unsafe

     private static final sun.misc.Unsafe U;
     private static final long BOUND;
     private static final long SLOT;
     private static final long MATCH;
     private static final long BLOCKER;
     private static final int ABASE;
     static {
         int s;
         try {
             U = sun.misc.Unsafe.getUnsafe();
             Class<?> ek = Exchanger.class;
             Class<?> nk = Node.class;
             Class<?> ak = Node[].class;
             Class<?> tk = Thread.class;
             BOUND = U.objectFieldOffset(ek.getDeclaredField("bound"));
             SLOT = U.objectFieldOffset(ek.getDeclaredField("slot"));
             MATCH = U.objectFieldOffset(nk.getDeclaredField("match"));
             BLOCKER = U.objectFieldOffset(tk.getDeclaredField("parkBlocker"));
             s = U.arrayIndexScale(ak); // 数组增量地址
             ABASE = U.arrayBaseOffset(ak) + (1 << ASHIFT); // 数组首元素偏移地址
         } catch (Exception e) {
             throw new Error(e);
         }
         if ((s & (s - 1)) != 0 || s > (1 << ASHIFT))
             throw new Error("Unsupported array scale");
     } 

s is an element of the array occupied by each of the address space, the first array element ABASE offset, preventing false sharing

Finally, arena = new Node [(FULL + 2) << ASHIFT], FULL, <= MMASK, scale, <= 1 << ASHIFT, described (FULL + 2) << ASHIFT a Node, is actually available FULL + 2, actually FULL + 1 th, last no use, but also to prevent false sharing, if the last use, so that the right and not filled, other data modification could affect it, that is, the occurrence of false sharing problem. Maximum effective index is MMASK (bound & MMASK), but m (actual maximum index) to increase when the FULL, no growth will cycle through the slot, try to exchange data.

Pseudorandom

h ^= h << 1; h ^= h >>> 3; h ^= h << 10;

Actually xorshift algorithm, T = (I + La) (I + Rb) (I + Lc), where, L representative of left, R representative of right, a, b, c respectively represent the above formula 3, 10, I {0,1} matrix representative of a total of 32-bit (int), that is, binary int, random algorithm is represented by T. Translation is the above formula: h ^ = h << 1; h ^ = h >>> 3; h ^ = h << 10.

Why should we use 1,3,10 it?

In fact, the pseudo-random number, is not truly random, but by algorithms simulated in order to achieve the effect of random, hope is the period of the bigger the better. The so-called cycle means that when a given input, and then outputs the resultant as the next input, and so forth, until a time equal to the output of the first input happens to be a random algorithm Random number of cycles. With this concept, we can write the code under test.

Visually estimated, int type maximum period should traverse all the values ​​of the type (other than 0, singular matrix [], if it is 0, the output will always 0, not to mention the random), that is, max - min = 232 - 1

Java code

 public class PseudoRandom {
     private static final Map<Long, StringBuilder> map = new ConcurrentHashMap<>();
 
     public static void random(int a, int b, int c) {
         long cnt = 0;
         int h = 1;
         do {
             h ^= h << a;
             h ^= h >>> b;
             h ^= h << c;
             cnt++;
         } while (h != 1);
 
         StringBuilder builder = map.get(cnt);
         if (builder == null) {
             builder = new StringBuilder();
             map.put(cnt, builder);
         }
 
         builder.append(" (" + a + ", " + b + ", " + c + ")");
     }
 
     public static void main(String[] args) {
         CountDownLatch latch = new CountDownLatch(11 * 11 * 11);
         ExecutorService s = Executors.newFixedThreadPool(10);
         for (int i = 1; i < 11; i++) { // i, j ,k实际上应该是31,这里仅为了说明问题,当改成31时,CountDownLatch应该初始化为31 * 31 * 31
             for (int j = 1; j < 11; j++) {
                 for (int k = 1; k < 11; k++) {
                     final int ii = i;
                     final int jj = j;
                     final int kk = k;
                     s.execute(new Runnable() {
                         @Override
                         public void run() {
                             random(ii, jj, kk);
                             latch.countDown();
                         }
                     });
                 }
             }
         }
 
         s.shutdown();
         try {
             latch.await(300, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
 
         TreeMap<Long, StringBuilder> t = new TreeMap<Long, StringBuilder>(Collections.reverseOrder());
         t.putAll(map);
 
         for (Map.Entry<Long, StringBuilder> entry : t.entrySet()) {
             System.out.println("[" + entry.getKey() + "]" + entry.getValue().toString());
         }
     }
 }

Output, per-cycle reverse order, i.e. the maximum front 

[4294967295] (1, 3, 10) (2, 7, 7) (2, 7, 9) (5, 9, 7) (7, 1, 9) (7, 7, 2) (7, 9, 5)
 [4160749537] (1, 7, 9) (4, 1, 9) (6, 5, 9)
 [3900702255] (1, 3, 4) (5, 5, 7) (7, 5, 5)
 [3758096377] (1, 9, 2) (2, 9, 1) (7, 7, 9)
 [2147483647] (1, 5, 5) (1, 9, 6) (2, 5, 5) (2, 5, 7) (5, 5, 1) (5, 5, 2) (6, 5, 7) (6, 9, 1) (7, 5, 2) (7, 5, 6)
 [2147483644] (1, 9, 10)
 [2147213313] (2, 5, 3) (3, 5, 2)
 [2147188740] (4, 5, 5) (4, 9, 1) (5, 5, 4)
 [2145385473] (7, 9, 9)
 [2145382404] (1, 5, 9)
 [2143288833] (5, 1, 6) (6, 1, 5)
 [2139094020] (1, 7, 6)
 [2113929153] (1, 5, 4) (4, 5, 1)
 [2080374753] (2, 3, 3) (3, 3, 2)
 [1997533470] (2, 9, 9)
 [1879048185] (2, 5, 9) (4, 7, 9)
 [1747831785] (8, 9, 5)
 [1610612733] (7, 3, 10)
 [1560280902] (3, 5, 5) (5, 5, 3)
 [1431655765] (1, 7, 7) (2, 9, 5) (5, 1, 8) (5, 9, 2) (7, 7, 1) (8, 1, 5)
 [1431562923] (1, 1, 2) (2, 1, 1)
 [1430257323] (3, 9, 7) (7, 9, 3)
 [1409286123] (5, 3, 7) (7, 3, 5) (9, 1, 10)
 [1339553285] (1, 9, 5) (5, 9, 1)
 [1242911789] (3, 7, 10) (5, 3, 10)
 [1174405085] (1, 3, 5) (5, 3, 1) (9, 3, 4)
 [1073741823] (3, 1, 6) (6, 1, 3)
 [1073594370] (1, 9, 4)
 [1064182911] (4, 3, 7) (7, 3, 4)
 [1006632930] (3, 1, 10)
 [714429611] (3, 1, 4) (4, 1, 3)
...

It can be seen in the first row happens (1,3,10) 4294967295 period, exactly 232--1

A row of a plurality of groups represented by equal periods.

Question, why have two left and one to the right of it? In fact, only once or left heterotrophic can achieve random results.

Guess the reason for this, probably because, for the first time left, is to allow more than a high, right, is to allow more than one low, so that high-low are involved, increase the randomness, the second left , it is the truly random.

Examples exchanger 

Two threads, two buffers, a thread to which a data buffer is filled, another thread to fetch data from the other buffer inside. When the thread will fill data buffer is filled, or take a thread of data will take the data buffer is empty, it initiates action to the other exchange buffer, and the timing of the exchange is that a buffer is full, the other a buffer is empty. The following code is very simple, there is no footnote.

public class FillAndEmpty {
     Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();
     DataBuffer initialEmptyBuffer = DataBuffer.allocate(1024);
     DataBuffer initialFullBuffer = DataBuffer.allocate(1024);
 
     class FillingLoop implements Runnable {
         public void run() {
             DataBuffer currentBuffer = initialEmptyBuffer;
             try {
                 while (currentBuffer != null) {
                     addToBuffer(currentBuffer);
                     if (currentBuffer.isFull()) {
                         System.out.println("[FillingLoop](Before)" + currentBuffer);
                         currentBuffer = exchanger.exchange(currentBuffer);
                         System.out.println("[FillingLoop](After)" + currentBuffer);
                     }
                 }
             } catch (InterruptedException ex) {
                 Thread.currentThread().interrupt();
             }
         }
     }
 
     class EmptyingLoop implements Runnable {
         public void run() {
             DataBuffer currentBuffer = initialFullBuffer;
             try {
                 while (currentBuffer != null) {
                     takeFromBuffer(currentBuffer);
                     if (currentBuffer.isEmpty()) {
                         System.out.println("[EmptyingLoop](Before)" + currentBuffer);
                         currentBuffer = exchanger.exchange(currentBuffer);
                         System.out.println("[EmptyingLoop](After)" + currentBuffer);
                     }
                 }
             } catch (InterruptedException ex) {
                 Thread.currentThread().interrupt();
             }
         }
     }
 
     void start() {
         Thread fillingLoopThread = new Thread(new FillingLoop());
         Thread emptyingLoopThread = new Thread(new EmptyingLoop());
 
         fillingLoopThread.start();
         emptyingLoopThread.start();
 
         try {
             Thread.sleep(10);
         } catch (InterruptedException e) {
             // do nothing
         }
         fillingLoopThread.interrupt();
         emptyingLoopThread.interrupt();
     }
 
     public void takeFromBuffer(DataBuffer buf) {
         buf.take();
     }
 
     public void addToBuffer(DataBuffer buf) {
         buf.add(1);
     }
 
     private static class DataBuffer {
         private final int[] buf;
         private final int size;
         private int index;
 
         private DataBuffer(int size) {
             this.size = size;
             this.buf = new int[size];
         }
 
         public static DataBuffer allocate(int size) {
             return new DataBuffer(size);
         }
 
         public boolean isEmpty() {
             return index == 0;
         }
 
         public boolean isFull() {
             return index == size - 1;
         }
 
         public int take() {
             if (index > 0) {
                 return buf[index--];
             }
 
             return -1;
         }
 
         public void add(int data) {
             if (index < size - 1) {
                 buf[index++] = data;
             }
         }
     }
 
     public static void main(String[] args) {
         FillAndEmpty fae = new FillAndEmpty();
         fae.start();
     }
 }

Output follows before and after the exchange, the data buffer holds two threads reversed. 

 [EmptyingLoop](Before)com.luoluo.exchanger.FillAndEmpty$DataBuffer@1733c6a5
 [FillingLoop](Before)com.luoluo.exchanger.FillAndEmpty$DataBuffer@39bcfec1
 [FillingLoop](After)com.luoluo.exchanger.FillAndEmpty$DataBuffer@1733c6a5
 [EmptyingLoop](After)com.luoluo.exchanger.FillAndEmpty$DataBuffer@39bcfec1
 ......

 

Published 136 original articles · won praise 6 · views 1532

Guess you like

Origin blog.csdn.net/weixin_42073629/article/details/104470510