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()方法也是非阻塞的.这个类用法、实现也都比较简单.