6.其他队列
6.1 对比
双端队列(Double-Ended Queue, deque)、栈(Stack)和队列(Queue)是三种基本且重要的数据结构,它们各自具有不同的操作特性和使用场景:
栈 (Stack):
结构特点:栈是一种后进先出(Last-In-First-Out, LIFO)的数据结构,它只允许在一端进行插入和删除操作。这一端通常称为栈顶。
操作特性:主要支持push(入栈,将元素添加到栈顶)、pop(出栈,移除并返回栈顶元素)以及peek(查看栈顶元素但不移除)等操作。
应用场景:栈常用于实现函数调用堆栈、括号匹配检查、深度优先搜索算法(DFS)等。
队列 (Queue):
结构特点:队列遵循先进先出(First-In-First-Out, FIFO)的原则,允许在队尾插入元素(enqueue或add),而在队头删除元素(dequeue或remove)。
操作特性:主要支持enqueue(入队)、dequeue(出队)、peek(查看队头元素但不移除)以及判断是否为空等操作。
应用场景:队列常见于多线程同步、任务调度、广度优先搜索算法(BFS)、消息传递系统等需要有序处理多个任务的场合。
6.2 双端队列
6.2.1 概念
双端队列 (Double-Ended Queue, deque):
结构特点:可以在两端进行插入和删除的序列容器,同时具备队列和栈的部分特性。
操作特性:包括push_front/pop_front(在/从队头操作)、push_back/pop_back(在/从队尾操作)等功能。
应用场景:滑动窗口算法、回文串检测、实时事件处理等。
6.2.2 链表实现
public class LinkedListDeque<E> implements Deque<E> { public static void main(String[] args) { LinkedListDeque<Integer>linkedListDeque=new LinkedListDeque<>(5); boolean offerLast1 = linkedListDeque.offerLast(1); boolean offerLast2 = linkedListDeque.offerLast(2); boolean offerLast3 = linkedListDeque.offerLast(3); boolean offerLast4 = linkedListDeque.offerLast(4); boolean offerLast5 = linkedListDeque.offerLast(5); boolean offerLast6 = linkedListDeque.offerLast(6); Integer first1 = linkedListDeque.poolFirst(); Integer first2 = linkedListDeque.poolFirst(); Integer last1 = linkedListDeque.poolLast(); Integer last2 = linkedListDeque.poolLast(); Integer last3 = linkedListDeque.poolLast(); Integer last4 = linkedListDeque.poolLast(); } private int capacity; private int size; Node<E> sentinel=new Node<E>(null,null,null); public LinkedListDeque(int capacity) { this.capacity = capacity; sentinel.next=sentinel; sentinel.prev=sentinel; } static class Node<E>{ Node prev; E value; Node next; public Node(Node<E> prev, E value, Node<E> next) { this.prev = prev; this.value = value; this.next = next; } } @Override public boolean offerFirst(E e) { if(isFull()){ return false; } //上一个节点是哨兵,下一个节点是哨兵.next Node oldFirst = sentinel.next; //新的节点 Node<E> addNode=new Node<E>(sentinel,e,oldFirst); //旧节点修改prev指针 oldFirst.prev=addNode; //修改哨兵next指针 sentinel.next=addNode; //增加容量 size++; return true; } @Override public boolean offerLast(E e) { if(isFull()){ return false; } //旧的最后一个节点 Node oldLast = sentinel.prev; //新的节点 Node addNode=new Node(oldLast,e,sentinel); //旧节点修改next指针 oldLast.next=addNode; //修改哨兵prev指针 sentinel.prev=addNode; size++; return true; } @Override public E poolFirst() { if(isEmpty()){ return null; } //需要出队列的节点 Node<E> poolNode=sentinel.next; //移除节点的下一个节点 Node<E>nextNode=poolNode.next; //修改下一个节点.prev指针 nextNode.prev=sentinel; //修改哨兵.next指针 sentinel.next=nextNode; size--; return poolNode.value; } @Override public E poolLast() { if(isEmpty()){ return null; } //需要出队列的节点 Node<E> poolNode=sentinel.prev; //移除节点的上一个节点 Node<E>prevNode=poolNode.prev; //修改上一个节点.next指针 prevNode.next=sentinel; //修改哨兵.prev指针 sentinel.prev=prevNode; size--; return poolNode.value; } @Override public E peekFirst() { if(isEmpty()){ return null; } return (E) sentinel.next.value; } @Override public E peekLast() { if(isEmpty()){ return null; } return (E) sentinel.prev.value; } @Override public boolean isEmpty() { return size==0; } @Override public boolean isFull() { return size==capacity; } @Override public Iterator<E> iterator() { return new Iterator<E>() { Node<E> piont=sentinel.next; @Override public boolean hasNext() { return piont!=sentinel; } @Override public E next() { E val= piont.value; piont=piont.next; return val; } }; } }
6.2.3 数组实现
public class ArrayDeque1<E> implements Deque<E>{ public static void main(String[] args) { ArrayDeque1<Integer> arrayDeque1=new ArrayDeque1(3); boolean full1 = arrayDeque1.isFull(); boolean empty1 = arrayDeque1.isEmpty(); boolean offerFirst1 = arrayDeque1.offerFirst(1); boolean offerFirst2 = arrayDeque1.offerFirst(2); boolean offerFirst3 = arrayDeque1.offerLast(3); boolean offerFirst4 = arrayDeque1.offerLast(4); boolean full2 = arrayDeque1.isFull(); boolean empty2 = arrayDeque1.isEmpty(); int poolFirst1= arrayDeque1.poolFirst(); int poolLast1= arrayDeque1.poolLast(); } E[]array; private int head; private int tail; /* * tail不存值,实际长度应该用参数+1 * */ public ArrayDeque1(int capacity) { array= (E[]) new Object [capacity+1]; } @Override /* * 先head-1再添加元素 * */ public boolean offerFirst(E e) { if(isFull()){ return false; } head= dec(head, array.length); array[head]=e; return true; } @Override /* * 添加元素后,tail+1 * */ public boolean offerLast(E e) { if(isFull()){ return false; } array[tail]=e; tail= inc(tail, array.length); return true; } @Override /* * 先获取值,再head++(转换为合法索引) * */ public E poolFirst() { if(isEmpty()){ return null; } E e = array[head]; //取消引用,自动释放内存 array[head]=null; head=inc(head,array.length); return e; } @Override /* * 先tail-- ,再获取元素(转换为合法索引) * */ public E poolLast() { if(isEmpty()){ return null; } tail=dec(tail,array.length); E e = array[tail]; //取消引用,自动释放内存 array[tail]=null; return e; } @Override public E peekFirst() { if(isEmpty()){ return null; } return array[head]; } /* * 获取 tail-1位置的元素 * */ @Override public E peekLast() { return array[dec(tail-1,array.length)]; } @Override /* * 头尾指向同一个位置即为空 * */ public boolean isEmpty() { return head==tail; } @Override /* * head ~ tail =数组.length-1 * tail>head tail-head ==数组.length-1 * tail<head head-tail==1 * */ public boolean isFull() { if(head <tail){ return tail-head==array.length-1; }else if(head >tail){ return head-tail==1; } return false; } @Override public Iterator<E> iterator() { return new Iterator<E>() { int poin=head; @Override public boolean hasNext() { return poin!=tail; } @Override public E next() { E e = array[poin]; poin = inc(poin + 1, array.length); return e; } }; } /* * 索引++之后越界恢复成0 * */ static int inc(int i, int length){ if(++i>=length){ return 0; } return i; } /* * 索引--之后越界恢复成数组长度 * */ static int dec(int i, int length){ if(--i <0)return length-1; return i; } }
注意:基本类型占用字节相同,不需要考虑内存释放,但是引用数据类型要考虑内存释放问题(当不删除元素时,该索引位置应该置位null,否则垃圾回收器不会自动回收)。
6.3 优先级队列
6.3.1 概念
优先级队列 (Priority Queue):
结构特点:每个元素都有一个优先级,每次取出的是当前优先级最高的元素。可以基于堆实现。
操作特性:主要包括insert(插入元素)、delete_min/max(移除并返回优先级最高/最低的元素)等。
应用场景:Dijkstra算法中的最短路径搜索、操作系统中的进程调度、事件驱动编程中的事件处理等。
6.3.2 无序数组实现
基于无序数组的优先级队列(例如使用索引堆):
插入元素:
无序数组实现如索引堆等结构可以相对快速地插入元素,并通过索引维护堆属性,插入操作的时间复杂度一般为O(log n)。
删除最高优先级元素:删除最小元素通常涉及到重新调整堆结构以满足堆属性,即保证父节点的优先级高于或等于子节点,时间复杂度同样是O(log n)。
优势:插入和删除操作的时间复杂度都相对较低,都是O(log n),适用于动态变化的数据集合。
相对于有序数组,插入和删除操作更加高效,特别是在大量元素插入、删除的情况下。
劣势:查找最高优先级元素并不像有序数组那样简单,每次取出最小元素都需要调整堆结构。
//基于无序数组的实现 public class PriorityQueue<E extends Priority> implements Queue<E> { public static void main(String[] args) { Test test1 = new Test(1, "任务9"); Test test2 = new Test(2, "任务8"); Test test3 = new Test(3, "任务7"); Test test4 = new Test(4, "任务6"); PriorityQueue priorityQueue=new PriorityQueue(3); boolean full1 = priorityQueue.isFull(); boolean empty1 = priorityQueue.isEmpty(); Priority peek1 = priorityQueue.peek(); boolean offer1 = priorityQueue.offer(test1); boolean offer2 = priorityQueue.offer(test2); boolean offer3 = priorityQueue.offer(test3); boolean offer4 = priorityQueue.offer(test4); boolean full2 = priorityQueue.isFull(); boolean empty2 = priorityQueue.isEmpty(); Priority peek2 = priorityQueue.peek(); Priority pool1 = priorityQueue.pool(); Priority pool2 = priorityQueue.pool(); Priority pool3 = priorityQueue.pool(); Priority pool4 = priorityQueue.pool(); } static class Test extends Priority { String task; public Test(int priority, String task) { super(priority); this.task = task; } } @Getter static Priority[] array; private static int size=0; public PriorityQueue(int capacity) { array = new Priority[capacity]; } @Override public boolean offer(E value) { if (isFull()) { return false; } array[size] = value; size++; return true; } @Override public E pool() { if(isEmpty()){ return null; } int maxIndex = getMaxIndex(); Priority priority = array[maxIndex]; remove(maxIndex); return (E) priority; } //移除元素 private void remove(int maxIndex) { if(maxIndex<size-1){ //不是最后一个元素 System.arraycopy(array,maxIndex+1,array,maxIndex,size-1-maxIndex); } size--; } private static int getMaxIndex(){ int max = 0; for (int i = 0; i < array.length; i++) { if (array[max].priority < array[i].priority) { max = i; } } return max; } @Override public E peek() { if(isEmpty()){ return null; } int max = 0; for (int i = 0; i < size; i++) { if (array[max].priority < array[i].priority) { max = i; } } Priority priority = array[max]; return (E) priority; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean isFull() { return size == array.length; } @Override public Iterator<E> iterator() { return new Iterator<E>() { private int p; @Override public boolean hasNext() { return p < array.length; } @Override public E next() { Priority priority = array[p]; p++; return (E) priority; } }; } }
6.3.3 有序数组实现
基于有序数组的优先级队列:
插入元素:
插入新元素需要保持数组的有序性,因此在插入过程中可能需要移动部分或全部已存在的元素以腾出位置给新元素,时间复杂度通常是O(n)。
由于数组本身是有序的,可以通过二分查找快速定位到插入点,但是插入后的调整仍然涉及元素移动。
删除最高优先级元素:可以直接访问并删除数组的第一个元素(假设是最小元素),时间复杂度为O(1)。
删除后如果需要维持有序,可能需要将最后一个元素移动到被删除元素的位置,或者使用某种方法来“填补”空缺,总体上删除操作的时间复杂度也是O(n)。
优势:查找最高优先级元素的时间复杂度低,为O(1)。
如果插入不是非常频繁且数组大小相对稳定,那么其效率可能会较高,因为查询和删除最小元素高效。
劣势:插入和删除元素的成本高,尤其是当数组较大时,频繁的移动操作会显著降低性能。
//基于有序数组的实现 public class PriorityQueue3<E extends Priority> implements Queue<E> { public static void main(String[] args) { Test test1 = new Test(1, "任务9"); Test test2 = new Test(2, "任务8"); Test test3 = new Test(3, "任务7"); Test test4 = new Test(4, "任务6"); PriorityQueue3 priorityQueue=new PriorityQueue3(3); boolean full1 = priorityQueue.isFull(); boolean empty1 = priorityQueue.isEmpty(); Priority peek1 = priorityQueue.peek(); boolean offer2 = priorityQueue.offer(test2); boolean offer1 = priorityQueue.offer(test1); boolean offer3 = priorityQueue.offer(test3); boolean offer4 = priorityQueue.offer(test4); boolean full2 = priorityQueue.isFull(); boolean empty2 = priorityQueue.isEmpty(); Priority peek2 = priorityQueue.peek(); Priority pool1 = priorityQueue.pool(); Priority pool2 = priorityQueue.pool(); Priority pool3 = priorityQueue.pool(); Priority pool4 = priorityQueue.pool(); } static class Test extends Priority { String task; public Test(int priority, String task) { super(priority); this.task = task; } } @Getter static Priority[] array; private static int size; public PriorityQueue3(int capacity) { array = new Priority[capacity]; } @Override public boolean offer(E value) { if (isFull()) { return false; } insert( value); size++; return true; } private void insert(E value) { int i=size-1; while (i>=0 && array[i].priority> value.priority){ //优先级大于入参向上移动 array[i+1]=array[i]; i--; } //在比他小的索引后面插入元素 array[i+1]=value; } @Override public E pool() { if(isEmpty()){ return null; } E priority = (E) array[size - 1]; array[--size]=null; return (priority) ; } @Override public E peek() { if(isEmpty()){ return null; } return (E) array[size-1]; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean isFull() { return size == array.length; } @Override public Iterator<E> iterator() { return new Iterator<E>() { private int p; @Override public boolean hasNext() { return p < array.length; } @Override public E next() { Priority priority = array[p]; p++; return (E) priority; } }; } }
6.3.4 基于堆实现
6.3.4.1 堆的概念
计算机科学中,堆是一种基于树的数据结构,通常用完全二叉树来实现。堆的特性如下:
完全二叉树属性:
堆是一个完全二叉树,这意味着除了最后一层之外,其他每一层都被完全填满,并且所有节点都尽可能地集中在左侧。
堆序性质:在大顶堆(也称为最大堆)中,对于任意节点 C 与其父节点 P,满足不等式 P.value >= C.value,即每个父节点的值都大于或等于其子节点的值。
在小顶堆(也称为最小堆)中,对于任意节点 C 与其父节点 P,满足不等式 P.value <= C.value,即每个父节点的值都小于或等于其子节点的值。
操作特性:插入操作:在保持堆序性质的前提下插入新元素,可能需要调整堆中的元素位置。
删除操作:删除堆顶元素(即最大或最小元素),也需要重新调整堆结构以保证剩余元素仍然满足堆序性质。
查找最大/最小元素:堆顶元素就是整个堆中的最大(在大顶堆中)或最小(在小顶堆中)元素,无需遍历整个堆即可直接获取。
存储方式:堆可以用数组来高效存储,由于是完全二叉树,可以利用数组下标与节点层级、左右子节点之间的关系紧密关联的特点,节省存储空间并加快访问速度。
应用:堆常用于实现优先队列,能够快速找到并移除具有最高或最低优先级的元素。
在许多算法和数据处理中,如求解最值问题、堆排序算法以及图的最小生成树算法(例如Prim算法或Kruskal算法结合使用优先队列)中都有重要应用。
6.3.4.2 堆的分类
堆在计算机科学中主要根据其内部元素的排序特性进行分类,可以分为以下两种类型:
大顶堆(Max Heap): 在大顶堆中,父节点的值总是大于或等于其所有子节点的值。堆的根节点是整个堆中的最大元素,因此,大顶堆常被用于实现优先队列,其中队列头部始终是当前最大的元素。
小顶堆(Min Heap): 在小顶堆中,父节点的值总是小于或等于其所有子节点的值。与大顶堆相反,小顶堆的根节点是整个堆中的最小元素,同样适用于实现优先队列,只不过在这种情况下,队列头部提供的是当前最小的元素。
这两种类型的堆都是完全二叉树结构,并且通常通过数组来实现存储,利用数组索引与树节点之间的逻辑关系快速访问和操作堆中的元素。除了大顶堆和小顶堆之外,还有其他形式的堆,例如斐波那契堆(Fibonacci Heap),它是一种更为复杂的高效数据结构,提供了更优化的插入、删除和合并操作,但并不像普通二叉堆那样要求严格的父节点与子节点的大小关系。
6.3.4.3 堆的计算
在树型数据结构中,从索引0开始存储节点数据是常见的做法,尤其是在数组或链表实现的树结构里。这里我们区分两种情况:
已知子节点求父节点:
如果树结构使用数组来表示,并且我们知道子节点对应的数组索引,那么根据某种固定规则(如:每个节点的左孩子通常位于2倍索引处,右孩子位于2倍索引 + 1处),可以计算出父节点的索引。
在完全二叉树中,给定一个节点i的索引,其父节点的索引可以通过 (i - 1) / 2 计算得出(向下取整)。
已知父节点求子节点:同样地,如果知道父节点在数组中的索引,我们可以很容易地找到它的两个子节点。
左子节点的索引通常是 2 * 父节点索引 + 1。
右子节点的索引通常是 2 * 父节点索引 + 2。如果从索引1开始存储节点:
已知子节点求父节点为: (i ) / 2。
已知父节点求子节点为:2i,2i+1。
请注意,这些规则适用于采用数组实现的完全二叉树和几乎完全二叉树等特定场景下的节点关系查找。对于其他类型的树或者非完全二叉树,可能需要额外的数据结构或不同的逻辑来维护父子节点之间的关系。例如,在链表形式的树结构中,父节点会直接持有指向其子节点的指针,因此不需要通过计算索引来定位子节点。
6.3.4.4 基于大顶堆实现
//基于有序数组的实现 public class PriorityQueue4<E extends Priority> implements Queue<E> { public static void main(String[] args) { Test test1 = new Test(1, "任务9"); Test test2 = new Test(2, "任务8"); Test test3 = new Test(3, "任务7"); Test test4 = new Test(4, "任务6"); PriorityQueue4 priorityQueue = new PriorityQueue4(3); boolean full1 = priorityQueue.isFull(); boolean empty1 = priorityQueue.isEmpty(); Priority peek1 = priorityQueue.peek(); boolean offer2 = priorityQueue.offer(test2); boolean offer1 = priorityQueue.offer(test1); boolean offer3 = priorityQueue.offer(test3); boolean offer4 = priorityQueue.offer(test4); boolean full2 = priorityQueue.isFull(); boolean empty2 = priorityQueue.isEmpty(); Priority peek2 = priorityQueue.peek(); Priority pool1 = priorityQueue.pool(); Priority pool2 = priorityQueue.pool(); Priority pool3 = priorityQueue.pool(); Priority pool4 = priorityQueue.pool(); } static class Test extends Priority { String task; public Test(int priority, String task) { super(priority); this.task = task; } } @Getter static Priority[] array; private static int size; public PriorityQueue4(int capacity) { array = new Priority[capacity]; } /* * 1.入队新元素,加入到数组末尾(索引child) * 2.不断比较新元素与父节点的优先级。 * 如果优先级低于新节点,则向下移动,并寻找下一个paraent * 直至父元素优先级跟高或者child==0为止 * */ @Override public boolean offer(E value) { if (isFull()) { return false; } if(isEmpty()){ array[0]=value; } int child = size++; int parent = (child - 1) / 2; //新元素和父元素优先级对比 while (value.priority > array[parent].priority && child != 0) { array[child] = array[parent]; //子节点指向父节点,父节点指向,原本的父节点的父节点 child = parent; parent = (child - 1) / 2; } array[child] = value; return true; } /* * 1.由于移除最后一个元素效率比较高, * 故应将第一个优先级最高的元素与最后一个元素互换。 * 2.将堆顶元素与两个子元素比较,与较大的那个更换, * 直至该节点为最后一个节点或者子节点都小于该节点 * * */ @Override public E pool() { if (isEmpty()) return null; swap(0, size - 1); size--; //保存优先级最大的元素 Priority p = array[size]; //置空垃圾回收 array[size] = null; //堆顶下潜 down(0); return (E) p; } private void swap(int i, int j) { Priority t = array[i]; array[i] = array[j]; array[j] = t; } private void down(int parent) { int left = parent * 2 + 1; int right = left + 1; int bigChild = parent; //比较左侧元素与父元素 if (left < size && array[left].priority > array[bigChild].priority) bigChild = left; //比较右侧元素与父元素 if (right < size && array[right].priority > array[bigChild].priority) bigChild = right; //交换较大的元素 if (bigChild != parent) { swap(bigChild, parent); //以最大的子元素节点索引为参数继续执行下潜操作 down(bigChild); } } @Override public E peek() { if(isEmpty())return null; return (E) array[0]; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean isFull() { return size == array.length; } @Override public Iterator<E> iterator() { return new Iterator<E>() { private int p; @Override public boolean hasNext() { return p < array.length; } @Override public E next() { Priority priority = array[p]; p++; return (E) priority; } }; } }
6.4 阻塞队列
/* * 为了避免多线程导致的数据混乱,例如:线程T1 修改之后未执行tail++,T2执行赋值, * 会把T1赋值给覆盖掉,然后执行两次tail++,最后导致数据混乱。 * 1.synchronized 关键字,简单功能少 * 2.ReentrantLock 可重入锁,功能多 * */ public class TestThreadUnsafe { public static void main(String[] args) { TestThreadUnsafe queue = new TestThreadUnsafe(); /* queue.offer("E1"); queue.offer("E2");*/ new Thread(() -> { try { queue.offer("E1"); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "t1").start(); new Thread(() -> { try { queue.offer("E2"); } catch (InterruptedException e) { throw new RuntimeException(e); } }, "t2").start(); } private final String[] array = new String[10]; private int tail = 0; private int size = 0; ReentrantLock reentrantLock = new ReentrantLock(); Condition tailWaits=reentrantLock.newCondition();//条件对象集合 public void offer(String s) throws InterruptedException { //加锁 等待直到解锁 // reentrantLock.lock(); // 加锁,阻塞过程中随时打断抛出异常 reentrantLock.lockInterruptibly(); try { /* * 如果使用if 多线程情况下,会导致新加入的线程未走判断,等待的线程被唤醒,两个线程同时向下执行 * 这种唤醒叫做虚假唤醒,每次都应该循环判断是否满了,而不应该用if * */ while (isFull()){ //等待 让线程进入阻塞状态 tailWaits.await(); // 阻塞后唤醒线程使用,且必须配合锁使用 /* reentrantLock.lockInterruptibly(); tailWaits.signal(); reentrantLock.unlock();*/ } array[tail] = s; if(++tail== array.length){ tail=0; } size++; } finally { //解锁必须执行 reentrantLock.unlock(); } } public boolean isFull(){ return size==array.length; } public String toString() { return Arrays.toString(array); } }
6.4.1 单锁实现
/* * 用锁保证线程安全。 * 条件变量让pool或者offer线程进入等待,而不是循环尝试,占用cpu * */ public class BlockingQueue1<E> implements BlockingQueue<E> { public static void main(String[] args) { BlockingQueue1<String> blockingQueue1=new BlockingQueue1<>(3); try { blockingQueue1.offer("任务1"); blockingQueue1.offer("任务2"); blockingQueue1.offer("任务3"); boolean offer = blockingQueue1.offer("任务4", 2000); System.out.println(offer); } catch (InterruptedException e) { throw new RuntimeException(e); } } private final E[] array; private int head; private int tail; private int size; private ReentrantLock lock = new ReentrantLock(); private Condition headWaits = lock.newCondition(); private Condition tailWaits = lock.newCondition(); public BlockingQueue1(int capacity) { this.array = (E[]) new Object[capacity]; } @Override public void offer(E e) throws InterruptedException { lock.lockInterruptibly(); try { //防止虚假唤醒 while (isFull()) { tailWaits.await(); } array[tail] = e; if (++tail == array.length) { tail = 0; } size++; //唤醒等待非空的线程 headWaits.signal(); } finally { lock.unlock(); } } /* * 优化后的offer,设置等待时间,超出则抛出异常 * */ @Override public boolean offer(E e, long timeout) throws InterruptedException { lock.lockInterruptibly(); try { //等待timeout纳秒抛出异常 long ns = TimeUnit.MICROSECONDS.toNanos(timeout); while (isFull()) { if (ns <= 0) { return false; } ns = tailWaits.awaitNanos(ns); } array[tail] = e; if (++tail == array.length) { tail = 0; } size++; //唤醒等待非空的线程 headWaits.signal(); return true; } finally { lock.unlock(); } } @Override public E pool() throws InterruptedException { lock.lockInterruptibly(); try { while (isFull()) { headWaits.await(); } E e = array[head]; array[head] = null; if (++head == array.length) { head = 0; } size--; //唤醒等待不满的线程 tailWaits.signal(); return e; } finally { lock.unlock(); } } public boolean isFull() { return size == array.length; } public boolean isEmpty() { return size == 0; } }
6.4.2 双锁实现
6.4.2.1 双锁实现1
由于tailWaits.signal();必须配合taillock.unlock(); taillock.unlock();使用,headloca也一样,这种方式极易出现死锁问题。
使用jps命令可以获取进程id,jstack+进程id 可以检测死锁等问题。
可以将两个锁列为平级,而不是嵌套即可解决。
/* * 单锁实现方式,offer和pool是互相影响的, * 但是head指针和tail执行两者并不互相影响, * 会降低运行效率,offer和pool不应该互相影响 * */ public class BlockingQueue2<E> implements BlockingQueue<E> { public static void main(String[] args) { BlockingQueue2<String> blockingQueue2 = new BlockingQueue2<>(3); try { blockingQueue2.offer("任务1"); blockingQueue2.offer("任务2"); blockingQueue2.offer("任务3"); boolean offer = blockingQueue2.offer("任务4", 2000); System.out.println(offer); } catch (InterruptedException e) { throw new RuntimeException(e); } } private final E[] array; private int head; private int tail; /* * 由于 pool和offer会同时操作size变量, * 所以size应该被声明为原子变量 * */ private AtomicInteger size=new AtomicInteger(); private ReentrantLock taillock = new ReentrantLock(); private ReentrantLock headlock = new ReentrantLock(); /* * headlock和headWaits必须配合使用否则会报错 * */ private Condition tailWaits = taillock.newCondition(); private Condition headWaits = headlock.newCondition(); public BlockingQueue2(int capacity) { this.array = (E[]) new Object[capacity]; } @Override public void offer(E e) throws InterruptedException { taillock.lockInterruptibly(); try { //防止虚假唤醒 while (isFull()) { tailWaits.await(); } array[tail] = e; if (++tail == array.length) { tail = 0; } size.addAndGet(1); //唤醒等待非空的线程 headWaits.signal(); } finally { taillock.unlock(); } } /* * 优化后的offer,设置等待时间,超出则抛出异常 * */ @Override public boolean offer(E e, long timeout) throws InterruptedException { taillock.lockInterruptibly(); try { //等待timeout纳秒抛出异常 long ns = TimeUnit.MICROSECONDS.toNanos(timeout); while (isFull()) { if (ns <= 0) { return false; } ns = tailWaits.awaitNanos(ns); } array[tail] = e; if (++tail == array.length) { tail = 0; } size.getAndIncrement(); //唤醒等待非空的线程,必须配合headlocal使用 headlock.lock(); headWaits.signal(); headlock.unlock(); return true; } finally { taillock.unlock(); } } @Override public E pool() throws InterruptedException { headlock.lockInterruptibly(); try { while (isFull()) { headWaits.await(); } E e = array[head]; array[head] = null; if (++head == array.length) { head = 0; } size.getAndDecrement(); //唤醒等待不满的线程,tailWaits闭合和tailLock配合使用 taillock.unlock(); tailWaits.signal(); taillock.unlock(); return e; } finally { headlock.unlock(); } } public boolean isFull() { return size.get() == array.length; } public boolean isEmpty() { return size.get() == 0; } }
6.4.2.2 双锁实现2
为了提升效率应该尽量减少taillock和headlock之间的互相影响,对应的就可以提升效率,tail的唤醒操作只由第一个线程执行,而后续的唤醒可以交给第一个线程来执行,headlock也是一样的。这也叫做级联操作。
/* * 单锁实现方式,offer和pool是互相影响的, * 但是head指针和tail执行两者并不互相影响, * 会降低运行效率,offer和pool不应该互相影响 * */ public class BlockingQueue2<E> implements BlockingQueue<E> { public static void main(String[] args) { BlockingQueue2<String> blockingQueue2 = new BlockingQueue2<>(3); try { blockingQueue2.offer("任务1"); blockingQueue2.offer("任务2"); blockingQueue2.offer("任务3"); boolean offer = blockingQueue2.offer("任务4", 2000); System.out.println(offer); } catch (InterruptedException e) { throw new RuntimeException(e); } } private final E[] array; private int head; private int tail; /* * 由于 pool和offer会同时操作size变量, * 所以size应该被声明为原子变量 * */ private AtomicInteger size = new AtomicInteger(); private ReentrantLock taillock = new ReentrantLock(); private ReentrantLock headlock = new ReentrantLock(); /* * headlock和headWaits必须配合使用否则会报错 * */ private Condition tailWaits = taillock.newCondition(); private Condition headWaits = headlock.newCondition(); public BlockingQueue2(int capacity) { this.array = (E[]) new Object[capacity]; } @Override public void offer(E e) throws InterruptedException { taillock.lockInterruptibly(); try { //防止虚假唤醒 while (isFull()) { tailWaits.await(); } array[tail] = e; if (++tail == array.length) { tail = 0; } size.addAndGet(1); //唤醒等待非空的线程, headWaits.signal(); } finally { taillock.unlock(); } } /* * 优化后的offer,设置等待时间,超出则抛出异常 * */ @Override public boolean offer(E e, long timeout) throws InterruptedException { //新增元素钱的个数,为0既说明是第一个线程 int c; taillock.lockInterruptibly(); try { //等待timeout纳秒抛出异常 long ns = TimeUnit.MICROSECONDS.toNanos(timeout); while (isFull()) { if (ns <= 0) { return false; } ns = tailWaits.awaitNanos(ns); } array[tail] = e; if (++tail == array.length) { tail = 0; } c = size.getAndIncrement(); // 如果c+1<arr.length说明还没有满,那么级联唤醒 if(c+1< array.length){ tailWaits.signal(); } } finally { taillock.unlock(); } try { //唤醒等待非空的线程,必须配合headlocal使用 headlock.lock(); if (c == 0) { //第一个线程 headWaits.signal(); } headlock.unlock(); } catch (Exception exception) { throw exception; } return true; } @Override public E pool() throws InterruptedException { E e; //取走钱的元素个数 int c; headlock.lockInterruptibly(); try { while (isFull()) { headWaits.await(); } e = array[head]; array[head] = null; if (++head == array.length) { head = 0; } c = size.getAndDecrement(); if (c > 1) { // 说明还有其他元素,唤醒下一个 headWaits.signal(); } } finally { headlock.unlock(); } //唤醒等待不满的线程,tailWaits闭合和tailLock配合使用 try { taillock.unlock(); //唤醒减少之前队列是满的那个线程 if (c == array.length) { tailWaits.signal(); } taillock.unlock(); } catch (Exception exception) { throw exception; } return e; } public boolean isFull() { return size.get() == array.length; } public boolean isEmpty() { return size.get() == 0; } }