CopyOnWriteArrayList、CopyOnWriteArraySet
這兩個類都比較簡單內部有一個數組和一把鎖,對所有寫操作加鎖.每次進行寫操作時都複製一個新的數組,在新數組上進行;而讀則在老數組上進行,有讀寫分離的意思,比Vector效率高,適合都多寫少的情況.
咱們看看其如何實現的
transient final ReentrantLock lock = new ReentrantLock();
private volatile transient Object[] array;
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
先看看其讀取方法
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
private static int lastIndexOf(Object o, Object[] elements, int index) {
if (o == null) {
for (int i = index; i >= 0; i--)
if (elements[i] == null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elements[i]))
return i;
}
return -1;
}
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
public int indexOf(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length);
}
public int lastIndexOf(Object o) {
Object[] elements = getArray();
return lastIndexOf(o, elements, elements.length - 1);
}
這些讀的方法都很簡單,也沒什麼新意.下面看看寫操作
//指定位置插入元素
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
//加鎖
lock.lock();
try {
//獲取舊數組
Object[] elements = getArray();
//獲取舊元素
E oldValue = get(elements, index);
//不相等則要覆蓋舊元素
if (oldValue != element) {
int len = elements.length;
//複製出一個新的數組
Object[] newElements = Arrays.copyOf(elements, len);
//覆蓋舊元素
newElements[index] = element;
//適用新數組
setArray(newElements);
} else {
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
可以看到所有的寫操作都加了鎖且都是操作新數組,這樣能有效避免寫操作之間的影響,且能避免進行寫操作時讀操作不準確;但這樣加大了內存的消耗,因此適合讀多寫少的場景.
CopyOnWriteArraySet內部持有一個CopyOnWriteArrayList引用,所有操作都是基於對CopyOnWriteArrayList的操作.
ArrayBlockingQueue
首先咱們先來看看其用法:
public class ArrayBlockingQueueTest {
public static void main(String[] args) {
final ArrayBlockingQueue<String> queue = new ArrayBlockingQueue(3);
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
queue.put("a");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable).start();
Runnable runnable2 = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
String take = queue.take();
System.out.println(take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
new Thread(runnable2).start();
}
}
ArrayBlockingQueue阻塞有界隊列,具有一下幾個基本特點:
- 基於數組實現的阻塞有界FIFO隊列
- 基於數組創建因此隊列大小不能改變;當隊列滿時添加元素方法會被阻塞,當隊列空時獲取元素方法會被阻塞
- 提供公平模式、非公平模式
下面咱們來看看其源碼,先看其主要屬性與構造方法
//存放元素的數組
final Object[] items;
//take, poll, peek or remove等方法操作的元素位置
int takeIndex;
//put, offer, or add等方法操作的元素位置
int putIndex;
//隊列中元素數量
int count;
//ReentrantLock控制併發
final ReentrantLock lock;
//取出元素方法等待條件
private final Condition notEmpty;
//添加元素方法等待條件
private final Condition notFull;
//默認非公平鎖
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
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();
}
下面看看其主要方法,先看add()與offer()
//add()實際上是調用offer()完成的
public boolean add(E e) {
return super.add(e);
}
//可以看出,此方法實際上是不阻塞的
public boolean offer(E e) {
//檢查元素是否爲Null,如果爲null直接拋出異常
checkNotNull(e);
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//conut==items.length時說明隊列已滿
if (count == items.length)
return false;
else {
insert(e);
return true;
}
} finally {
lock.unlock();
}
}
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 {
//如果隊列滿了且等待時間小於等於0,則返回false;否則等待指定時間
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
insert(e);
return true;
} finally {
lock.unlock();
}
}
//插入方法
private void insert(E x) {
items[putIndex] = x;
putIndex = inc(putIndex);//put元素位置
++count;
//通知lock的非空條件隊列上的線程
notEmpty.signal();
}
final int inc(int i) {
return (++i == items.length) ? 0 : i;
}
再看看put()和take()
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();
}
}
//獲取一個元素
private E extract() {
final Object[] items = this.items;
E x = this.<E>cast(items[takeIndex]);
items[takeIndex] = null;
//改變take位置
takeIndex = inc(takeIndex);
--count;
//通知lock的滿條件隊列上的線程
notFull.signal();
return x;
}
可以看到put()和take()方法是阻塞隊列的真正實現方法,當隊列滿了時,put()方法被阻塞,直到有元素被取出,put()方法才能添加元素;當隊列爲空時,take()方法被阻塞直到有元素被添加,take()方法才能獲取元素
再來看看poll()方法
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//如果隊列爲空則返回null;否則返回一個元素
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//如果隊列爲null且等待時間<=0則等待指定時間,否則返回null
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return extract();
} finally {
lock.unlock();
}
}
可以看到poll()方法也是非阻塞的.這個類用法、實現也都比較簡單.