LinkedTransferQueue

LinkedTransferQueue 出身 Google ,现在已经被集成在 JDK7 中,但目前主流的 JDK 平台仍然是 JDK6 ,所以很多时候我们需要这样引入:

import com.google.code.yanf4j.util ;

她的作者依然是 Doug Lea 大神,他说:

LinkedTransferQueue 是一个聪明的队列,他是 ConcurrentLinkedQueue,

SynchronousQueue

(in “fair” mode

公平模式   ), and unbounded LinkedBlockingQueue   的超集。

我们从她的声明中也可以看出这一点:

public class

LinkedTransferQueue<E> extends AbstractQueue<E> implements

BlockingQueue<E>

Google 的注释中,有这样一段描述:

This class extends the approach used in FIFO-mode

SynchronousQueues. See the internal documentation, as well as

the PPoPP 2006 paper “Scalable Synchronous Queues” by Scherer,

Lea & Scott

http://www.cs.rice.edu/~wns1/papers/2006-PPoPP-SQ.pdf )

The main extension is to provide different Wait modes for the

main “xfer”

method that puts or takes items.  These

don’t

impact the basic dual-queue logic, but instead control whether

or how threads block upon insertion of request or data nodes

into the dual queue. It also uses slightly different

conventions for tracking whether nodes are off-list or

cancelled.

LinkedTransferQueue 实现了一个重要的接口 TransferQueue, 该接口含有下面几个重要方法:

1. transfer(E e)

若当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素 e 到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。

2. tryTransfer(E e)

若当前存在一个正在等待获取的消费者线程(使用 take() 或者 poll() 函数),使用该方法会即刻转移 / 传输对象元素 e ;若不存在,则返回 false ,并且不进入队列。这是一个不阻塞的操作。

3. tryTransfer(E e, long timeout, TimeUnit unit)

若当前存在一个正在等待获取的消费者线程,会立即传输给它 ; 否则将插入元素 e 到队列尾部,并且等待被消费者线程获取消费掉 , 若在指定的时间内元素 e 无法被消费者线程获取,则返回 false ,同时该元素被移除。

4. hasWaitingConsumer()

判断是否存在消费者线程

5. getWaitingConsumerCount()

获取所有等待获取元素的消费线程数量

6. size()

因为队列的异步特性,检测当前队列的元素个数需要逐一迭代,可能会得到一个不太准确的结果,尤其是在遍历时有可能队列发生更改。

7. 批量操作

类似于

addAll,removeAll,

retainAll, containsAll, equals, toArray 等方法,API不能保证一定会立刻执行。

因此,我们在使用过程中,对这些不能有所期待,这是一个具有异步特性的队列。

其实 transfer 方法在 SynchronousQueue 的实现中就已存在了 , 只是没有做为 API 暴露出来。SynchronousQueue 有一个特性 : 它本身不存在容量 , 只能进行线程之间的元素传送。 SynchronousQueue 在执行 offer 操作时,如果没有其他线程执行 poll ,则直接返回 false. 线程之间元素传送正是通过 transfer 方法完成的。

有一个使用案例,我们知道 ThreadPoolExecutor 调节线程的原则是:先调整到最小线程,最小线程用完后,他会将优先将任务放入缓存队列 (offer(task)), 等缓冲队列用完了,才会向最大线程数调节。这似乎与我们所理解的线程池模型有点不同。我们一般采用增加到最大线程后,才会放入缓冲队列中,以达到最大性能。 ThreadPoolExecutor 代码段:

如果我们采用 SynchronousQueue 作为 ThreadPoolExecuto 的缓冲队列时,在没有线程执行 poll 时 ( 即存在等待线程 ) ,则 workQueue.offer(command) 返回 false, 这时 ThreadPoolExecutor就会增加线程,最快地达到最大线程数。但也仅此而已,也因为 SynchronousQueue 本身不存在容量 , 也决定了我们一般无法采用 SynchronousQueue 作为 ThreadPoolExecutor 的缓存队列。而一般采用 LinkedBlockingQueue 的 offer 方法来实现。最新的 LinkedTransferQueue 也许可以帮我们解决这个问题,后面再说。

transfer 算法比较复杂,实现很难看明白。大致的理解是采用所谓双重数据结构 (dual data structures) 。之所以叫双重,其原因是方法都是通过两个步骤完成:

保留与完成。比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列 , 所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为 NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。大体是这个意思,这种方式优美了完成了线程之间的高效协作。

对于

LinkedTransferQueue,Doug

Lea

进行了尽乎极致的优化。   Grizzly   的采用了   PaddedAtomicReference  

PaddedAtomicReference 相对于父类 AtomicReference 只做了一件事情,就将共享变量追加到64 字节。我们可以来计算下,一个对象的引用占 4 个字节,

它追加了 15 个变量共占 60 个字节,再加上父类的 Value 变量,一共 64 个字节。 上面追加15个4字节对象的代码看起来是不是很奇葩?其实不是的 , 这样做是为了在并发中提升效率。

为什么追加 64 字节能够提高并发编程的效率呢 ? 因为对于英特尔酷睿 i7 ,酷睿, Atom 和 NetBurst , Core Solo 和

Pentium

M

处理器的   L1     L2     L3   缓存的高速缓存行是   64   个字节宽,不支持部分填充缓存行,这意味着如果队列的头节点和尾节点都不足   64   字节的话,处理器会将它们都读到同一个高速缓存行中,在多处理器下每个处理器都会缓存同样的头尾节点,当一个处理器试图修改头接点时会将整个缓存行锁定,那么在缓存一致性机制的作用下,会导致其他处理器不能访问自己高速缓存中的尾节点,而队列的入队和出队操作是需要不停修改头接点和尾节点,所以在多处理器的情况下将会严重影响到队列的入队和出队效率。   Doug lea   使用追加到   64   字节的方式来填满高速缓冲区的缓存行,避免头接点和尾节点加载到同一个缓存行,使得头尾节点在修改时不会互相锁定。

那么是不是在使用 Volatile 变量时都应该追加到 64 字节呢?不是的。在两种场景下不应该使用这种方式。第一: 缓存行非 64 字节宽的处理器 ,如 P6 系列和奔腾处理器,它们的 L1 和 L2 高速缓存行是 32 个字节宽。第二: 共享变量不会被频繁的写 。因为使用追加字节的方式需要处理器读取更多的字节到高速缓冲区,这本身就会带来一定的性能消耗,共享变量如果不被频繁写的话,锁的几率也非常小,就没必要通过追加字节的方式来避免相互锁定。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
jeesuite-libs分布式架构开发套件。包括缓存(一二级缓存、自动缓存管理)、队列、分布式定时任务、文件服务(七牛、阿里云OSS、fastDFS)、日志、搜索、代码生成、API网关、配置中心、统一认证平台、分布式锁、分布式事务、集成dubbo、spring boot支持、统一监控等。所有release版都经过严格测试并在生产环境稳定运行4年+。 功能列表: cache模块 基于配置支持单机、哨兵、分片、集群模式自由切换 更加简单的操作API封装 一级缓存支持(ehcache & guava cache)、分布式场景多节点自动通知 多组缓存配置同时支持 (一个应用多个redis server) 分布式模式开关 kafka模块 基于spring封装简化配置和调用方式 基于配置新旧两版Consumer API兼容支持 支持二阶段处理,即:fetch线程同步处理和process线程异步处理 消费成功业务处理失败自动重试或自定义重试支持 process线程池采用LinkedTransferQueue,支持线程回收和队列大小限制,确保系统崩溃等不会有数据丢失。 支持特殊场景发送有状态的消息(如:同一个用户的消息全部由某一个消费节点处理) producer、consumer端监控数据采集,由(jeesuite-admin)输出 兼容遗留kafka系统、支持发送和接收无封装的消息 mybatis模块 代码生成、自动CRUD、可无缝对接mybaits增强框架Mapper 基于properties配置多数据源支持,无需修改XML 读写分离,事务内操作强制读主库 基于注解自动缓存管理(所有查询方法结果自动缓存、自动更新,事务回滚缓存同步回滚机制) 自动缓存实现基于jeesuite-cache和spring-data-redis 分页组件 敏感操作拦截 scheduler模块 支持分布式保证单节点执行(按节点平均分配job) 支持failvoer,自动切换故障节点 支持多节点下并行计算 支持无注册中心单机模式 支持自定义重试策略 支持配置持久化(启动加载、变更保存) 支持控制台(jeesuite-admin)任务监控、开停、动态修改调度时间策略、手动触发执行 jeesuite-security 配置简单(初始化一个类即可) 满足认证授权基本需求 更加贴近日常使用业务场景 可选本地session和共享session 可选是否支持多端同时登录 dubbo、springboot跨服务登录状态传递支持 rest模块 自动resonse封装(xml、json) i18n request、response日志记录 自动友好错误 校验框架 filesystem模块 七牛文件服务支持 阿里云OSS文件服务支持 fastDFS文件系统支持 支持spring集成 配置式切换服务提供商 common模块 一些常用工具类 common2模块(需要依赖一些组件或者基础设置) 分布式锁 分布式全局ID生成器 excel导入导出 jeesuite-springboot-starter模块 springboot集成支持

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值