java并发编程 ArrayBlockingQueue详解

文章目录

  • 前言
  • 1 ArrayBlockingQueue是什么
  • 2 核心属性详解
  • 3 核心方法详解
    • 3.1 add(E e)
    • 3.2 offer(E e)
    • 3.3 put(E e)
    • 3.4 take()
    • 3.5 poll()
    • 3.6 peek()
    • 3.7 size()
    • 3.8 remove(Object o)
    • 3.9 contains(Object o)
    • 3.10 drainTo(Collection c, int maxElements)
  • 4 总结

前言

学习ArrayBlockingQueue需要掌握ReentrantLock 原理,或者了解其使用也行。
java 并发编程系列文章目录

1 ArrayBlockingQueue是什么

按照类注释写的:ArrayBlockingQueue是一个 “由数组支持的有界阻塞队列”,该队列对元素FIFO(先进先出)进行排序。队列的头是在队列中停留时间最长的元素。队列的尾部是在队列中停留时间最短的元素。新元素被插入到队列的尾部,队列检索操作获得队列头部的元素。

2 核心属性详解

通过核心属性就可以看出,通过ReentrantLock 实现线程安全的对数组items进行操作

	//数据存储的数组
	final Object[] items;

    //把元素取出的下标index
    int takeIndex;

    //把元素放入的下标index
    int putIndex;

    //数组中元素的个数统计字段
    int count;

    //保证原子性的可重入锁
    final ReentrantLock lock;

    //两个等待条件
    private final Condition notEmpty;
    private final Condition notFull;

3 核心方法详解

3.1 add(E e)

    public boolean add(E e) {
        return super.add(e);
    }
    public boolean add(E e) {
	    if (offer(e))
	        return true;
	    else
	    	//如果offer失败,返回的fasle add方法是抛出异常的
	        throw new IllegalStateException("Queue full");
	}

3.2 offer(E e)

offer方法会有个boolean值返回,代表添加成功或者失败,但是add方法,如果添加失败会抛出异常

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
    	// 上面已经说了,ArrayBlockingQueue是有界阻塞队列,如果当前items的元素数量count已经等于数组的长度了,则添加失败
        if (count == items.length)
            return false;
        else {
        	//
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
private void enqueue(E x) {
    final Object[] items = this.items;
    //数组中放入元素
    items[putIndex] = x;
    //防止数组下标越界
    if (++putIndex == items.length)
        putIndex = 0;
    //数组元素的数量+1
    count++;
    //唤醒等待获取元素的线程从数组中获取数据
    notEmpty.signal();
}

3.3 put(E e)

put方法也是添加一个元素,区别offer在于如果当前items数组已经放满了,他会阻塞等待,等待别的线程从数组中把元素取走,然后唤醒它去添加元素。

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
        	//items数组已经放满 会阻塞在这等待
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

3.4 take()

获取一个元素,如果没有元素会阻塞等待直到有元素时获取并返回

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    //加锁保证取元素的原子性
    lock.lockInterruptibly();
    try {
        while (count == 0)
        	//当items数组为空时,会阻塞等待直到items数组中被放入元素,会notEmpty.signal()
            notEmpty.await();
        //从items获取一个元素返回
        return dequeue();
    } finally {
        lock.unlock();
    }
}
//从items 获取一个元素
private E dequeue() {
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    //获取元素
    E x = (E) items[takeIndex];
    items[takeIndex] = null;
    if (++takeIndex == items.length)
    //维护takeindex
        takeIndex = 0;
    //元素数量-1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    //唤醒如put方法添加数据但元素已满阻塞在那的线程进行添加元素
    notFull.signal();
    return x;
}

3.5 poll()

和take方法相比会返回null 而不是阻塞

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//如果没有元素则返回null 不是阻塞
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

3.6 peek()

public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//返回数组中的元素,和dequeue 相比 他没有把数组中的元素设置成null 只是拿到这个元素
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        lock.unlock();
    }
}
final E itemAt(int i) {
    return (E) items[i];
}

3.7 size()

ArrayBlockingQueue 的size方法也是强一致性的

    public int size() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        	//返回当前items中元素的数量
            return count;
        } finally {
            lock.unlock();
        }
    }

3.8 remove(Object o)

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count > 0) {
            final int putIndex = this.putIndex;
            int i = takeIndex;
            do {
                if (o.equals(items[i])) {
                	//移除当前元素,并唤醒添加元素阻塞的线程
                    removeAt(i);
                    return true;
                }
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);//就是遍历所有的元素进行比较
        }
        return false;
    } finally {
        lock.unlock();
    }
}

3.9 contains(Object o)

和remove方法一样,只不过找到元素,会返回true, remove会移除元素

public boolean contains(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count > 0) {
            final int putIndex = this.putIndex;
            int i = takeIndex;
            do {
                if (o.equals(items[i]))
                    return true;
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);
        }
        return false;
    } finally {
        lock.unlock();
    }
}

3.10 drainTo(Collection c, int maxElements)

把多少个元素转移到指定的c集合中去。

public int drainTo(Collection<? super E> c, int maxElements) {
    checkNotNull(c);
    if (c == this)
        throw new IllegalArgumentException();
    if (maxElements <= 0)
        return 0;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    	//取最小值,可能当前集合中都没有这么多元素
        int n = Math.min(maxElements, count);
        int take = takeIndex;
        int i = 0;
        try {
            while (i < n) {
            	//元素取出 添加到c集合中,自身items元素移除
                @SuppressWarnings("unchecked")
                E x = (E) items[take];
                c.add(x);
                items[take] = null;
                if (++take == items.length)
                    take = 0;
                i++;
            }
            return n;
        } finally {
            //... notFull.signal();
        }
    } finally {
        lock.unlock();
    }
}

4 总结

  1. 添加和获取方法都分为阻塞式和非阻塞式
  2. 通过ReentrantLock确保Object[] items的线程安全问题
  3. 因为是数组,保证fifo的时候需要知道当前获取元素的下标和放入元素的下标,takeindex和putindex你追我赶的局面,因为是数组,当到达数组末尾的时候,需要重新到头。如果只用takeindex和putindex大小进行比较的话是不知道集合中是否已经满了,所以引入count才能知道是否满了

你可能感兴趣的:(java并发编程,java)