Java多线程--并发中集合的使用之LinkedTransferQueue

LinkedTransferQueue

 

起源: 我觉得是这样的,之前的BlockingQueue是对 读取 或者 写入 锁定整个队列,所以在比较繁忙的时候,各种锁比较耗时

而当时有一个SynchronizedQueue其实不能叫Queue,因为只能放一个物件,要么有一个物件在等人拿,要么有一个空等人放

根据这个原理,诞生了LinkedTransferQueue,利用CompareAndSwap进行一个无阻塞的队列,针对每一个操作进行处理样大家就不用抢得那么辛苦了

 

数据结构

在类的内部保持着一个栈,基本单位是node,根据 hasData区分里面有两种元素,要么是 Data 要么是 Reservation,不会同时存在

并且有一个变量head指向最前面的node,没东西则是null

Node

{

  isData    是不是数据,是的话item放具体东西

  item   如果不是数据则为null

  next   下一个节点

  waiter  如果不是数据则是reservation,有一个线程在等待

}

 

过程:

整个存取过程分成两部分

 1:MATCH(原节点,新节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for  (;;) {                             // restart on append race
 
             for  (Node h = head, p = h; p !=  null ;) {  // 如果头结点为空则跳过,非空进去找第一个可用节点
                 boolean  isData = p.isData;
                 Object item = p.item;
                 if  (item != p && (item !=  null ) == isData) {  // 判断原节点可用性,如data的item应该是数值,如果是null则表明用过了
                     if  (isData == haveData)    // 两个节点是相同类型,不用match了,去下一步
                         break ;
                     if  (p.casItem(item, e)) {  // 节点不同类型,match成功,更改原节点item,表明不可用
                         for  (Node q = p; q != h;) { //什么,我居然不是head节点了?我要让它指向我!
                             Node n = q.next;   // update by 2 unless singleton
                             if  (head == h && casHead(h, n ==  null  ? q : n)) {
                                 h.forgetNext();
                                 break ;
                             }                  // advance and retry
                             if  ((h = head)   ==  null  ||
                                 (q = h.next) ==  null  || !q.isMatched())
                                 break ;         // unless slack < 2
                         }
                         LockSupport.unpark(p.waiter); //根据原节点的类型,reservation则叫人收货,data则叫null收货
                         return  LinkedTransferQueue.<E>cast(item); //根据原节点的类型,reservation则返回null,data则返回数据
                     }
                 }
                 Node n = p.next; //下一个节点
                 p = (p != n) ? n : (h = head);  // Use head if p offlist
             }

  重点是找出第一个可用节点,如果是null则跳过,如果与进来的节点相同(本来就有data,还放data)也跳过,如果不同(本来是data,现在是reservation,返回data值 / 本来是reservation,现在是data,叫人来收货,返回reservation值=空)

 2:处理节点

1
2
3
4
5
6
7
8
9
10
if  (how != NOW) {                  // No matches available
                 if  (s ==  null )
                     s =  new  Node(e, haveData);
                 Node pred = tryAppend(s, haveData); //尝试添加新node
                 if  (pred ==  null )
                     continue  retry;            // 不成功则重试整个过程
                 if  (how != ASYNC)
                     return  awaitMatch(s, pred, e, (how == TIMED), nanos); //根据参数,等不等别人放数据,拿数据,等多久
             }
             return  e;  // not waiting

  MATCH失败了才会进入这个环节,把新节点放进栈内,并根据参数决定立刻返回或者等待返回

EXAMPLES

1:Head->Data    Input->Data

Match:      根据他们的属性 发现 cannot match ,因为是同类的

处理节点:   所以把新的data放在原来的data后面,然后head往后移一位,Reservation同理

HEAD=DATA->DATA

 

2:Head->Data    Input->Reservation  (取数据)

Match:      成功match,就把Data的item变为reservation的值(null,有主了),并且返回数据。

处理节点:  没动,head还在原地

HEAD=DATA(用过)

 

3:Head->Reservation  Input->Data(放数据)

Match:       成功match,就把Reservation的item变为Data的值(有主了),并且叫waiter来取

处理节点:  没动

HEAD=RESERVATION(用过)

 

总结:LinkedTransferQueue通过CAS尝试放入data或增加reservation。

其消耗小于把整个队列锁掉,但是在并发特别高的情况下大家抢着尝试一样会影响速度

至于为什么跨过了1.6到1.7这个类才出现我觉得有点神奇

 

简单用法介绍------------------------------------------------------------------------------------------------------

存:

put();   放元素进去队列,注意队列是可以无限长的

add();   同上

transfer();  这个是重点,如果队列中有人发现有人在等,则直接给那个人(有一个参数waiter指定了在等的线程)

如果没人在等,就放进队列

取:

poll();  立即返回,如果没有元素就是空

take(); 如果没有元素,那就等

PS:最好是用poll然后自己处理空的状况,如果全是take然后又迟迟没有东西,那就一堆内存在等了。

 

起源: 我觉得是这样的,之前的BlockingQueue是对 读取 或者 写入 锁定整个队列,所以在比较繁忙的时候,各种锁比较耗时

而当时有一个SynchronizedQueue其实不能叫Queue,因为只能放一个物件,要么有一个物件在等人拿,要么有一个空等人放

根据这个原理,诞生了LinkedTransferQueue,利用CompareAndSwap进行一个无阻塞的队列,针对每一个操作进行处理样大家就不用抢得那么辛苦了

 

数据结构

在类的内部保持着一个栈,基本单位是node,根据 hasData区分里面有两种元素,要么是 Data 要么是 Reservation,不会同时存在

并且有一个变量head指向最前面的node,没东西则是null

Node

{

  isData    是不是数据,是的话item放具体东西

  item   如果不是数据则为null

  next   下一个节点

  waiter  如果不是数据则是reservation,有一个线程在等待

}

 

过程:

整个存取过程分成两部分

 1:MATCH(原节点,新节点)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for  (;;) {                             // restart on append race
 
             for  (Node h = head, p = h; p !=  null ;) {  // 如果头结点为空则跳过,非空进去找第一个可用节点
                 boolean  isData = p.isData;
                 Object item = p.item;
                 if  (item != p && (item !=  null ) == isData) {  // 判断原节点可用性,如data的item应该是数值,如果是null则表明用过了
                     if  (isData == haveData)    // 两个节点是相同类型,不用match了,去下一步
                         break ;
                     if  (p.casItem(item, e)) {  // 节点不同类型,match成功,更改原节点item,表明不可用
                         for  (Node q = p; q != h;) { //什么,我居然不是head节点了?我要让它指向我!
                             Node n = q.next;   // update by 2 unless singleton
                             if  (head == h && casHead(h, n ==  null  ? q : n)) {
                                 h.forgetNext();
                                 break ;
                             }                  // advance and retry
                             if  ((h = head)   ==  null  ||
                                 (q = h.next) ==  null  || !q.isMatched())
                                 break ;         // unless slack < 2
                         }
                         LockSupport.unpark(p.waiter); //根据原节点的类型,reservation则叫人收货,data则叫null收货
                         return  LinkedTransferQueue.<E>cast(item); //根据原节点的类型,reservation则返回null,data则返回数据
                     }
                 }
                 Node n = p.next; //下一个节点
                 p = (p != n) ? n : (h = head);  // Use head if p offlist
             }

  重点是找出第一个可用节点,如果是null则跳过,如果与进来的节点相同(本来就有data,还放data)也跳过,如果不同(本来是data,现在是reservation,返回data值 / 本来是reservation,现在是data,叫人来收货,返回reservation值=空)

 2:处理节点

1
2
3
4
5
6
7
8
9
10
if  (how != NOW) {                  // No matches available
                 if  (s ==  null )
                     s =  new  Node(e, haveData);
                 Node pred = tryAppend(s, haveData); //尝试添加新node
                 if  (pred ==  null )
                     continue  retry;            // 不成功则重试整个过程
                 if  (how != ASYNC)
                     return  awaitMatch(s, pred, e, (how == TIMED), nanos); //根据参数,等不等别人放数据,拿数据,等多久
             }
             return  e;  // not waiting

  MATCH失败了才会进入这个环节,把新节点放进栈内,并根据参数决定立刻返回或者等待返回

EXAMPLES

1:Head->Data    Input->Data

Match:      根据他们的属性 发现 cannot match ,因为是同类的

处理节点:   所以把新的data放在原来的data后面,然后head往后移一位,Reservation同理

HEAD=DATA->DATA

 

2:Head->Data    Input->Reservation  (取数据)

Match:      成功match,就把Data的item变为reservation的值(null,有主了),并且返回数据。

处理节点:  没动,head还在原地

HEAD=DATA(用过)

 

3:Head->Reservation  Input->Data(放数据)

Match:       成功match,就把Reservation的item变为Data的值(有主了),并且叫waiter来取

处理节点:  没动

HEAD=RESERVATION(用过)

 

总结:LinkedTransferQueue通过CAS尝试放入data或增加reservation。

其消耗小于把整个队列锁掉,但是在并发特别高的情况下大家抢着尝试一样会影响速度

至于为什么跨过了1.6到1.7这个类才出现我觉得有点神奇

 

简单用法介绍------------------------------------------------------------------------------------------------------

存:

put();   放元素进去队列,注意队列是可以无限长的

add();   同上

transfer();  这个是重点,如果队列中有人发现有人在等,则直接给那个人(有一个参数waiter指定了在等的线程)

如果没人在等,就放进队列

取:

poll();  立即返回,如果没有元素就是空

take(); 如果没有元素,那就等

PS:最好是用poll然后自己处理空的状况,如果全是take然后又迟迟没有东西,那就一堆内存在等了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值