Java多线程16:ArrayBlockingQueue的详细介绍及源码分析——学习方腾飞Java并发编程的艺术

ArrayBlockingQueue

ArrayBlockingQueue:
1、数组实现:ArrayBlockingQueue内部其实是一个数组
2、阻塞队列:当资源已经被某个线程占有的时候,当请求访问这个资源的线程被阻塞,必须等到占用资源的线程释放锁,才有机会去竞争锁
3、有界:是指其对应的数组有size,当队列满的时候无法添加,必须等到有其他线程释放位置才可以加入
4、线程安全:内部通过ReentrantLock实现线程安全

看一下ArrayBlockingQueue的源码

还是从增删开始理解ArrayBlockingQueue

1、构造函数

// 初始化ArrayBlockingQueue数组的容量,调用另一个构造函数
public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

/**
这里传入了数组的容量和一个boolean值,其中boolean值表示创建的ReetrantLock是否为
公平锁,true表示公平锁,按请求时间分配,false则非公平锁,效率会高一些,可以去看
lock那一节的讲解
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
            //创建数组
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        //通过Condition来实现更精确的访问
        notEmpty = lock.newCondition();//初始化非空等待队列
        notFull =  lock.newCondition();//初始化非满等待队列 

/**可以给Queue中添加默认集合,注意:这里添加默认集合要加锁,但他并不是为了
起到互斥的作用,而是为了保证可见性。线程T1是实例化ArrayBlockingQueue对象,
T2是对实例化的ArrayBlockingQueue对象做入队操作(要保证T1和T2的执行顺序),
如果不对它进行加锁操作(加锁会保证其可见性,也就是写回主存),T1的集合c有可能
只存在T1线程维护的缓存中,并没有写回主存,T2中实例化的ArrayBlockingQueue维护
的缓存以及主存中并没有集合c,此时就因为可见性造成数据不一致的情况,引发线程安全问题。 
*/
public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            final Object[] items = this.items;
            int i = 0;
            try {
                for (E e : c)
                    // 取出非空元素,遇到null会抛出NullPointerException
                    items[i++] = Objects.requireNonNull(e);
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            // 实际的有效个数
            count = i;
            // 判断下是否满了
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

2、添加add(E e)

// 就是调用offer()方法
public boolean add(E e) {
        if (offer(e))//offer方法由Queue接口定义
            return true;
        else
            throw new IllegalStateException("Queue full");
    }

public boolean offer(E e) { 
  checkNotNull(e);//检查入队元素是否为空 
  final ReentrantLock lock = this.lock; 
  lock.lock();//获得锁
  try { 
    if (count == items.length)//队列满时,直接返回false 
      return false; 
    else { 
      insert(e);//队列未满,插入e
      return true; 
    } 
  } finally {
    lock.unlock();// 手动解锁
  }
}

//insert 
private void insert(E e) { 
  items[putIndex] = x; 
  putIndex = inc(putIndex); 
  ++count; 
  notEmpty.signal();//唤醒非空等待队列中的线程
 }
//inc,判断数组是否满了,否就记录角标,是的话置0
private int inc(int i) { 
  return (++i == items.length) ? 0 : i; 
} 

//特殊的put方法,阻塞插入
public void put(E e) throws InterruptedException { 
  checkNotNull(e);//检查插入元素是否为空 
  final ReentrantLock lock = this.lock; 
  lock.lockInterruptibly();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。 
  try { 
    while (count == items.length) 
      notFull.await();//当队列满时,使非满等待队列休眠 
    insert(e);//此时表示队列非满,插入元素,同时在该方法里唤醒非空等待队列 
  } finally { 
    lock.unlock(); 
  } 
}

删除remove()

public E remove() {
        E x = poll();//调用poll方法
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

//poll,队列中有元素时返回元素,没有元素时返回null 
public E poll() { 
  final ReentrantLock lock = this.lock; 
  lock.lock(); 
  try { 
    return (count == 0) ? null : extract(); //调用extract方法
  } finally { 
    lock.unlock(); 
  } 
}

//extract 
private E extract() { 
  final Object[] items = this.items; 
  E x = this.<E>cast(items[takeIndex]);//移除队首元素,FIFO原则
  items[takeIndex] = null;//将队列数组中的第一个元素置为null,便于GC回收 
  takeIndex = inc(takeIndex); // 判断一下位置
  --count; 
  notFull.signal();//唤醒非满等待队列线程 
  return x; 
}

//特殊的删除take,阻塞删除 
public E take() throws InterruptedException { 
  final ReentrantLock lock = this.lock(); 
  lock.lockInterrupted();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。 
  try { 
    while (count == 0)//队列元素为空 
      notEmpty.await();//非空等待队列休眠 
    return extract();//此时表示队列非空,故删除元素,同时在里唤醒非满等待队列 
  } finally { 
    lock.unlock(); 
  } 
}

来个例子

import java.util.Iterator;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * @author fitz.bai
 */
public class MyThread20 {

    private static Queue<String> queue = new ArrayBlockingQueue<>(20);

    public static void main(String[] args) {

        new MyThread20.MyThread("a").start();
        new MyThread20.MyThread("b").start();
    }

    private static void printAll() {
        String value;
        Iterator iter = queue.iterator();
        while (iter.hasNext()) {
            value = (String) iter.next();
            System.out.print(value + ", ");
        }
        System.out.println();
    }

    private static class MyThread extends Thread {
        MyThread(String name) {
            super(name);
        }

        @Override
        public void run() {
            for (int i = 1; i <= 5; i++) {
                String val = Thread.currentThread().getName() + i;
                queue.add(val);
                printAll();
            }
        }
    }
}
// 运行结果
b1, a1, 
b1, a1, a2, 
b1, a1, a2, a3, 
b1, a1, a2, a3, a4, 
b1, a1, a2, a3, a4, a5, 
b1, 
b1, a1, a2, a3, a4, a5, b2, 
b1, a1, a2, a3, a4, a5, b2, b3, 
b1, a1, a2, a3, a4, a5, b2, b3, b4, 
b1, a1, a2, a3, a4, a5, b2, b3, b4, b5, 

当然,把ArrayBlockingQueue换成普通的ArrayList会报错!
以上就是对ArrayBlockingQueue的详细分析,欢迎大家指出问题,共同学习。

猜你喜欢

转载自blog.csdn.net/qq_22798455/article/details/81636772