使用最多的场景就是线程池

    ArrayBlockingQueue
    LinkedBlockingQueue
    SynchonousQueue

    jdk提供的四种线程池
    定长 fix LinkedBlockingQueue
    单一 single LinkedBlockingQueue
    变长 cache SynchonousQueue

    数组实现:维护一个数组
    链表实现:维护一个内部类Node

    ArrayBlockingQueue
    底层基于数组,具有阻塞的功能,FIFO,生产消费者模式
    Queue提供的api
    image.png

    add,offer 插入元素
    remove,poll 取元素
    element,peek 查询元素

    抛异常 阻塞 超时 返回特殊值(一般情况都是boolean,null)

    插入元素(遇到慢的情况) add put offer offer

    取元素(删除取出的元素) remove take poll poll

    查询元素(对队列没有影响) element peek

    ArrayBlockingQueue结构

    items
    存放元素
    takeIndex
    取元素的指针,记录下一次出队的位置
    putIndex
    存元素的指针,记录下一次入队的位置 (指向下一个空位置)
    两个指针可以形成一个环形数组,take追着put跑,当put到数组的最大值的时候会将putIndex指向下标为0的节点,takeIndex也是从下标为0的节点开始处理,这样会重复利用数组的节点,当takeIndex==putIndex说明元素被取空了或者满了
    count
    计算元素个数
    ReentrantLook
    ArrayBlockingQueue中维护了一把锁,入队和出队是相互阻塞的
    Condition
    有两个Condition是通过ReentrantLook来获取的,这里使用到await和signal,lock.newCondition返回的对象await必须要用同一个对象signal唤醒
    notEmpty 出队的对象,说明队列不为空,唤醒的是take
    notFull 入队的对象,说明队列

    image.png

    LinkedBlockingQueue(FIFO)
    容量:默认取得是Integer.MAX_VALUE
    内部使用两把锁,所以使用AtomicInteger来计数保证原子性,出队入队用的不同的锁,互相不阻塞
    属性:

    AtomicInteger count 用来计数
    Node head 头节点,初始化的时候 last = head = new Node(null);来赋值head指向一个空的node节点
    Node last :尾节点,入队的时候 last = last.next = node

    出队的时候从头节点出队,把当前head=head.next, 吧head.item返回

    出队代码:

    1. private E dequeue() {
    2. // assert takeLock.isHeldByCurrentThread();
    3. // assert head.item == null;
    4. Node<E> h = head;
    5. Node<E> first = h.next;
    6. h.next = h; // help GC
    7. head = first;
    8. E x = first.item;
    9. first.item = null;
    10. return x;
    11. }

    入队代码:

    1. private void enqueue(Node<E> node) {
    2. // assert putLock.isHeldByCurrentThread();
    3. // assert last.next == null;
    4. last = last.next = node;
    5. }

    image.png

    SynchronousQueue
    无锁的队列,使用UNSAFE保证线程安全,主要就是cas
    无缓冲队列,不存储元素,用来阻塞线程,必须匹配到有一个入队一个出队两个线程才会同时返回
    一个线程put,一个线程take才会执行,否则就是阻塞
    阻塞的是一组线程,把线程维护起来,有公平和非公平的概念
    通过isData来判断是出队还是入队,要是和阻塞的线程的isData不匹配或者队列为空,那就阻塞

    ConcurrentLinkedQueue
    单向链表
    无锁的,通过CAS来实现线程安全,不用切换线程上下文,但是再并发大的时候会导致线程一直自旋使CPU飙高
    内部维护了一个head和一个tail两个node节点,其中再添加元素的时候tail节点不是每次添加玩都会去移动,是每入队两次tail才移动一次,这样减少了tail的移动次数,tail.next == null 那么tail就是真正的尾节点(如果tail直接指向尾节点的话因为设置tail是CAS操作,性能上不如两次移动一次)

    1. public boolean offer(E e) {
    2. checkNotNull(e);
    3. final Node<E> newNode = new Node<E>(e);
    4. for (Node<E> t = tail, p = t;;) {
    5. Node<E> q = p.next;
    6. if (q == null) {
    7. // p is last node
    8. if (p.casNext(null, newNode)) {
    9. // Successful CAS is the linearization point
    10. // for e to become an element of this queue,
    11. // and for newNode to become "live".
    12. if (p != t) // hop two nodes at a time
    13. // 这里设置移动tail节点,第一次p == t 就不会设置tail节点
    14. casTail(t, newNode); // Failure is OK.
    15. return true;
    16. }
    17. // Lost CAS race to another thread; re-read next
    18. }
    19. else if (p == q) // p.next = p 并发下可能会p.next = p
    20. // We have fallen off list. If tail is unchanged, it
    21. // will also be off-list, in which case we need to
    22. // jump to head, from which all live nodes are always
    23. // reachable. Else the new tail is a better bet.
    24. p = (t != (t = tail)) ? t : head;
    25. else
    26. // Check for tail updates after two hops.
    27. p = (p != t && t != (t = tail)) ? t : q;
    28. }
    29. }

    优先级队列
    PriorityBlockingQueue(线程安全)/priorityQueue(非线程安全)
    内部维护了一个数组,初始容量是11
    小顶堆:
    入队的时候向上浮(入队的时候先把元素放到最后,然后向上浮)
    出队向下沉(出队的时候吧最后一个元素放到出队元素的地方然后向下沉)

    数组的第一个元素为空目的就是快速根据子节点找到父节点的位置 index/2 向下取整 = 父节点下标
    image.png
    可以进行排序

    1. @Test
    2. public void test5(){
    3. int[] arr = {3,2,5,8,1,9};
    4. PriorityQueue<Integer> priorityQueue = new PriorityQueue(arr.length);
    5. for(int i : arr){
    6. priorityQueue.add(i);
    7. }
    8. Iterator iterator = priorityQueue.iterator();
    9. while (iterator.hasNext()){
    10. System.out.print(iterator.next() + ",");
    11. }
    12. // 输出:1,2,5,8,3,9, 迭代器没有顺序
    13. int size = priorityQueue.size();
    14. for (int i = 0; i < size; i++) {
    15. System.out.print(priorityQueue.poll() + ",");
    16. }
    17. // 输出:1,2,3,5,8,9, poll是有顺序的
    18. }

    入队:

    1. public boolean offer(E e) {
    2. if (e == null)
    3. throw new NullPointerException();
    4. final ReentrantLock lock = this.lock;
    5. lock.lock();
    6. int n, cap;
    7. Object[] array;
    8. while ((n = size) >= (cap = (array = queue).length))
    9. // 这里执行扩容的方法,使用的cas,因为已进入方法就吧锁释放了
    10. tryGrow(array, cap);
    11. try {
    12. Comparator<? super E> cmp = comparator;
    13. if (cmp == null)
    14. siftUpComparable(n, e, array);
    15. else
    16. siftUpUsingComparator(n, e, array, cmp);
    17. size = n + 1;
    18. notEmpty.signal();
    19. } finally {
    20. lock.unlock();
    21. }
    22. return true;
    23. }
    24. private void tryGrow(Object[] array, int oldCap) {
    25. // 这里释放锁的原因是不阻塞读
    26. lock.unlock(); // must release and then re-acquire main lock
    27. Object[] newArray = null;
    28. // 只需要一个线程做扩容
    29. if (allocationSpinLock == 0 &&
    30. UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
    31. 0, 1)) {
    32. try {
    33. int newCap = oldCap + ((oldCap < 64) ?
    34. (oldCap + 2) : // grow faster if small
    35. (oldCap >> 1));
    36. if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
    37. int minCap = oldCap + 1;
    38. if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
    39. throw new OutOfMemoryError();
    40. newCap = MAX_ARRAY_SIZE;
    41. }
    42. if (newCap > oldCap && queue == array)
    43. newArray = new Object[newCap];
    44. } finally {
    45. allocationSpinLock = 0;
    46. }
    47. }
    48. // 让扩容后的线程优先拿到资源
    49. if (newArray == null) // back off if another thread is allocating
    50. Thread.yield();
    51. lock.lock();
    52. if (newArray != null && queue == array) {
    53. queue = newArray;
    54. System.arraycopy(array, 0, newArray, 0, oldCap);
    55. }
    56. }

    DelayQueue 延迟队列
    具有阻塞的功能,线程安全

    1. // 创建一个延迟线程池里面是通过它的内部类DelayedWorkQueue来做的延迟功能,但是原理和DelayQueue差不多
    2. ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    3. public ScheduledThreadPoolExecutor(int corePoolSize) {
    4. super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
    5. new DelayedWorkQueue());
    6. }
    1. private final transient ReentrantLock lock = new ReentrantLock();
    2. // 内部调用的就是优先级队列的方法
    3. private final PriorityQueue<E> q = new PriorityQueue<E>();
    4. // 当前是否有线程再排队,用于取元素的时候,当取得时候发现有线程在排队那就不用取了,直接排 //队就好了
    5. private Thread leader = null;
    6. private final Condition available = lock.newCondition();
    1. public boolean offer(E e) {
    2. final ReentrantLock lock = this.lock;
    3. lock.lock();
    4. try {
    5. // 入队直接调用PriorityQueue的offer方法
    6. q.offer(e);
    7. if (q.peek() == e) {
    8. leader = null;
    9. available.signal();
    10. }
    11. return true;
    12. } finally {
    13. lock.unlock();
    14. }
    15. }

    扩展:禁止指令重拍的方式
    volatile,final,sync,lock