Java多线程并发编程学习笔记【并发包中队列笔记】

JUC包下主要队列类关系图如下(基于JAVA1.8)

主要有DelayQueue、PriorityBlockingQueue、ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue,还有双向队列Deque相关类暂时不看

上面五个类按照实现方式来分可以分为,链表实现和数组实现。数组又分循环数组实现的有界队列,和完全二叉树实现的无界队列。

链表实现有:ConcurrentLinkedQueue(无界)、LinkedBlockingQueue(有界(或者说可选有界));

循环数组实现:ArrayBlockingQueue,有界队列;

平衡二叉树数组实现:PriorityBlockingQueue、DelayQueue(内部放一个PriorityQueue),无界队列,自动扩容。

一、ConcurrentLinkedQueue

线程安全、无界非阻塞队列,入列和出列使用CAS来实现。

  • boolean offer:末尾添加一个元素,如果参数是null会抛空指针异常(NPE),否则返回true(因为采用自旋CAS,不会阻塞)。操作tail节点存在延迟,累计两次的tail节点改动才会更改tail节点;
  • boolean add:调用offer;
  • E poll:会获取并移除头部一个元素,如果队列为空则返回null;在移除一个元素时,只是简单地使用CAS操作把当前节点的item值设置为null,然后通过重新设置节点头部将该元素从队列里面移除。被移除的节点就变成了孤立节点,这个节点会在垃圾回收时被回收掉。
  • E peek:获取队列头部元素,如果队列为空则返回null;第一次调用时会删除哨兵节点(初始化时,item为null的节点)。
  • int size()、boolean contains()操作不一定精确。
  • boolean remove(Object o),存在则删除,多个则删除第一个,返回true;不存在则返回false

二、LinkedBlockingQueue

线程安全有界(或者说可选有界)阻塞队列,入列出列分别使用不同的ReentrantLock。不传队列大小则默认为Integer.MAX_VALUE。

  • boolean offer:末尾添加一个元素,如果队列有空闲则插入成功返回true;如果队列满则丢弃当前元素然后返回false。如果参数是null会抛空指针异常(NPE);不等待出列;
  • boolean add:调用offer;
  • void put:向队尾插入一个元素,如果队列有空闲则插入,元素为null会抛NPE;如果队列已满则阻塞当前队列,知道队列有空闲再插入。如果在阻塞时被其他线程线程设置中断,那么会抛出中断异常而返回。
  • E poll:会获取并移除头部一个元素,如果队列为空则返回null;流程大体是将头结点的next设置为头结点,并将原来的头结点设置为null,不等待入列
  • E peek:获取队列头部元素,如果队列为空则返回null,不等待入列;
  • E take:获取头部元素并移除。如果队列为空则阻塞直到队列有新元素进来后返回元素。如果阻塞时被设置中断标志,则被阻塞线程会抛出中断异常而返回。
  • size()、contains操作可以准确。
  • boolean remove(Object o),存在则删除,多个则删除第一个,返回true;不存在则返回false

三、ArrayBlockingQueue

线程安全有界阻塞队列,数组实现。入列下标、出列下标、队列元素个数等变量没有加volatile,因为这些变量本身已经在锁内操作了。

构造函数可以选公平锁(先来先得)和非公平锁(大家抢),默认非公平锁。

  • boolean offer:向队列尾部插入一个元素,如果队列有空闲空间则插入,元素为null会抛NPE;如果队列满则丢弃元素并返回false;不等待出列。
  • void put:向队尾插入一个元素,如果有空闲则插入后直接返回true,元素为null会抛NPE;如果队列已满则阻塞当前线程直到队列有空闲并插入成功后返回true。如果在阻塞时被其他线程线程设置中断,那么会抛出中断异常而返回。
  • E poll:获取并移除头部一个元素,如果队列为空则返回null;流程大体是将takeindex的数组元素设置为null,然后对takeindex重新计算(加1或重置为0);不等待入列
  • E take:获取头部元素并移除。如果队列为空则阻塞直到队列有新元素进来后返回元素。如果阻塞时被设置中断标志,则被阻塞线程会抛出中断异常而返回。
  • E peek:获取队列头部(takeindex)元素,如果队列为空则返回null,不等待入列;
  • size()、contains操作可以准确。
  • boolean remove(Object o),存在则删除,多个则删除第一个,返回true;不存在则返回false

四、PriorityBlockingQueue

PriorityBlockingQueue利用完全二叉树实现,数据结构为小顶堆(最小堆)。每次出列取其最顶端元素(根节点),也是最小元素,其二叉树只能保证每个非叶子结点都比其叶子节点要小,不能保证左右叶子结点之间的大小顺序,更不能保证叶子节点都比非叶子结点要大。

boolean offer:在队列中插入一个元素,过程大体是通过比较器comparator与其父节点比较大小,如果待插入元素比父节点小,那么互相调换位置;然后再与更上一层的父节点比较,如果一直比父节点小,则一直调换位置,直到与根节点比较完成为止。这样就能保证每次插入元素后,根节点总是最小的;

boolean add:内部调用offer;

E pool:获取队列根节点元素,如果队列为空则返回null;不等待入列;获取根节点元素后,会重新調整树结构,大体流程是,先取根节点下的子节点中最小的节点作为根节点,然后调整被选子节点的子树结构。由于被选子节点树会少一个节点,所以会把结构树中最后一个节点加入到需要重构的子树中去,确保重构后整体仍然是完全二叉树。

void put:内部调用offer;

E take:获取根节点元素,如果队列为空则阻塞等待

int size:计算队列个数

例子如下:

package sample.Thread;

public class TestPriorityBlockingQueue implements Comparable<TestPriorityBlockingQueue> {
        private int priority =0;
        private String taskName;

        public int getPriority() {
            return priority;
        }

        public void setPriority(int priority) {
            this.priority = priority;
        }

        public String getTaskName() {
            return taskName;
        }

        public void setTaskName(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public int compareTo(TestPriorityBlockingQueue o) {
            if(this.priority>=o.getPriority()) {
                return 1;
            } else {
                return -1;
            }
        }

        public void doSomeThing() {
            System.out.println(taskName+":"+priority);
        }

}


@Test
    public void testPriorityBlockingQueue() {
        PriorityBlockingQueue<TestPriorityBlockingQueue> priorityBlockingQueue = new PriorityBlockingQueue<TestPriorityBlockingQueue>();
        Random random =new Random();
        for(int i=0;i<10;i++) {
            TestPriorityBlockingQueue task =new TestPriorityBlockingQueue();
            task.setPriority(random.nextInt(10));
            task.setTaskName("taskName:"+i);
            priorityBlockingQueue.offer(task);
        }
        while(!priorityBlockingQueue.isEmpty()) {
            TestPriorityBlockingQueue task = priorityBlockingQueue.poll();
            if(null!=task) {
                task.doSomeThing();
            }
        }
}

五、DelayQueue

DelayQueue内部使用PriorityQueue存放数据,使用ReentrantLock实现线程同步,其原理和PriorityBlockingQueue一样。DelayQueue队列中的每个元素都有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最快要过期元素。

其他笔记

1、循环数组array实现的队列要比链表实现的要快,非阻塞(使用cas)比阻塞(有lock)队列要快

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值