JUC源码分析30-线程池-Exchanger
来源:互联网 发布:蘑菇街算法工程师 编辑:程序博客网 时间:2024/04/30 21:24
- Demo
- 算法原理
- 结构
- 调用方法
- exchange
- doExchange
- hashIndex
- createSlot
- await
- tryCancel
- spinWait
- 总结
本想JUC最后一节写下Executors的,然后结束JUC。看了下代码,完全是一个工具类,哎,都是ThreadPoolExecutor、ScheduledThreadPoolExecutor还有callable的封装,代码看起来也没什么难度,不能浪费时间,还是看下Exchanger吧。
Demo
Exchanger还真没用过,写个demo试验下看看。
public class Hello { public static void main(String[] args) throws InterruptedException, ClassNotFoundException, InstantiationException, IllegalAccessException { final Exchanger<String> presents = new Exchanger<String>(); Thread boy = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+ "送美女一朵:鲜花"); try { String gift = presents.exchange("flower"); System.out.println(Thread.currentThread() + "获得美女赠送的:" + gift); } catch (InterruptedException e) { } } }); Thread girl = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread()+ "送哥们一个kiss"); try { String gift = presents.exchange("kiss"); System.out.println(Thread.currentThread() + "获得帅哥赠送的:" + gift); } catch (InterruptedException e) { } } }); boy.start(); girl.start(); Thread.yield(); }}
运行结果为:
Thread[Thread-0,5,main]送美女一朵:鲜花Thread[Thread-1,5,main]送哥们一个kissThread[Thread-0,5,main]获得美女赠送的:kissThread[Thread-1,5,main]获得帅哥赠送的:flower
可以看到实现了2个线程间的数据交换。
与之前看过的SynchronousQueue不同的是,Exchanger实现了不同线程间的相互交换,而SynchronousQueue是一个线程传递数据给另一个线程,一个是交换,一个是传递。
算法原理
javadoc里面有一段关于Exchanger的实现的介绍,原文为:
The basic idea is to maintain a "slot", which is a reference toa Node containing both an Item to offer and a "hole" waiting toget filled in. If an incoming "occupying" thread sees that theslot is null, it CAS'es (compareAndSets) a Node there and waitsfor another to invoke exchange. That second "fulfilling" threadsees that the slot is non-null, and so CASes it back to null,also exchanging items by CASing the hole, plus waking up theoccupying thread if it is blocked. In each case CAS'es mayfail because a slot at first appears non-null but is null uponCAS, or vice-versa. So threads may need to retry theseactions.
看懂应该没什么问题,举个栗子说明下,2个部落人(精灵和矮人吧)去集市交易,精灵到集市找一圈发现矮人没来,就想地皮那么贵先圈块地插个牌占坑吧,然后精灵扔下货该睡觉睡觉,该干嘛干嘛去了,矮人来集市一看我靠,你来这么早啊,赶紧把货交换了,通知下精灵,东西我换了,我先走了,赶紧拿货回家吧,拜了拜个了您。
结构
/** cup个数,控制slot数量和自旋 */private static final int NCPU = Runtime.getRuntime().availableProcessors();/** slot数量 */private static final int CAPACITY = 32;/** * slot最大用到多少 */private static final int FULL = Math.max(0, Math.min(CAPACITY, NCPU / 2) - 1);/** spin次数 */private static final int SPINS = (NCPU == 1) ? 0 : 2000;/** 超时exchange在park前的自旋次数 */private static final int TIMED_SPINS = SPINS / 20;/** tryCancel时更新,表示取消 */private static final Object CANCEL = new Object();/** 代表null入参或exchange返回null */private static final Object NULL_ITEM = new Object();/** 要交换数据的节点 */private static final class Node extends AtomicReference<Object> { /** 创建该Node的线程要交换的数据 */ public final Object item; /** 绑定的线程 */ public volatile Thread waiter; /** * Creates node with given item and empty hole. * @param item the item */ public Node(Object item) { this.item = item; }}/** * slot(坑位)就是数据交换的地方,用了缓存行填充,避免伪共享 */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 数组 */private volatile Slot[] arena = new Slot[CAPACITY];/** * slot数组最大有几个可用 */private final AtomicInteger max = new AtomicInteger();/** 空构造 */public Exchanger() {}
调用方法
exchange
public V exchange(V x) throws InterruptedException { if (!Thread.interrupted()) { Object v = doExchange((x == null) ? NULL_ITEM : x, false, 0); if (v == NULL_ITEM) return null; if (v != CANCEL) return (V)v; Thread.interrupted(); // Clear interrupt status on IE throw } throw new InterruptedException();}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();}
一个带超时响应一个不带,最后都是调用doExchange()
方法。返回值3种情况:
1. 交换到null;
2. 正常值;
3. 被中断,抛异常。
doExchange
/** 实现交换的方法,timed和nanos控制超时 */private Object doExchange(Object item, boolean timed, long nanos) { Node me = new Node(item); // 创建node int index = hashIndex(); // hasIndex近似对max的mode操作,获取slot的index int fails = 0; // cas失败次数 for (;;) { Object y; // Contents of current slot Slot slot = arena[index]; //arena并没有初始化,所以slot用到的时候需要create if (slot == null) // 如果slot为null,那就创建一个 createSlot(index); // Continue loop to reread else if ((y = slot.get()) != null && // 如果某个slot不会null,那就说明已经有其他线程占用在等待交换 slot.compareAndSet(y, null)) { //先cas设置为null,防止其他线程 Node you = (Node)y; // Transfer item if (you.compareAndSet(null, item)) { LockSupport.unpark(you.waiter); //交换自己的值,然后唤醒节点等待的线程 return you.item; //返回交换后的值 } // Else cancelled; continue } else if (y == null && // 如果slot还没有被占用,那就占用 slot.compareAndSet(null, me)) { if (index == 0) // 在0 slot上阻塞 return timed ? awaitNanos(me, slot, nanos) : await(me, slot); Object v = spinWait(me, slot); // Spin wait for non-0 if (v != CANCEL) return v; me = new Node(item); // 丢弃之前的cancel节点,新建一个 int m = max.get(); if (m > (index >>>= 1)) // 有可能是max太大了,减少下 max.compareAndSet(m, m - 1); } else if (++fails > 1) { // 如果在一个index slot上2次都失败 int m = max.get(); if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1)) //失败3次,那就扩大max,index换个位置试试 index = m + 1; // Grow on 3rd failed slot else if (--index < 0) index = m; // Circularly traverse } }}
hashIndex
private final int hashIndex() { long id = Thread.currentThread().getId(); int hash = (((int)(id ^ (id >>> 32))) ^ 0x811c9dc5) * 0x01000193; int m = max.get(); int nbits = (((0xfffffc00 >> m) & 4) | // Compute ceil(log2(m+1)) ((0x000001f8 >>> m) & 2) | // The constants hold ((0xffff00f2 >>> m) & 1)); // a lookup table int index; while ((index = hash & ((1 << nbits) - 1)) > m) // May retry on hash = (hash >>> nbits) | (hash << (33 - nbits)); // non-power-2 m return index;}
这个方法用了FNV-1a算法,说实话没看懂,但是近似看成是对max的mod操作,返回要查找的slot数组下标。
createSlot
/** 创建一个slot */private void createSlot(int index) { // Create slot outside of lock to narrow sync region Slot newSlot = new Slot(); Slot[] a = arena; synchronized (a) { if (a[index] == null) a[index] = newSlot; }}
await
/** slot 0上的阻塞 */private static Object await(Node node, Slot slot) { Thread w = Thread.currentThread(); int spins = SPINS; //跟awaitNanos不一样 for (;;) { Object v = node.get(); if (v != null) //这里判断node.get,如果有其他线程交换会cas这个值 return v; else if (spins > 0) // 先自旋 --spins; else if (node.waiter == null) // 自旋次数够了后,如果节点的等待线程null,设置当前 node.waiter = w; else if (w.isInterrupted()) // 如果线程被中断,cancel节点 tryCancel(node, slot); else // park等待唤醒 LockSupport.park(node); }}/** 响应超时的await */private Object awaitNanos(Node node, Slot slot, long nanos) { int spins = TIMED_SPINS; //跟await不一样 long lastTime = 0; Thread w = null; for (;;) { Object v = node.get(); if (v != null) 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; else if (node.waiter == null) node.waiter = w; else if (w.isInterrupted()) tryCancel(node, slot); else LockSupport.parkNanos(node, nanos); } else if (tryCancel(node, slot) && !w.isInterrupted()) //超时后cancel节点,如果线程没有被中断,扫描其他位置slot,看看有没有可交换的,有就交换 return scanOnTimeout(node); }}
带超时的await,在超时后如果线程没有被中断,会扫描其他位置的slot,看看有没有可交换的节点。
tryCancel
/** cancel指定slot位置的节点,清空slot、设置节点的值cancel */private static boolean tryCancel(Node node, Slot slot) { if (!node.compareAndSet(null, CANCEL)) return false; if (slot.get() == node) // 再次校验node是否变化,估计是怕节点被交换处理掉了 slot.compareAndSet(node, null); return true;}
spinWait
/** 在非0slot自旋 */private static Object spinWait(Node node, Slot slot) { int spins = SPINS; //这里的spins跟awaitNanos里面的超时自旋不一样 for (;;) { Object v = node.get(); if (v != null) return v; else if (spins > 0) --spins; else tryCancel(node, slot); //自旋次数达到还没有交换,那就cancel }}
总结
Exchanger这个类,估计是我孤陋寡闻了,没看见实际运用的案例,不过还好,不是很难,理解原理看看代码就行。
写完这个类,就结束JUC系列吧,其实还有个forkjoin没写,后面看看吧。当初面试阿里,窃以为谈的不错,没想到最后挂了,哎,郁闷了好久,所以写了这个系列,磕磕绊绊的,没想到能写这么多,感谢网上那些参考的文章,有些漏了没写,过段时间再试试阿里吧。
- JUC源码分析30-线程池-Exchanger
- 源码分析之JUC-Exchanger
- Java多线程 -- JUC包源码分析16 -- Exchanger源码分析
- JUC源码分析26-线程池-ThreadPoolExecutor
- JUC源码分析27-线程池-FutureTask
- JUC源码分析28-线程池-ExecutorCompletionService
- JUC源码分析29-线程池-ScheduledThreadPoolExecutor
- 《Java源码分析》:Exchanger
- 《Java源码分析》:Exchanger
- JUC - Semaphore 源码分析
- JUC - CountDownLatch 源码分析
- JUC - ReentrantLock 源码分析
- JUC - Condition 源码分析
- JUC - ReentrantReadWriteLock 源码分析
- JUC - ThreadPoolExecutor 源码分析
- JUC - FutureTask 源码分析
- Java 并发 --- Exchanger源码分析
- JUC - AbstractQueuedSynchronizer(AQS) 源码分析
- HDU 4704 (Sum)
- break与continue
- (wait()或者notify()必须采用当前锁调用 ) && (类.方法VS对象.方法)
- OPENCV2.4.9源码分析
- android中传递复杂参数,activity之间和fragment之间的bundle传递集合/对象
- JUC源码分析30-线程池-Exchanger
- 实验三.任务一.LinearLayout
- ubuntu 14.04 下安装theano
- webkit内核浏览器滚动条设置
- HDU1520(树形dp入门题)
- C#大数加减乘除取模
- LeetCode143—Reorder List
- STM32F0非对齐访问引起的硬件错误及其排除
- lightoj 1019 模板dijkstra