并发编程-Collections &&Queue

BlockingQueue

队列:数据存储结构,由链表或数组实现,FIFO。

  • ArrayBlockingQueue 由数组支持的有界队列
  • LinkedBlockingQueue 由链接节点支持的可选有界队列
  • PriorityBlockingQueue 由优先级堆支持的无界优先级队列
  • DelayQueue 由优先级堆支持的、基于时间的调度队列

BlockingQueue的核心是ReentrantLock 和 AQS中的Condition。

ArrayBlockingQueue

基于数组,不可扩容。

底层原理

ArrayBlockingQueue初始化需要传递两个参数:capacity表示容量;fair表示是否为公平锁,默使用非公平锁。

	public ArrayBlockingQueue(int capacity, boolean fair) {
     
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

ArrayBlockingQueue初始化时,会创建两个条件队列:

  • notEmpty-取元素时判断是否为空队列;
  • notFull-放入元素时判断队列是否已满。
	public void put(E e) throws InterruptedException {
     
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
     
            while (count == items.length)
            	//实现:java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#await()
                notFull.await();
            enqueue(e);
        } finally {
     
            lock.unlock();
        }
    }

元素放入流程:

  1. 获取独占锁

  2. 判断队列是否已满

    1. 已满:当前数量等于队列长度,将线程尝试放入条件等待队列中。以下是await()方法源码和部分自定义注释。注意:位于条件队列中的节点不会被唤醒,只有转移到CLH队列中的节点才会被唤醒。
    		public final void await() throws InterruptedException {
           
                if (Thread.interrupted())
                    throw new InterruptedException();
                //将当前节点假如等待队列中,并移除状态为CANCEL状态的节点
                Node node = addConditionWaiter();
                //一次性释放所有锁,释放失败的线程节点状态会变成CANCEL状态
                int savedState = fullyRelease(node);
                int interruptMode = 0;
                //判断节点是否在同步等待(CLH)队列中
                while (!isOnSyncQueue(node)) {
           
                	//阻塞当前线程
                    LockSupport.park(this);
                    //是因为什么原因导致中断的,如果节点加入条件队列前就被中断了,则返回1或-1。
                    //checkInterruptWhileWaiting:将当前条件队列的节点状态设置为0并转移到CLH队列中。如果转移失败了,线程会让度资源,一直到成功转移到CLH队列为止。
                    //只有加入到CLH队列中才有可能被唤醒,且拥有竞争锁的权利。
                    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                        break;
                }
                //尝试获取锁
                if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                    interruptMode = REINTERRUPT;
                //程序走到这里说明已经获取到了独占锁
                if (node.nextWaiter != null) // clean up if cancelled
                	//删除cancel状态的节点
                    unlinkCancelledWaiters();
                //处理不同情况下被中断的线程
                if (interruptMode != 0)
                    reportInterruptAfterWait(interruptMode);
            }
    
    1. 未满时,放入条件数组元素中。

HashMap

JDK1.7HashMap 死锁(成环)

JDK1.7HashMap(数组+链表) 在多线程场景下,多线程扩容期间原链表转移到新链表中时,造成了位置互换,造成链表成环,存在节点位置互换指针引用的问题,有可能导致死锁。

JDK1.8HashMap 优化

当链表长度>=8时,会将链表转成红黑树。
put步骤:

  1. 遍历数组,得到不为空的下一个元素并赋值给一个新的元素(Node e),此时还会将原来的元素置为null。
  2. 对新链表e进行长度判断,如果链表只有一个节点,对元素e重新hash,得出一个新的数组下标,并丢入到新的数组中。
  3. 如果链表不止一个节点,首先会判断是不是红黑树结构。
  4. 如果不是红黑树结构,会进行高低位索引存储。

ConcurrentHashMap

  • JDK1.7 中用的segment实现,segment继承自ReentrantLock,使用分段锁实现线程安全。
  • JDK1.8 中使用Node实现,对关键代码块使用Synchronized关键字实现线程安全。

你可能感兴趣的:(Java架构师沿途风景,Java,队列,集合)