我们到底能走多远系列(42)

扯淡:

  乘着有空,读些juc的源码学习下。后续把juc大致走一边,反正以后肯定要再来。

主题:

BlockingQueue 是什么
A java.util.Queue that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element. 
一个能阻塞的队列在两个操作队列时的阻塞:
  1,获取队列中元素时,队列为空,则阻塞,直到队列中有元素。
  2,存放一个元素时,队列已满,则阻塞,直到队列中有空位置可以存放。
BlockingQueue 作为接口规定了实现的规矩。
下面是队列核心的存取操作方法的4个种类:
  Throws exception Special value Blocks Times out
Insert add(e) offer(e) put(e) offer(e, time, unit)
Remove remove() poll() take() poll(time, unit)
Examine element() peek() not applicable not applicable
 
根据上面表,在队列满或空时的策略分别包含了,抛出异常,返回boolean值,阻塞线程,阻塞到超时。
为什么要这么选择,就不清楚了。我们需要注意的是除了第三种,其他方法都没有真正阻塞线程。
 
ArrayBlockingQueue:
内部用数组实现的一个queue,按照元素先进先出(FIFO)原则。初始化后,队列容量不可改变。
支持可选的公平机制,来保证阻塞的操作线程能按照顺序排列等待。默认是不公平机制。
 
源码实现:
1,使用Object[]的一个数组来存储元素
// 队列存放元素的容器
final Object[] items; // 下一次读取或移除的位置
int takeIndex; // 存放下一个放入元素的位置
int putIndex; // 队列里有效元素的数量
int count; // 所有访问的保护锁
final ReentrantLock lock; // 等待获取的条件
private final Condition notEmpty; // 等待放入的条件
private final Condition notFull;

2,整个队列是有一个环绕机制的,比如这时候我一直取数据,那么读取的下标会一直后移,知道数组的末尾。如果这时候制定数组的尾部后一个下标时数组的头位。如此即实现环绕的一个队列。如此实现十分精妙,可说是整个队列实现的基础机制。

如此,这个队列的容量是不可改变的。

// 指针前移
final int inc(int i) {
return (++i == items.length) ? 0 : i;
} // 指针后移
final int dec(int i) {
return ((i == 0) ? items.length : i) - 1;
}

3,直接看下核心的put和take方法实现:

put

    public void put(E e) throws InterruptedException {
checkNotNull(e);//不能放null
final ReentrantLock lock = this.lock;//先把锁赋给final修饰的局部变量
// 在JUC的很多类里,都会看到这种写法:把类的属性赋值给方法内的用final修饰一个变量。
// 这是因为类的属性是存放在堆里的,方法内的变量是存放在方法栈上的,访问方法栈比访问堆要快。
// 在这里,this.lock属性要访问两次,通过赋值给方法的局部变量,就节省了一次堆的访问。
// 其他的类属性只访问一次就不需要这样处理了。
lock.lockInterruptibly();//加锁
try {
//循环保证避免避免虚假唤醒,虚假唤醒就是此事如果有多个线程都wait,
       //而被同时唤醒时都会去执行下面的insert
//如果在while循环中,那么唤醒后先判断count大小,来确定是继续wait还是insert。
while (count == items.length)
notFull.await();//阻塞线程
insert(e);
} finally {
lock.unlock();//释放锁
}
}

take

    public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}

其中使用到insert和extract方法,当然也可以看到只有持有锁的情况下才会调用这两个方法,如此这个方法的调用不需要关系是否线程安全,调用前保证线程安全:

    private void insert(E x) {
items[putIndex] = x;// 1,存值,非常简便
putIndex = inc(putIndex);//2,移动下标,使用inc方法
++count;//3,增加元素总数
notEmpty.signal();//4,通知在非空条件上等待的读线程
}
    private E extract() {
final Object[] items = this.items;//先将类变量赋给方法变量,前面提过这个用处
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
takeIndex = inc(takeIndex);
--count;
notFull.signal();
return x;
}

操作示意图:

1,一个环的数组

2,再放一个元素:

3,取一个元素

当然ArrayBlockingQueue里还有其他方法,这里就不赘述了。有兴趣的同学可以深入继续探索。

总结:

 1,一个环的数组设计十分巧妙。

 2,将类变量赋给方法变量的编码方式

 

---------------------------------------20161202补充---------------------------------------------

ArrayBlockingQueue利用下面三个元素控制队列:

/** Main lock guarding all access */
final ReentrantLock lock; /** Condition for waiting takes */
private final Condition notEmpty; /** Condition for waiting puts */
private final Condition notFull;

ReentrantLock:可重入锁,在操作队列时,用来同步化。

Condition notEmpty & Condition notFull 是ReentrantLock中的。
 Lock 框架包含了对 wait 和 notify 的概括,这个概括叫作 条件(Condition)
    public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}

比如下面的take方法,就是用Condition来实现blocking的。

public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}

而当有元素放入BlockingQueue时,用notEmpty.signal()方法通知阻塞在这个条件上的线程可以抢机会进入执行了

 private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}

以上的同步实现方式应该是很经典的实现方式。

让我们继续前行

----------------------------------------------------------------------

努力不一定成功,但不努力肯定不会成功。

ArrayBlockingQueue-我们到底能走多远系列(42)的更多相关文章

  1. ThreadPoolExecutor机制探索-我们到底能走多远系列(41)

    我们到底能走多远系列(41) 扯淡: 这一年过的不匆忙,也颇多感受,成长的路上难免弯路,这个世界上没人关心你有没有变强,只有自己时刻提醒自己,不要忘记最初出发的原因. 其实这个世界上比我们聪明的人无数 ...

  2. JMS生产者+单线程发送-我们到底能走多远系列(29)

    我们到底能走多远系列(29) 扯淡: “然后我俩各自一端/望着大河弯弯/终于敢放胆/嘻皮笑脸/面对/人生的难”      --- <山丘> “迎着风/迎向远方的天空/路上也有艰难/也有那解 ...

  3. Spring mvc源码url路由-我们到底能走多远系列(38)

    我们到底能走多远系列38 扯淡: 马航的事,挺震惊的.还是多多珍惜身边的人吧. 主题: Spring mvc 作为表现层的框架,整个流程是比较好理解的,毕竟我们做web开发的,最早也经常接触的就是一个 ...

  4. 服务调用方案(Spring Http Invoker) - 我们到底能走多远系列(40)

    我们到底能走多远系列(40) 扯淡:  判断是否加可以效力于这家公司,一个很好的判断是,接触下这公司工作几年的员工,了解下生活工作状态,这就是你几年后的状态,如果满意就可以考虑加入了. 主题: 场景: ...

  5. node实现http上传文件进度条 -我们到底能走多远系列(37)

    我们到底能走多远系列(37) 扯淡: 又到了一年一度的跳槽季,相信你一定准备好了,每每跳槽,总有好多的路让你选,我们的未来也正是这一个个选择机会组合起来的结果,所以尽可能的找出自己想要的是什么再做决定 ...

  6. node模拟http服务器session机制-我们到底能走多远系列(36)

    我们到底能走多远系列(36) 扯淡: 年关将至,总是会在一些时间节点上才感觉时光飞逝,在平时浑浑噩噩的岁月里都浪费掉了太多的宝贵.请珍惜! 主题:      我们在编写http请求处理和响应的代码的时 ...

  7. js中this和回调方法循环-我们到底能走多远系列(35)

    我们到底能走多远系列(35) 扯淡: 13年最后一个月了,你们在13年初的计划实现了吗?还来得及吗? 请加油~ 主题: 最近一直在写js,遇到了几个问题,可能初入门的时候都会遇到吧,总结下. 例子: ...

  8. html5实现饼图和线图-我们到底能走多远系列(34)

    我们到底能走多远系列(34) 扯淡: 送给各位一段话:     人生是一个不断做加法的过程     从赤条条无牵无挂的来     到学会荣辱羞耻 礼仪规范     再到赚取世间的名声 财富 地位    ...

  9. Bean实例化(Spring源码阅读)-我们到底能走多远系列(33)

    我们到底能走多远系列(33) 扯淡: 各位:    命运就算颠沛流离   命运就算曲折离奇   命运就算恐吓着你做人没趣味   别流泪 心酸 更不应舍弃   ... 主题: Spring源码阅读还在继 ...

随机推荐

  1. [问题2014S15] 复旦高等代数II(13级)每周一题(第十五教学周)

    [问题2014S15]  设 \(O\) 为 \(n\) 阶正交阵,\(A=\mathrm{diag}\{a_1,a_2,\cdots,a_n\}\) 为实对角阵, 证明: 方阵 \(OA\) 的特征 ...

  2. 从零开始学iPhone开发(4)——使用WebView

    转自 总结关于iPhone中UIWEBVIEW读取本地GBK编码格式html 关于webView读取本地GBK编码的html,尝试了两天,终于成功. 欢喜之余,把感想记下来.一般来说,不成都是人犯错, ...

  3. 配置eclipse使能打开当前文件所在目录

    配置方法如下:Run -> External Tools -> External Tools Configurations...右键Program,new 在右边的界面中: Locatio ...

  4. CentOS7 续续

    1.配置网络,虚拟机为桥接模式,IP地址为 192.168.100+学号/24,配置完成后可以通过ping物理机192.168.100段,或者ping 192.168.100.140验证2.通过临时与 ...

  5. Dialog , ProgressDialog , PopWindow 区别

    本质区别: Dialog:非阻塞对话框,弹出对话框时时,后台还可以做事情,点击背景时,对话框消失 ProgressDialog:带有圆形进度或者条形进度的对话框,一般结合handler使用.任务完成后 ...

  6. 遗传算法在JobShop中的应用研究(part 2:编码)

    编码 在上一篇博客中我们讨论了车间调度问题的编码,具体说就是根据工件的个数和每个工件的工序数来生成12122这样的数字排列,具体的说一个工件包含多少道工序,那么这个工件的编号就出现多少次.从12122 ...

  7. [HIHO1052]基因工程(找规律)

    题目链接:http://hihocoder.com/problemset/problem/1052 题意:中文题面,就是修改其中几个字符,使得[0,k-1]和[n-k,n-1]的字符相同. 会发现一个 ...

  8. XAF应用开发教程-内置Attribute功能列表

    在 XAF 框架,一些用来生成一个业务应用程序的信息是在Attribute中指定.您可以将属性应用到业务类 (或它的成员) 指定验证规则,指定如何对数据进行显示. 设置关系类等.本主题提供了有关在何处 ...

  9. Android handler 详解(面试百分之100问到)

    handler在Android中被称为“消息处理者”,在多线程中比较常用. handler内部实现原理 handler实现机制:1,Message对象,表示要传递的一个消息,内部使用链表数据结构实现一 ...

  10. C语言语法之运算符及优先级

    注:该内容整理自以下链接. http://blog.csdn.net/huangblog/article/details/8271791 表:C语言运算符及优先级 优先级 运算符 名称或含义 使用形式 ...