大飞老师带你看线程(并发容器-SynchronousQueue)下

本文作者:王一飞,叩丁狼高级讲师。原创文章,转载请注明出处。

接上一篇, 本篇讲SynchronousQueue队列非公平策略put与take操作

源码分析

2:非公平锁策略- put / take

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        if (transferer.transfer(e, false, 0) == null) {
            Thread.interrupted();
            throw new InterruptedException();
        }
    }
    public E take() throws InterruptedException {
        E e = transferer.transfer(null, false, 0);
        if (e != null)
            return e;
        Thread.interrupted();
        throw new InterruptedException();
    }

put 跟 take 方法有都调用:
调用transfer 方法:
put : transferer.transfer(e, false, 0)
take: transferer.transfer(null, false, 0);
从前面内部结构看: SynchronousQueue 非公平策略底层实际上委托给TransferStack 栈实现, 而内部存储数据使用SNode 栈节点 来看下节点源码:

static final class SNode {
    volatile SNode next;        //下一个节点    
    volatile SNode match;       //相匹配的节点
    volatile Thread waiter;     //线程挂起与唤醒控制: park /unpark      
    Object item;                //节点内容    
    //模式控制变量
    //非公平策略模式有3种:
    //REQUEST:表示消费数据-take操作
    //DATA:表示生产数据-put操作
    //FULFILLING:介于上2个状态间
        //多一个状态原因:TransferStack 配对操作原理是:每个线程配对时,都会先加入到栈顶中, 不管是take还是put,如果此时发现入栈的线程跟栈内线程可以配对,那么此时线程可以使用FULFILLING状态类标记:将要执行配对逻辑线程, 提示其他配对线程,配对时跳过这个节点,匹配其他的线程。
    int mode;

    //cas原子操作, 设置next节点
    boolean casNext(SNode cmp, SNode val) {...}
    //节点匹配,匹配成功,则unpark等待线程
    boolean tryMatch(SNode s) {...}
    //取消节点, 将节点内容设置为自己
    void tryCancel() {...}
    //判断是否操作结束
    boolean isCancelled() {...}
}

此处研究的是公平锁策略, 所以, 此时的transfer变量执项的是: TransferStack 类的实例

E transfer(E e, boolean timed, long nanos) {
    SNode s = null; 
    //根据transfer方法参数e判断当前操作模式
    //有值DATA 反之为REQUEST 
    int mode = (e == null) ? REQUEST : DATA;
    for (;;) {
        SNode h = head;  //初始时head为null
        //第一次put或take h==null成立, 如果已经存在挂起线程,入栈, 不管take或put mode必须一致才可以进入,此分支是入栈分支,不会进行配对
        if (h == null || h.mode == mode) {  
            if (timed && nanos <= 0) {  //超时操作   
                //队列中有配对线程,但是配对线程被取消了
                if (h != null && h.isCancelled()) 
                    casHead(h, h.next);   //下移动栈顶节点,出栈
                else
                    return null;
             //满足并进入此分支, 创建新节点,当前线程入栈成为栈顶节点
            } else if (casHead(h, s = snode(s, e, h, mode))) {
             //一旦栈顶位置移动成功, 挂起当前线程,等待配对线程
                SNode m = awaitFulfill(s, timed, nanos);
                if (m == s) {             
                    clean(s);  //一旦配对成功, 对应的线程出栈(被清除)
                    return null;
                }
                if ((h = head) != null && h.next == s)
                    casHead(h, s.next);     
                return (E) ((mode == REQUEST) ? m.item : s.item); //返回结果
            }
        //出现mode不同操作时,那么就需要配对了,此时需要满足单前栈顶节点不是正在配对节点, 所以做了排除其他也企图对栈顶节点配对的竞争线程操作
        } else if (!isFulfilling(h.mode)) { 
            if (h.isCancelled())  //检查,如果栈顶节点配其他线程配对成功,栈顶节点下移
                casHead(h, h.next);   
            //如果没有竞争线程, 线程入栈成为栈顶节点, 同时加正在配对状态
            else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {
                for (;;) {
                    //获取目标配对的节点
                    SNode m = s.next;    
                    //如果目标配对节点为null,  则表示目标配对节点被其他线程抢走了,
                    if (m == null) {      
                        casHead(s, null);   //从新移动栈顶节点为null
                        s = null;     //配对失败, 跳出循环, 从新循环判断     
                        break;              
                    }
                    //如果不为null, 获取目标配对节点下一个节点
                    SNode mn = m.next;
                    if (m.tryMatch(s)) { //尝试配对
                        casHead(s, mn);  //配对成功,移动栈顶节点到mn, 同时配对的2个节点同时出栈, 返回结果  
                        return (E) ((mode == REQUEST) ? m.item : s.item);
                    } else                
                        //如果配对失败,寻找原目标节点下一个节点,重新循环配对
                        s.casNext(m, mn);  
                }
            }
        } else {  //进入这个分支,表示目标配对的节点是一个正在配对的节点
            //直接跳过,配对下一个节点 , 接下逻辑跟上面配对一样                     
            SNode m = h.next;            
            if (m == null)               
                casHead(h, null);         
            else {
                SNode mn = m.next;
                if (m.tryMatch(h))         
                    casHead(h, mn);        
                else                        
                    h.casNext(m, mn);       
            }
        }
    }
}

非公平策略操作流程图:


添加元素

获取元素

这里需要说明, 如果元素过多,并发时,配对的线程具体要配对谁,那么就随机啦,无法按照图中理想的配对顺序。这也是为什么说是非公平策略啦。

总结:
结合代码: transferer执行流程可归纳为以下:
1: transferer调用transfer方法实现SynchronousQueue 公平队列的take跟put操作
2:不管是take或者put 线程只要跟栈顶节点模式一样,都要入栈,然后通过自旋方式寻找匹配,找不到配对挂起。如果时间消耗完毕,或者被取消,直接出列。
3:如果找到配对的节点,将当前线程打上FULFILLING标记,尝试配对。如果是此时有竞争配对线程,检查到该节点有FULFILLING标记,直接跳过,寻找下一个配对节点。
4:如果配对成功, 弹出当前配对成功2个节点。如果配对失败,从头循环。

想获取更多技术干货,请前往叩丁狼官网:http://www.wolfcode.cn/all_article.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容