Java并发编程(六)《并发容器和框架》

本文深入解析了ConcurrentHashMap的工作原理,包括其数据结构、初始化过程、get与put操作细节等。此外,还介绍了多种阻塞队列的特点及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java并发编程(六)《并发容器和框架》

@(并发)

6.1 map死循环

在多线程的情况下,map的put操作会形成循环链表,导致get操作死循环(Infinite Loop),导致Cpu利用率接近100%

6.2 ConcurrentHashMap(分段锁线程安全map)

6.2.1. 数据结构

(1). 结构

Alt text

(2). 类图关系

Alt text

  • ConcurrentHashMap 组合了Segment数组
  • Segment 组合了HashEntery数组

(3). Segment分段锁

static final class Segment<K,V> extends ReentrantLock implements Serializable {
        /*
         * 当前每个Segment里实际包含HashEntry元素的数量
         * 变量申明volatile ,它的写立马对后续的读可见
         */
        transient volatile int count;

        /*
         * 当Segment进行put、clear、remove操作时,modCount就+1
         * 用来标记Segment容器是否变化
         */
        transient int modCount;

        /*
         * Segment容器中包含HashEntry的元素数量的阈值
         * 当容器中元素数量超过threshold时,table将rehash重新散列
         * 一般threshold=loadFactor * 容器容量(容器容量不等于包含HashEntry实际数量)
         */
        transient int threshold;

        /*
         * Segment容器中包含HashEntry数组
         * 变量申明volatile ,它的写立马对后续的读可见        
         */
        transient volatile HashEntry<K,V>[] table;

        /*
         * 负载因子,通常等于0.75,最优
         * 负载因子较小时,降低了冲突的可能性,链表长度减少,加快查询速度,但是所占空间加大,频繁rehash性能会降低; 负载因子较大时,元素分布比较紧凑,容易导致hash冲突,链表加长,访问更新速度较慢,但是空间利用率高。        
         */
        final float loadFactor;
}

注意:
1.Segment容器容量 : 表示HashEntry的数组的大小.
2.Segment的阈值:表示HashEntry的数组的大小 * 负载因子.
3.Segment的元素数量:HashEntry的数组放入的实际元素数量.
4.Segment实际是可重入锁ReentrantLock,具有锁的特性 .

(4). 实际存放元素的HashEntry数组

static final class HashEntry<K,V> {

    /*
     * 放入map中元素的key, 声明final, key一旦初始化确定不能更改    
     */
    final K key;
    /*
     * 放入map中元素的key的hash值   
     */
    final int hash;
    /*
     * 放入map中元素的value    
     */
    volatile V value;
    // 当key的hash冲突时(hash值相同),通过next组成链表,此时查询速度较慢(相对于数组而言)
    final HashEntry<K,V> next; 
}

6.2.2. 初始化

/**
 * 初始化ConcurrentHashMap
 * (1) ConcurrentHashMap初始化容量:DEFAULT_INITIAL_CAPACITY=16
 * (2) 默认负载因子:DEFAULT_LOAD_FACTOR=0.75
 * (3) 默认并发度(同时可16个线程进行修改操作):DEFAULT_CONCURRENCY_LEVEL=16
 */
public ConcurrentHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}

/**
 * 1.初始化用于定位Segment锁位置的散列运算中的segmentShift,segmentMask(掩码)
 * 2.ConcurrentHashMap中包含的Segment数组,大小为ssize,同时初始化每个Segment中HashEntry数组
 * 3.通过以上两步,默认情况下初始化成:
 *   包含有16个Segment分段锁,每个Segment有1个HashEntry的ConcurrentHashMap
 */
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
        throw new IllegalArgumentException();

    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;

    //求出用于定位参与散列运算的位数sshift 
    int sshift = 0;
    int ssize = 1;
    //求出初始化Segment的长度ssize ,即并发度
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    segmentShift = 32 - sshift;
    //求出参与散列运算的的掩码=散列槽的长度-1
    segmentMask = ssize - 1;
    //初始化ConcurrentHashMap中包含的Segment数组,大小为ssize 
    this.segments = Segment.newArray(ssize);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
   /**
    * 计算每个Segment中容纳HashEntry数组的大小
    * 规则:将Map设定的初始容量initialCapacity除以Segment分段锁的数量,
    * 求出每个Segment应该容纳多少HashEntry
    */
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = 1;
    while (cap < c)
        cap <<= 1;
    //初始化每个Segment中HashEntry数组
    for (int i = 0; i < this.segments.length; ++i)
        this.segments[i] = new Segment<K,V>(cap, loadFactor);
}

6.2.4. 定位Segment

/**
 * 通过放入map中元素的key的hashcode再hash的值,取出高4位,同segmentMask掩码15进行与运算(相当于取摸),
 * 从而定位到具体的Segment槽slot位置
 */
final Segment<K,V> segmentFor(int hash) {
    return segments[(hash >>> segmentShift) & segmentMask];
}

private static int hash(int h) {
    // Spread bits to regularize both segment and index locations,
    // using variant of single-word Wang/Jenkins hash.
    h += (h <<  15) ^ 0xffffcd7d;
    h ^= (h >>> 10);
    h += (h <<   3);
    h ^= (h >>>  6);
    h += (h <<   2) + (h << 14);
    return h ^ (h >>> 16);
}

6.2.5. get 操作

6.2.5.1 get操作的高效

1. get操作不需要加锁。
2. 用volatile替代锁。
3. 将map中使用到共享变量统计Segment大小的count,以及存储值的HashEntry[]数组声明成volatile,使他们能在线程之间保持可见性,能多个线程同时读。
4. 之所以不会读到旧的值,这是由java内存模型的happen before原则决定的,对volatile字段的写先于读操作,即get操作总能拿到最新的值.
5.共享变量如下:

//The number of elements in this segment's region.
transient volatile int count;

// The per-segment table.
transient volatile HashEntry<K,V>[] table;

// HashEntry's value.
volatile V value;

6.2.5.2 key hash散列定位到具体的Segment桶

public V get(Object key) {
    int hash = hash(key.hashCode());
    return segmentFor(hash).get(key, hash);
}

6.2.5.3 从桶里取出数据

V get(Object key, int hash) {
   /**
    * count表示:Segment桶里存储的HashEntry数量;
    * 申明volatile,读取的时候直接从主存取,写的时候直接写到主存里
    */
    if (count != 0) { // read-volatile
        //通过hash值找到对应的HashEntry链表的第一个元素
        HashEntry<K,V> e = getFirst(hash);
        //遍历HashEntry e的链表
        while (e != null) {
            //当hash以及key对应的值相等,即返回value
            if (e.hash == hash && key.equals(e.key)) {
                V v = e.value;
                if (v != null)
                    return v;
                return readValueUnderLock(e); // recheck
            }
            e = e.next;
        }
    }
    return null;
}

/**
 * 通过hash值找到对应的HashEntry链表的第一个元素
 */
HashEntry<K,V> getFirst(int hash) {
    //table(read-volatile)
    HashEntry<K,V>[] tab = table;
    //对table长度取摸,定位的存储位置
    return tab[hash & (tab.length - 1)];
}

6.2.6. put 操作

6.2.6.1 定位Segment,要将元素put到那个Segment桶里

public V put(K key, V value) {
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key.hashCode());
    //定位Segmnet,将元素放入其中
    return segmentFor(hash).put(key, hash, value, false);
}

6.2.6.2 将元素加入Segment的HashEntry数组中

/*
 *将元素put到对应的Segment的HashEntry[]数组内
 */
V put(K key, int hash, V value, boolean onlyIfAbsent) {
     //put要对共享变量操作,这里加独占锁,只允许一个线程进入,保证线程安全
     lock();
     try {
         //获取此时HashEntry[]容纳的元素个数
         int c = count;
         //HashEntry[]大小是否超过阈值threshold,若超过则Hash Table扩容
         if (c++ > threshold) // ensure capacity
             rehash();
         HashEntry<K,V>[] tab = table;
         int index = hash & (tab.length - 1);
         //通过放入元素的hash值与HashEntry[]长度取摸,定位到具体的HashEntry链表,取第一个元素
         HashEntry<K,V> first = tab[index];
         HashEntry<K,V> e = first;
         //遍历定位到具体的HashEntry链表,看put元素的key是否已经存在
         while (e != null && (e.hash != hash || !key.equals(e.key)))
             e = e.next;
         V oldValue;
         //若put元素的key值存在,则新值替换旧值
         if (e != null) {
             oldValue = e.value;
             if (!onlyIfAbsent)
                 e.value = value;
         }
         //若put元素的key值不存在,将put的元素构成新HashEntry加入到链表头部,Segment操作变更modCount++,统计HashEntry[]元素个数count
         else {
             oldValue = null;
             ++modCount;
             tab[index] = new HashEntry<K,V>(key, hash, first, value);
             count = c; // write-volatile
         }
         return oldValue;
     } finally {
         //最后释放独占锁,唤醒同步队列中等待的线程
         unlock();
     }
 }

6.2.6.3 如何扩容rehash()

6.2.7. size 和 isEmpty操作

(1) 高并发下,判断CurrentHashMap是否为空,isEmpty的效率比size()==0效率高很多.
(2) size方法:先尝试2次不锁住Segment的方式,统计每个Segment的count大小,若两次统计的过程中某Segment的modCount发生变化,则将所有的Segment全部锁住,统计count大小。

public int size() {
    final Segment<K,V>[] segments = this.segments;
    long sum = 0;
    long check = 0;
    int[] mc = new int[segments.length];
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
        check = 0;
        sum = 0;
        int mcsum = 0;
       /**
        *统计每个segmnet的count,相加
        */
        for (int i = 0; i < segments.length; ++i) {
            sum += segments[i].count;
            mcsum += mc[i] = segments[i].modCount;
        }
        /**
        *看每个segmnet的modCount是否变化,若变化重新统计
        */
        if (mcsum != 0) {
            for (int i = 0; i < segments.length; ++i) {
                check += segments[i].count;
                if (mc[i] != segments[i].modCount) {
                    check = -1; // force retry
                    break;
                }
            }
        }
        if (check == sum)
            break;
    }
    if (check != sum) { // Resort to locking all segments
        sum = 0;
       /**
        *将segment全部锁住
        */
        for (int i = 0; i < segments.length; ++i)
            segments[i].lock();
        for (int i = 0; i < segments.length; ++i)
            sum += segments[i].count;
        for (int i = 0; i < segments.length; ++i)
            segments[i].unlock();
    }
    if (sum > Integer.MAX_VALUE)
        return Integer.MAX_VALUE;
    else
        return (int)sum;
}

(3) isEmpty方法:判断ConcurrentHashMap是否为空,看其中每个segment的count是否都为0,若都为0了则再次校验每个segment的count是否都为0,每个segment的modCount是否有变化,若都没变则map为空.

public boolean isEmpty() {
    final Segment<K,V>[] segments = this.segments;
    /*
     * We keep track of per-segment modCounts to avoid ABA
     * problems in which an element in one segment was added and
     * in another removed during traversal, in which case the
     * table was never actually empty at any point. Note the
     * similar use of modCounts in the size() and containsValue()
     * methods, which are the only other methods also susceptible
     * to ABA problems.
     */
    int[] mc = new int[segments.length];
    int mcsum = 0;
    for (int i = 0; i < segments.length; ++i) {
        if (segments[i].count != 0)
            return false;
        else
            mcsum += mc[i] = segments[i].modCount;
    }
    // If mcsum happens to be zero, then we know we got a snapshot
    // before any modifications at all were made.  This is
    // probably common enough to bother tracking.
    if (mcsum != 0) {
        for (int i = 0; i < segments.length; ++i) {
            if (segments[i].count != 0 ||
                mc[i] != segments[i].modCount)
                return false;
        }
    }
    return true;
}

6.3 ConcurrentLinkedQueue(非阻塞线程安全队列)

6.3.1 offer入队(cas)

6.3.2 poll出队(cas)

6.4 .阻塞线程安全队列

6.4.1 ArrayBlockingQueue(数组有界阻塞队列)

(1) 数据结构

/**
 * 存放任务的循环数组
 */
final Object[] items;

/**
 * 队列头部下标,for next take, poll, peek or remove
 */
int takeIndex;

/**
 * 添加新元素的下标,队列尾部下标的next,for next put, offer or add
 */
int putIndex;

/**
 *阻塞队列中当前容纳任务的数量
 */
int count;

/**
 * 独占锁,保护队列,让其线程安全
 */
final ReentrantLock lock;

/**
 * 队列同步器上的等待队列,等待队列不满
 */
private final Condition notFull;

/**
 * 队列同步器上的等待队列,等待队列不空
 */
private final Condition notEmpty;

(2) offer()入队

offer操作:

  • 第一个线程进来,加锁锁住整个队列,为了保证队列线程安全,方法只允许同一时间一个线程访问;
  • 若队列任务数量count已满,直接返回false;若不满,在队列尾部添加元素并且唤醒notEmpty等待队列头节点去竞争消费阻塞队列
  • 线程释放独占锁。

代码分析:

//队尾入队
public boolean offer(E e) {
    //1.检查入队元素是否为空,若为空会抛出空指针异常
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //2.加锁,保证只有一个线程进入,其他线程在同步队列上等待
    lock.lock();
    try {
        //3.队列已满返回false,否则出队
        if (count == items.length)
            return false;
        else {
            insert(e);
            return true;
        }
    } finally {
        //4.final块释放锁
        lock.unlock();
    }
}
//入队操作具体类
private void insert(E x) {
    //在循环数组尾部的next下标putIndex那,放入新元素
    items[putIndex] = x;
    //修改putIndex+1,若已到数组最后一个元素,putIndex重0开始循环
    putIndex = inc(putIndex);
    //元素容量+1
    ++count;
    //此时队列中有新元素了,不在是空的了,通知notEmpty等待队列中的head节点继续消费了
    notEmpty.signal();
}
//Circularly decrement i
final int inc(int i) {
    return (++i == items.length) ? 0 : i;
}

(3) 超时offer()入队

当队队列满时无法入队,一般直接返回false入队失败,但是超时等待offer方法允许线程在(notFull Condition)上等待一段时间,若在规定的等待时间内,线程被poll出队操作唤醒并且能够得到锁,则可以继续入队。

代码分析:

//超时等待入队
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            //等待nanos纳秒
            nanos = notFull.awaitNanos(nanos);
        }
        insert(e);
        return true;
    } finally {
        lock.unlock();
    }
}

(4) poll()出队

直接上代码:

//出队    
public E poll() {
    //1.加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        //2.若队列为空,直接返回空,否则返回队列头部元素
        return (count == 0) ? null : extract();
    } finally {
        //3.释放元素
        lock.unlock();
    }
}
//真实出队操作
private E extract() {
    final Object[] items = this.items;
    //将队头takeIndex数组元素出队
    E x = this.<E>cast(items[takeIndex]);
    //help-gc
    items[takeIndex] = null;
    //takeIndex+1,若已到数组最后一个元素,takeIndex重0开始循环
    takeIndex = inc(takeIndex);
    --count;
        //此时队列出队后,元素不满了,要通知notFull condition继续生产元素了
    notFull.signal();
    return x;
}
final int inc(int i) {
    return (++i == items.length) ? 0 : i;
}

(5) poll()超时出队

若队列此时没有元素,可以在notEmpt condition上等待一段时间,若在规定的时间内有生产者线程signal,并且能够竞争到锁,允许其继续出队。

代码分析:

//超时出队
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0) {
            if (nanos <= 0)
                return null;
            //等待时间纳秒级别
            nanos = notEmpty.awaitNanos(nanos);
        }
        return extract();
    } finally {
        lock.unlock();
    }
}

(6) put()和take()

  • put操作同样是入队操作,当队列满时无法入队,线程会在notFull condition上一直等待阻塞住,直到出队线程通知。
  • take操作同样是出队操作,当对列为空时无法出队,线程会在notEmpty condition一直等待阻塞住,直到入队线程通知。

代码分析:

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            //队列满,线程等待
            notFull.await();
        insert(e);
    } finally {
        lock.unlock();
    }
}
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        //队列空,线程等待
        while (count == 0)
            notEmpty.await();
        return extract();
    } finally {
        lock.unlock();
    }
}

6.4.2 LinkedBlockingQueue(链表有界阻塞队列)

(1) 数据结构

public class LinkedBlockingQueue<E>{
    //队列容量
    private final int capacity;

    //此时队列元素数量,因为入队出队可以并发进行,统计数量要用原子类型
    private final AtomicInteger count = new AtomicInteger(0);

    //队头,实际头部的prev节点
    private transient Node<E> head; 

    //队尾,实际尾部的next节点
    private transient Node<E> last;

    /** 入队持有锁,put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();

    /** 入队时的等待队列 */
    private final Condition notFull = putLock.newCondition();

    /** 出队持有锁,take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** 出队时的等待队列 */
    private final Condition notEmpty = takeLock.newCondition();

    /**
    * 队列节点对象
    */
   static class Node<E> {
       //节点存放的具体元素
       E item;

       /**
        * One of:
        * - the real successor Node
        * - this Node, meaning the successor is head.next
        * - null, meaning there is no successor (this is the last node)
        */
        //当前节点的next
       Node<E> next;

       Node(E x) { item = x; }
   }
}

(1) offer()

/**
 *入队操作,若队列已满则返回false,否则入队
 *入队操作,只是对队尾节点last共享变量的操作,所以只要持有putLock就好
 */
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    //1.获取队列元素数量
    final AtomicInteger count = this.count;
    //2.若队列满,直接返回false,入队失败
    if (count.get() == capacity)
        return false;
    int c = -1;
    //3.队列未满,可以入队了,将添加元素包装成节点元素node
    Node<E> node = new Node(e);
    final ReentrantLock putLock = this.putLock;
    //4.入队,将队列的尾节点last->next=当前node,所以要持有putLock
    putLock.lock();
    try {
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        //5.释放putLoack
        putLock.unlock();
    }
    if (c == 0)
        //6.若添加之前队列元素为空,此时需要通知消费者,即notEmpty等待队列,可以poll,take了
        signalNotEmpty();
    return c >= 0;
}

//入队真实操作,将新节点加到队列尾部,last指向当前节点
private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    last = last.next = node;
}

(2) 超时offer()

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    if (e == null) throw new NullPointerException();
    long nanos = unit.toNanos(timeout);
    int c = -1;
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        //当队列满时,线程可以等待一段时间,再次判断队列是否已满,尝试入队
        while (count.get() == capacity) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(new Node<E>(e));
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}

(3) poll()

/**
 *出队操作,若队列为空则返回null,否则可以出队
 *出队操作,只是对队头节点head共享变量的操作,所以只要持有takeLock就好
 */
public E poll() {
    final AtomicInteger count = this.count;
    //1.获取队列元素个数,若等于0,出队操作直接返回空
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    //2.尝试持有takeLock
    takeLock.lock();
    try {
        //3.再次判断元素个数>0,出队
        if (count.get() > 0) {
            x = dequeue();
            //元素个数线程安全的+1
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        //4.释放锁
        takeLock.unlock();
    }
    if (c == capacity)
        //5.若之前队列已满,这里出队了,此时队列不满了,要通知到notFull等待队列
        signalNotFull();
    return x;
}
//真实出队操作 
private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    //获取队头
    Node<E> h = head;
    //实际出队的节点是head的next
    Node<E> first = h.next;
    h.next = h; // 将之前的head节点断开引用,help GC
    head = first;
    E x = first.item;
    first.item = null;
    return x;
}

(4) 超时poll()

(5) put()和take()

  • put操作同样是入队操作,当队列满时无法入队,线程会在notFull condition上一直等待阻塞住,直到出队线程通知。
  • take操作同样是出队操作,当对列为空时无法出队,线程会在notEmpty condition一直等待阻塞住,直到入队线程通知。

(6) remove(Object o)和boolean contains(Object o)

注意remove某个元素或者是否包含某个元素的时候,因为不知道元素是队头或者队尾,还是中间元素,所以要对putLock和takeLock都要加锁,才能进行操作。

6.4.3 PriorityBlockingQueue(优先级无界阻塞队列)

内部维护一个有顺序的object数组,默认按优先级升序排序(插入排序),出队直接取第一个元素。

6.4.4 DelayQueue(延时优先级无界阻塞队列)

(1) 数据结构

public class DelayQueue<E extends Delayed>{

    /** 入队出队都需要持有这把锁 */
    private final ReentrantLock lock= new ReentrantLock();

    /** 用于根据delay时间排序的优先级队列,每次poll操作取的头结点 */
    private final PriorityQueue<E> q = new PriorityQueue<E>();

    /*优化阻塞通知的线程元素leader(Leader-Follower模式), 
     *leader线程不为空,表示有线程在消费头结点,其他线程follower直接等待await。
     */
    private Thread leader = null;

    /*用于实现阻塞和通知的Condition对象 
     *等待的场景:队列为空、leader线程不为空(代表有线程在消费头结点)、头结点过期时间没到
     *通知的场景:offer时新节点成为头节点、leader线程消费完头节点
     */
    private final Condition available = lock.newCondition();
}

//延时队列中的元素必须继承这个Delayed,实现getDelay,Comparable方法
public interface Delayed extends Comparable<Delayed> {
        //用于获取元素的延时时间点,精确到纳秒级别
    long getDelay(TimeUnit unit);
}

(2) offer()

  1. 加锁
  2. 将元素添加到优先级队列中
  3. 若添加的元素经过优先级排序后排在队首设置leader为null,通知等待在avaliable上的头节点线程消费。
  4. 释放锁
public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        q.offer(e);
        if (q.peek() == e) {
            leader = null;
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

(2) poll()

  1. 加锁
  2. 查看优先级队列的头部。
  3. 若头节点为空或者deplay延时时间没有到,则返回空,否则出队。
  4. 释放锁
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E first = q.peek();
        if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
        lock.unlock();
    }
}

(3) put()

(4) take()

  1. 加锁
  2. 查看队列的头节点,若为空则线程进入等待。
  3. 获取头节点的delay,若delay到期了,则出队成功。
  4. 判断leader 线程是否为空,若不为空表示已经有线程在等待消费了,这里直接阻塞线程。
  5. leader为空,获取当前线程,设置leader线程==当前线程,当前线程等待delay到期,在finally块中释放leader元素的引用.
  6. 循环执行2-5
  7. 如果leader为空并且优先级队列不为空的情况下(判断还有没有其他后续节点),调用signal通知其他的线程.
  8. 释放锁。
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            if (first == null)
                available.await();
            else {
                long delay = first.getDelay(TimeUnit.NANOSECONDS);
                if (delay <= 0)
                    return q.poll();
                else if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

(5) 实际运用

  1. 缓存过期健清除:
    将cache中的设置了过期时间的元素放入一个DelayQueue中,后台有一个线程不停的poll头节点,将最早过期的元素从缓存中清除掉。

6.4.5 SynchronousQueue(不存储元素的阻塞队列)

6.4.6 LinkedTransferQueue(链表无界阻塞队列)

6.4.7 LinkedBlockingDueue(链表双向阻塞队列)

6.4.8 阻塞队列原理

同步队列器等待通知机制,阻塞队列和等待队列交互的结果。

6.4.9 Fork/Join

原理:工作窃取算法(双端队列),当一个线程工作结束,不会销毁而是取其他工作队列的尾部任务来继续执行。最大限度的利用线程,代码太复杂以后在研究。

6.5 Queue队列

基本操作

public interface Queue<E> extends Collection<E> {
    /**
     * 入队,如果队列已满,则抛出异常
     */
    boolean add(E e);
    /**
     * 入队,如果队列已满,则返回false,不抛异常
     */
    boolean offer(E e);

    /**
     * 出队,移除并返回队列头部的元素,如果队列为空,则抛出异常
     */
    E remove();
    /**
     * 出队,移除并返回队列头部的元素,如果队列为空,则返回false,不抛异常
     */
    E poll();

    /**
     * 出队,不移除只返回队列头部的元素,如果队列为空,则抛出异常
     */
    E element();
    /**
     * 出队,不移除只返回队列头部的元素,如果队列为空,则返回false,不抛异常
     */
    E peek();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值