2019-05-18 阻塞队列和并发容器

1.什么是阻塞队列

阻塞队列(BlockingQueue)是指当队列为空时,取出元素的操作会一直阻塞,直到队列有元素。当队列满时,插入元素会阻塞,直到队列恢复非满可用状态。

2.java里的阻塞队列

JDK提供7个阻塞队列,分别是:

  • ArrayBlockinigQueue: 一个由数据组成的有界阻塞队列。
  • LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列。
  • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列。
  • DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue: 一个不存储元素的阻塞队列。
  • LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
  • LinkedBlockingQueue: 一个由链表结构组成的双向阻塞队列。

下面介绍几种常见的阻塞队列:
ArrayBlockingQueue
ArrayBlockingQueue是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。我们可以使用以下代码创建一个公平的阻塞队列:

  ArrayBlockingQueue fairQueue = new  ArrayBlockingQueue(1000,true);

LinkedBlockingQueue
LinkedBlockingQueue是一个用链表实现的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE,此队列按照先进先出的原则对元素进行排序。

PriorityBlockingQueue
PriorityQueue是一个支持优先级的无界队列。默认情况下元素采取自然排序,也可以通过比较器comparator来执行元素的排序规则。

DelayQueue
DelayQueue是一个支持演示获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delay接口,在创建元素时可以指定多久才能从队列中获取当前的元素。只有延时期满的元素才能从队列中获取。我们可以将DelayQueue运用在以下应用场景:

  • 缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
  • 定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。
    使用例子
package com.demo.contrainer;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayQueueTest {
    
    class DelayEle implements Delayed{
        public long delayTime;
        public String name;
        public DelayEle (int delayTime, String name){
            this.delayTime = System.currentTimeMillis() + delayTime;
            this.name = name;
        }

        public int compareTo(Delayed o) {
            return this.delayTime  - ((DelayEle)o).delayTime >  0 ? 1 :
                            this.delayTime  - ((DelayEle)o).delayTime < 0  ? -1 :0;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(this.delayTime  - System.currentTimeMillis() , TimeUnit.MILLISECONDS);
        }
        public String toString(){
            return this.name;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        DelayQueueTest t = new DelayQueueTest();
        //这里第一个参数是在多少毫秒才能被取出
        DelayEle ele1 = t.new DelayEle(1000,"t1");
        DelayEle ele2 = t.new DelayEle(5000,"t2");
        DelayEle ele3 = t.new DelayEle(3000,"t3");
        
        DelayQueue dq = new DelayQueue();
        dq.add(ele1);
        dq.add(ele2);
        dq.add(ele3);
        
        while(dq.size() > 0){
            System.out.println(dq.take());
        }
    }
    
}

3.阻塞队列中的方法 VS 非阻塞队列中的方法

1.非阻塞队列中的几个主要方法:

  add(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常;

  remove():移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常;

  offer(E e):将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false;

  poll():移除并获取队首元素,若成功,则返回队首元素;否则返回null;

  peek():获取队首元素,若成功,则返回队首元素;否则返回null

对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。

2.阻塞队列中的几个主要方法:
阻塞队列包括了非阻塞队列中的大部分方法,上面列举的5个方法在阻塞队列中都存在,但是要注意这5个方法在阻塞队列中都进行了同步措施。除此之外,阻塞队列提供了另外4个非常有用的方法:

  put(E e)

  take()

  offer(E e,long timeout, TimeUnit unit)

  poll(long timeout, TimeUnit unit)
  • put方法用来向队尾存入元素,如果队列满,则等待;
  • take方法用来从队首取元素,如果队列为空,则等待;
  • offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
  • poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;

4.并发容器 copy-on-write(COW)

1.CopyOnWirte容器即写时复制的容器,就是当容器添加元素的时候,先将容器复制一份,然后往新的容器里面添加元素,然后再将原容器指向新容器。JDK1.5以后,java并发包提供了两个使用CopyOnWrite机制实现的并发容器:CopyOnWriteArrayList和CopyOnWriteArraySet。
2.优点:容器支持并发的读,不需要加锁。适用于读多写少的并发场景。
3.缺点:内存占用问题以及无法保证数据一致性。

5.并发容器ConcurrentHashMap

1.锁分段技术:ConCurrentHashMap容器里有多把锁,每一把锁用于锁容器其中一部分数据,当多线程范文容器中不同数据段的数据是,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率。
2.ConcurrentHashMap的分段segment数组长度必须是2的n次方,这样的好处是方便采用移位操作来进行hash,加快hash的过程。
3.Segment的数据结构:

static final class Segment<K,V> extends ReentrantLock implements Serializable {
    transient volatile int count;
    transient int modCount;
    transient int threshold;
    transient volatile HashEntry<K,V>[] table;
    final float loadFactor;
}

Segment里面的成员变量的意义:

count:Segment中元素的数量
modCount:对table的大小造成影响的操作的数量(比如put或者remove操作)
threshold:阈值,Segment里面元素的数量超过这个值依旧就会对Segment进行扩容
table:链表数组,数组中的每一个元素代表了一个链表的头部
loadFactor:负载因子,用于确定threshold
4.ConcurrentHashMap的get操作是不用加锁的,put操作是加锁完成的.

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

推荐阅读更多精彩内容