• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

多线程下使用容器(下)

技术杂谈 勤劳的小蚂蚁 3个月前 (01-19) 94次浏览 已收录 0个评论 扫描二维码

还记得我们之前定义过一个叫BlockedQueue的队列么,它的插入操作和移出操作都有可能导致线程进入等待状态,我们把这样的队列就叫做阻塞队列,这种队列被广泛的用在生产者/消费者模式中,它这么有用,设计java的大叔们当然会为我们定义好现成的东西给我们用啦,所以它们提出了一个叫BlockingQueue接口,当阻塞队列不可用时(比如在队列已满的时候继续插入,当队列为空的时候移出元素),各个方法的处理方式如下:


具体是这个意思:
  • 抛出异常
    当队列已满时调用add方法插入元素会抛出IllegalStateException异常,当队列为空时调用remove方法移出元素会抛出NoSuchElementException异常。
  • 返回特殊值
    调用offer方法插入元素如果成功返回true,队列已满时返回false;调用poll方法移出元素如果成功返回该元素,队列为空时返回null
  • 阻塞
    当队列已满时调用put方法插入元素会一直阻塞到别的线程从队列中移出元素或者别的线程中断了阻塞线程,当队列为空时调用take方法移出元素会一直阻塞别的线程往队列中插入元素或者别的线程中断了阻塞线程。
  • 超时阻塞
    在指定时间内阻塞,超过指定时间返回。
小贴士:

如果队列是无界的,就是有多少元素就插多少元素的那种,则调用put和offer方法永远不会被阻塞。
针对BlockingQueue接口,设计java的大叔给我们提供了下边7种实现类,我们前边已经用过内置锁的wait/notify机制或者显式锁的Condition机制来构建过我们自己的阻塞队列,所以下边我并不打算过多的说这些阻塞队列的原理,重点时掌握这些类的特性以便我们使用。

`ArrayBlockingQueue`

底层是数组结构的有界阻塞队列。

公平性不用再强调了吧,就是当阻塞条件解除时,阻塞的线程是否按照先来后到的恢复执行。

`LinkedBlockingQueue`

底层是链表结构的有界阻塞队列。

`PriorityBlockingQueue`

支持优先级排序的无界阻塞队列。
所谓自然顺序,就是指待排序类实现了Comparable接口,使用覆盖的compare方法进行排序,也可以通过传入自定义的Comparator比较器进行排序。如果你不是很清楚的话,可以跳到前边的章节重新看看。

`DelayQueue`

支持延时获取的无界阻塞队列。
假如这个队列里有3个元素,第1个元素需要在8:10才能获取,第2个元素需要8:20才能获取,第3个元素需要在8:30才能获取。假设当前的时间是8:00,虽然此刻队列里有元素,但是现在调用take方法仍然会阻塞,直到10分钟后第1个元素获取时间到才可以恢复执行,获取完第1个元素后又开始阻塞,直到再过10分钟第2个元素获取时间到才恢复执行,获取第3个元素的过程也是这样。
我们大致看一下它的代码:
public class DelayQueue<E extends Delayedextends AbstractQueue<Eimplements BlockingQueue<E{

    private final PriorityQueue<E> q = new PriorityQueue<E>();

    // … 为节省篇幅,省略其他内容

}    

可以看到,如果你要用DelayedQueue来存储元素,那么元素的类必须实现Delayed接口。另外,DelayQueue内部维护了一个PriorityQueue,数据其实存储在这个队列里边。
首先看一下Delayed接口:
public interface Delayed extends Comparable<Delayed{
    long getDelay(TimeUnit unit);
}
可以看到,Delayed其实继承了Comparable接口,我们之前详细唠叨过Comparable接口,实现了这个接口的类意味着它的对象可以相互比较,比较规则在compareTo方法中指定。因为存储的元素都实现了Comparable接口,所以插入底层的PriorityQueue都是有序的,我们一般设置的比较规则就是:获取等待时间越少,那么这个对象就越小,这样就可以保证头节点的获取等待时间最少
getDelay方法代表着当前对象可以被获取的剩余时间,在DelayedQueuetake方法里有这么一段代码:
public E take() throws InterruptedException {
    // … 为突出重点,省略其他代码

    E first = q.peek();  //获取但不移除
    long delay = first.getDelay(TimeUnit.NANOSECONDS);
    if (delay <= 0//如果不大于0则移除
        return q.poll();
    // … 为突出重点,省略其他代码    

}    

也就是如果调用该对象的getDelay方法返回结果不大于0,说明这个节点可以获取,如果返回结果大于0,说明还没到获取该对象的时机,这个线程将处于限时等待状态
下边我们来定义一个延时队列的元素类:
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayedObject implements Delayed {

    private int time;   //节点可以获取的时间,单位:纳秒

    public DelayedObject(int time) {
        this.time = time;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        long curNaos = System.nanoTime();   //获取以纳秒表示的当前时间
        return time – curNaos;
    }

    @Override
    public int compareTo(Delayed o) {
        if (this == o) {
            return 0;
        }

        if (o instanceof DelayedObject) {   //如果该对象是DelayedObject,直接比较获取时间
            int d = this.time – ((DelayedObject) o).time;
            return (d == 0) ? 0 : ((d < 0) ? –1 : 1);
        }

        //该对象不是DelayedObject,比较getDelay方法
        long d = getDelay(TimeUnit.NANOSECONDS) – o.getDelay(TimeUnit.NANOSECONDS);
        return (d == 0) ? 0 : ((d < 0) ? –1 : 1);
    }
}
time代表节点可以获取的时间,getDelay方法直接比较一下指定获取时间和当前时间,如果差值大于0,说明时间还没到,否则时间就到了。compareTo方法表明了在比较两个DelayedObject对象时,是根据对象的获取时间判断的,获取时间越小,在队列里的位置就越靠前。

`SynchronousQueue`

不存储元素的阻塞队列。
这种队列内部并没有维护链表也没有维护数组,在一个线程调用put方法之后就会进入阻塞状态,直到另一个线程调用take方法把元素拿走或者响应中断才继续恢复执行。

`LinkedTransferQueue`

底层是链表结构的无界阻塞队列。
与其他阻塞队列相比,这个队列多了两个方法:

`LinkedBlockingDeque`

底层是链表结构的双向阻塞队列。
这个阻塞队列的两端都可以进行插入和阻塞操作,在原来队列的基础上增加了addFirstaddLastofferFirstofferLastpeekFirstpeekLast等方法。



    丨极客文库, 版权所有丨如未注明 , 均为原创丨
    本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
    转载请注明原文链接:多线程下使用容器(下)
    喜欢 (0)
    [247507792@qq.com]
    分享 (0)
    勤劳的小蚂蚁
    关于作者:
    温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

    您必须 登录 才能发表评论!

    • 精品技术教程
    • 编程资源分享
    • 问答交流社区
    • 极客文库知识库

    客服QQ


    QQ:2248886839


    工作时间:09:00-23:00