在之前分析AsyncTask源码的时候有看到其内部自定义线程池里使用到了ArrayDeque这个类去存放线程,今天这篇文章就对它进行解析。
看到ArrayDeque这个名字会发现跟我们频繁使用的ArrayList很像,都是用其内部维护的数组存储数据,都是线程不安全的实现。来看看Google的介绍
Resizable-array implementation of the {@link Deque} interface. Array deques have no capacity restrictions; they grow as necessary to support usage. They are not thread-safe; in the absence of external synchronization, they do not support concurrent access by multiple threads. Null elements are prohibited. This class is likely to be faster than {@link Stack} when used as a stack, and faster than {@link LinkedList} when used as a queue.
大概意思是实现了Deque接口,且大小可变,没有容量的限制,不是线程安全的,在外面没有同步的情况下,不支持多线程访问。不支持添加null元素,当作为栈使用的时候比Stack更快,当作为队列使用的时候比LinkedList更高效。
这里面的LinkedList应该都知道,而Stack这个类大家可能会见的少,Stack是继承Vector这个类的,而Vector跟ArrayList是一个父亲下来的;Vector是google设计的线程安全的数组,而ArrayList是线程不安全,但是高效的数组;突然想起来以前遇到一个面试问题,讲述一下为什么有了StringBuild,还要设计一个StringBuffer,其实跟Vector和ArrayList的存在一样,为了适应不同业务需求而分别存在。你单线程高效但是并发不安全,你并发安全但是单线程不高效。所以Stack也是线程安全的(是一种后进先出的数据结构,只能在一端进行压栈或者出栈的数据操作)。
接下来看看源码里定义的变量
/**
* The array in which the elements of the deque are stored.
* The capacity of the deque is the length of this array, which is
* always a power of two. The array is never allowed to become
* full, except transiently within an addX method where it is
* resized (see doubleCapacity) immediately upon becoming full,
* thus avoiding head and tail wrapping around to equal each
* other. We also guarantee that all array cells not holding
* deque elements are always null.
* transient修饰符是避免在实现Serializable接口后被自动序列化进行传递
* 存储元素的数组,它的长度是2的n次方,
*/
transient Object[] elements; // non-private to simplify nested class access
/**
* The index of the element at the head of the deque (which is the
* element that would be removed by remove() or pop()); or an
* arbitrary number equal to tail if the deque is empty.
* 头部元素索引,并不是指数组第0个元素
*/
transient int head;
/**
* The index at which the next element would be added to the tail
* of the deque (via addLast(E), add(E), or push(E)).
* 下一次添加到队列尾部的元素索引,即当前最后一个元素的索引+1
*/
transient int tail;
/**
* The minimum capacity that we'll use for a newly created deque.
* Must be a power of 2.
* 数组最小长度
*/
private static final int MIN_INITIAL_CAPACITY = 8;
主要维护了一个数组,长度不限,长度为2的n次方,且记录了队首和队尾的位置,保证能进行双端操作
再看看构造方法
/**
* Constructs an empty array deque with an initial capacity
* sufficient to hold 16 elements.
* 初始化一个长度为16的数组,这个长度是可变的
*/
public ArrayDeque() {
elements = new Object[16];
}
/**
* Constructs an empty array deque with an initial capacity
* sufficient to hold the specified number of elements.
*
* @param numElements lower bound on initial capacity of the deque
*
* 初始化一个指定容量的数组
*/
public ArrayDeque(int numElements) {
//分配数组空间
allocateElements(numElements);
}
/**
* Constructs a deque containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator. (The first element returned by the collection's
* iterator becomes the first element, or front of the
* deque.)
*
* @param c the collection whose elements are to be placed into the deque
* @throws NullPointerException if the specified collection is null
*
* 将指定集合存入数组
*/
public ArrayDeque(Collection extends E> c) {
//分配数组空间
allocateElements(c.size());
//将元素添加到数组
addAll(c);
}
/**
* Allocates empty array to hold the given number of elements.
*
* @param numElements the number of elements to hold
*
* 分配一个空数组存储给定数量元素
* 数组大小肯定是大于或者等于numElements值的,因为要求必须是2的n次方
* 假如传入的是11,那2的3次方=8<11;2的4次方=16>11;所以最终数组大小是16
*/
private void allocateElements(int numElements) {
int initialCapacity = MIN_INITIAL_CAPACITY;
// Find the best power of two to hold elements.
// Tests "<=" because arrays aren't kept full.
if (numElements >= initialCapacity) {
initialCapacity = numElements;
initialCapacity |= (initialCapacity >>> 1);
initialCapacity |= (initialCapacity >>> 2);
initialCapacity |= (initialCapacity >>> 4);
initialCapacity |= (initialCapacity >>> 8);
initialCapacity |= (initialCapacity >>> 16);
initialCapacity++;
if (initialCapacity < 0) // Too many elements, must back off
initialCapacity >>>= 1; // Good luck allocating 2^30 elements
}
elements = new Object[initialCapacity];
}
构造方法主要就是初始化一个给定大小的数组,给数组分配空间的方法是allocateElements(int numElements),我们看下这里面的逻辑:
[ >>> ] : 是无符号右移运算符,把二进制数往右移动指定数量位置,左边空出来的位用0补充,右边移出去的位舍弃,即高位补零,低位舍弃;
例如 5 >>> 1,5的二进制是101,往右移一位10,高位补零就是010,即5 >>> 1 = 2;
例如-5 >>> 1,同样的道理,在计算机中负数是以其正数对应的补码表示;-5的二进制数计算方法是先获取5的原码(一个整数按照绝对值得到的二进制数),再得到反码(将二进制数按位取反得到的数),再将反码加1得到补码;
5的原码全称是00000000 00000000 00000000 00000101,int类型数是32位
5的反码就是 11111111 11111111 11111111 11111010
5的补码就是 11111111 11111111 11111111 11111011
这样-5的二进制就是11111111 11111111 11111111 11111011,将其右移一位得到
01111111 11111111 11111111 11111101 = 2147483645
这样-5 >>> 1 = 2147483645,
[ |= ] :也是位运算符一种,例如a |= b,等价于a = a|b,即把a和b按 位或 操作后的值赋值给a;位或 操作逻辑是第一个操作数的第n位与第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0;例如 5|3 ,5的二进制是101,3的二进制是011,对应位进行比较,得出111,即 5|3 = 7。
了解了计算方法,再回到allocateElements方法中:
假设我们传入的值是11,看第一句代码:initialCapacity |= (initialCapacity >>> 1);这个等价于initialCapacity = initialCapacity | (initialCapacity >>> 1);initialCapacity 值等于11,先计算11无符号右移一位,11的二进制是1011,右移后是0101,即5;接着计算11 | 5,11的二进制是1011,5的二进制是0101,位或操作后得到值是1111=15,这样initialCapacity = 15;再看第二句initialCapacity |= (initialCapacity >>> 2); 相当于initialCapacity = 15 | (15 >>>2),这样算出来的值依然是15;后面三个位运算就不一一演示了,这样位运算结束后得到的值是15,再通过initialCapacity++,得到数组最终长度是16,正好是2的4次方。
继续看添加元素方法
/**
* Inserts the specified element at the front of this deque.
*
* @param e the element to add
* @throws NullPointerException if the specified element is null
* 将元素从数组的最后一个位置向前依次存放
*/
public void addFirst(E e) {
if (e == null)
throw new NullPointerException();
//插入元素的同时计算head的值
//进行与操作是为了防止数组越界,比如刚开始head=0,head-1=-1,这样肯定会出现问题的
elements[head = (head - 1) & (elements.length - 1)] = e;
//判断数组是否满了
if (head == tail)
doubleCapacity();
}
/**
* Inserts the specified element at the end of this deque.
*
* This method is equivalent to {@link #add}.
*
* @param e the element to add
* @throws NullPointerException if the specified element is null
* 将元素从数组的第一个位置依次向后存放
*/
public void addLast(E e) {
if (e == null)
throw new NullPointerException();
elements[tail] = e;
//判断数组是否满了
if ( (tail = (tail + 1) & (elements.length - 1)) == head)
doubleCapacity();
}
/**
* Inserts the specified element at the front of this deque.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Deque#offerFirst})
* @throws NullPointerException if the specified element is null
*/
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
/**
* Inserts the specified element at the end of this deque.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Deque#offerLast})
* @throws NullPointerException if the specified element is null
*/
public boolean offerLast(E e) {
addLast(e);
return true;
}
public boolean offer(E e) {
return offerLast(e);
}
public boolean add(E e) {
addLast(e);
return true;
}
/**
* Doubles the capacity of this deque. Call only when full, i.e.,
* when head and tail have wrapped around to become equal.
* 数组满的时候将数组容量翻倍
* 这时候head和tail指向同一个元素
*/
private void doubleCapacity() {
//assert关键字是断言的意思,指代码执行到这个地方的时候是预期的结果,不会影响到程序的执行
//可以和if语句有差不多的效果,jdk1.4以后引入
assert head == tail;
int p = head;
int n = elements.length;
//计算从哪个位置开始复制
int r = n - p; // number of elements to the right of p
//将现有数组长度左移一位得到新数组长度
int newCapacity = n << 1;
if (newCapacity < 0)
throw new IllegalStateException("Sorry, deque too big");
//根据新的长度实例化一个新的数组
Object[] a = new Object[newCapacity];
//从elements[p]开始取r个长度的值复制到a[0]~a[r-1]
System.arraycopy(elements, p, a, 0, r);
//从elements[0]开始取p个长度的值复制到a[r]~a[r+p-1]
System.arraycopy(elements, 0, a, r, p);
//重置数组
elements = a;
//重置head和tail值
head = 0;
tail = n;
}
这几个方法一个是从数组头部添加元素,一个从数组尾部添加元素,这就是双端队列;如果数组长度达到限定长度,进行长度翻倍
继续看取数据方法
/**
* 检索并删除索引为head的元素
* @throws NoSuchElementException {@inheritDoc}
*/
public E removeFirst() {
E x = pollFirst();
if (x == null)
throw new NoSuchElementException();
return x;
}
/**
* 检索并删除索引为tail的元素
* @throws NoSuchElementException {@inheritDoc}
*/
public E removeLast() {
E x = pollLast();
if (x == null)
throw new NoSuchElementException();
return x;
}
/**
* 获取head索引处的值,并把数组该处值置为null,同时通过size()获取的值也会减小1
* @return
*/
public E pollFirst() {
final Object[] elements = this.elements;
final int h = head;
//保存head索引处的值
@SuppressWarnings("unchecked")
E result = (E) elements[h];
// Element is null if deque empty
if (result != null) {
//删除head索引的值
elements[h] = null; // Must null out slot
//计算head+1值作为新的索引,这样与操作是避免h + 1=elements.length 出现数组越界
head = (h + 1) & (elements.length - 1);
}
return result;
}
/**
* 获取tail索引处的值,并把数组该处值置为null,同时通过size()获取的值也会减小1
* @return
*/
public E pollLast() {
final Object[] elements = this.elements;
//获取tail - 1索引处的值 这样的操作是处理临界情况(当tail为0时)
final int t = (tail - 1) & (elements.length - 1);
//保存tail索引处的值
@SuppressWarnings("unchecked")
E result = (E) elements[t];
if (result != null) {
//删除tail索引处的值
elements[t] = null;
//tail指向的是下个要添加元素的索引
tail = t;
}
return result;
}
/**
* @throws NoSuchElementException {@inheritDoc}
* 获取head索引处的值
*/
public E getFirst() {
@SuppressWarnings("unchecked")
E result = (E) elements[head];
if (result == null)
throw new NoSuchElementException();
return result;
}
/**
* @throws NoSuchElementException {@inheritDoc}
* 获取tail-1处索引处的值
*/
public E getLast() {
@SuppressWarnings("unchecked")
E result = (E) elements[(tail - 1) & (elements.length - 1)];
if (result == null)
throw new NoSuchElementException();
return result;
}
@SuppressWarnings("unchecked")
public E peekFirst() {
// 获取head索引处的值,可能为null
return (E) elements[head];
}
@SuppressWarnings("unchecked")
public E peekLast() {
//获取tail-1处索引处的值,可能为null
return (E) elements[(tail - 1) & (elements.length - 1)];
}
接下来看看简单使用
ArrayDeque deque = new ArrayDeque<>();
deque.addFirst(1);
deque.addFirst(2);
deque.addFirst(3);
deque.addFirst(4);
deque.addFirst(5);
deque.addFirst(6);
deque.addFirst(7);
deque.addFirst(8);
for (Integer value:deque) {
Log.e(TAG,"deque value="+value);
}
看日志
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=8
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=7
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=6
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=5
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=4
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=3
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=2
06-25 19:13:27.463 11323-11323 E/MainActivity: deque value=1
这个输入顺序好像跟ArrayList相反的对吧,这是因为addFirst方法是将元素从数组的最后一个位置向前依次存放,可以说是一个后进先出的规则
如果使用addLast方法添加元素,日志如下
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=1
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=2
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=3
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=4
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=5
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=6
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=7
06-25 19:22:43.341 19507-19507 E/MainActivity: deque value=8
它是一个先进先出的规则,将元素从数组的第一个位置依次向后存放
其它方法也很简单,就不一一演示了。我们回到AsyncTask源码中对ArrayDeque的使用:
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
//r执行结束后执行下一个任务
scheduleNext();
}
}
});
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
通过offer方法添加工作线程,这个方法会调用offerLast(E e),然后调用到addLast(E e),看上面的注释,这个方法将元素从数组的第一个位置依次向后存放,并且改变的是tail的值,head的值一直是0;然后通过poll()方法取线程,会调用pollFirst()方法,这个方法通过head取值,取完值将head+1;这样AsyncTask的工作逻辑就是先添加的线程先执行。