之前的队列都是在单线程中使用的,如果把它用到多线程中会怎样呢?
举一个简单的例子:
offer中的这段代码
arr[size] = e;
size ++;
假设有个线程执行了添加操作:arr[tail] = e;但是还没来得及size ++ ,下一个线程又开始执行offer,那么第二次不会添加新的元素,而是把上一个添加的元素给覆盖掉了。
为了避免类似这种的线程不安全,我们得给这个类上把锁
至于线程锁的选择,关键字synchronized 实现的线程锁功能太过单调,不够灵活,这里不使用这种方式。
我们使用 java.util.concurrent.locks.ReentrantLock 这个类来实现,再搭配Condition 这个类来实现等待和唤醒
先定义有个接口:
public interface BlockingQueue<E>
{
/**
* 入队
* @param e 元素
* @throws InterruptedException
*/
void offer(E e) throws InterruptedException;
/**
* 入队
* @param e 元素
* @param timeout 线程等待时间
* @return 是否入队成功
* @throws InterruptedException
*/
boolean offer(E e, long timeout) throws InterruptedException;
/**
* 出队
* @return 出队元素
* @throws InterruptedException
*/
E poll() throws InterruptedException;
}
实现类:
public class BlockingQueue1<E> implements BlockingQueue<E>
{
private final E[] arr ;//存储元素的数组,环形数组
private int head;//头指针
private int tail;//尾指针
private int size;//元素个数
public BlockingQueue1(int capacity)
{
arr = (E[]) new Object[capacity];//初始化数组
}
private ReentrantLock lock = new ReentrantLock();//锁
private Condition headWaits = lock.newCondition();//队头等待队列
private Condition tailWaits = lock.newCondition();//队尾等待队列
/**
* 判断队列是否为空
* @return 如果队列为空,返回true
*/
private boolean isEmpty()
{
return size == 0;
}
/**
* 判断队列是否已满
* @return 如果队列已满,返回true
*/
private boolean isFull()
{
return size == arr.length;
}
@Override
/**
* 入队
* @param e 元素
* @throws InterruptedException
*/
public void offer(E e) throws InterruptedException
{
lock.lockInterruptibly();//获取锁
try
{
while (isFull())
{
tailWaits.await();//如果队列已满,等待
}
arr[tail] = e;//从尾部插入元素
if (++ tail == arr.length)//如果尾指针到达数组末尾,那么尾指针指向数组头部
{
tail = 0;
}
size ++;
headWaits.signal();//唤醒等待队头的线程
}
finally
{
lock.unlock();//释放锁
}
}
@Override
/**
* 入队
* @param e 元素
* @param timeout 线程等待时间
* @return 是否入队成功
* @throws InterruptedException
*/
public boolean offer(E e, long timeout) throws InterruptedException
{
lock.lockInterruptibly();
try
{
long t = TimeUnit.MILLISECONDS.toNanos(timeout);//毫秒转纳秒
while (isFull())
{
if (t <= 0)
{
return false;
}
t = tailWaits.awaitNanos(t); // 最多等待多少纳秒,返回值为剩余时间
}
arr[tail] = e;
if (++ tail == arr.length)//如果尾指针到达数组末尾,那么尾指针指向数组头部
{
tail = 0;
}
size ++;
headWaits.signal();//唤醒等待队头的线程
return true;
}
finally
{
lock.unlock();
}
}
@Override
/**
* 出队
* @return 出队元素
* @throws InterruptedException
*/
public E poll() throws InterruptedException
{
lock.lockInterruptibly();
try
{
while (isEmpty())
{
headWaits.await();//如果队列为空,等待
}
E e =arr[head];
arr[head] = null;
if (++ head == arr.length)//如果头指针到达数组末尾,那么头指针指向数组头部
{
head = 0;
}
size --;
tailWaits.signal();//唤醒等待队尾的线程
return e;
}
finally
{
lock.unlock();
}
}
@Override
public String toString()
{
return Arrays.toString(arr);
}
}
这样就能避免线程不安全了,那么就ok了吗?
NO,NO,NO,虽然解决了前面的问题,但是又引发了新的问题,那就是线程死锁。
怎么就死锁了呢?
你看offer的这段:
while (isFull())
{
tailWaits.await();//如果队列已满,等待
}
和poll的这段:
while (isEmpty())
{
headWaits.await();//如果队列为空,等待
}
假设只能放两个元素,offer线程三个,全堵在这里,poll也有三个,前两个poll 解了前两个offer的围,这时元素空了,还有一个offer赌在这里,还有一个poll要去唤醒它,但是队列空了呀,poll自己也堵住了,这个时候就没戏了。
那怎么解决这个问题呢?
那就不能只用一把锁了,得用双锁。
public class BlockingQueue2<E> implements BlockingQueue<E>
{
private final E[] arr ;//环形数组
private int head;//头指针
private int tail;//尾指针
private AtomicInteger size = new AtomicInteger();//元素个数,使用原子类,保证线程安全
public BlockingQueue2(int capacity)
{
arr = (E[]) new Object[capacity];
}
private ReentrantLock tailLock = new ReentrantLock();//尾部锁
private ReentrantLock headLock = new ReentrantLock();//头部锁
private Condition headWaits = headLock.newCondition();//头部等待队列
private Condition tailWaits = tailLock.newCondition();//尾部等待队列
private boolean isEmpty()
{
return size.get() == 0;
}
private boolean isFull()
{
return size.get() == arr.length;
}
@Override
public void offer(final E e) throws InterruptedException
{
int c ;//添加前的元素个数
tailLock.lockInterruptibly();
try
{
while (isFull())
{
tailWaits.await();
}
arr[tail] = e;
if (++ tail == arr.length)
{
tail = 0;
}
c = size.getAndIncrement();//先获取再自增
if (c < arr.length - 1)//若此次线程处理完后还有元素可以添加,那么通知等待的offer线程
{
tailWaits.signal();
}
}
finally
{
tailLock.unlock();
}
if (c == 0)//如果添加前是空的,那么通知等待的poll线程
{
headLock.lock();
try
{
headWaits.signal();
}
finally
{
headLock.unlock();
}
}
}
@Override
public boolean offer(E e, long timeout) throws InterruptedException
{
int c;//添加前的元素个数
tailLock.lockInterruptibly();
try
{
long t = TimeUnit.MILLISECONDS.toNanos(timeout);//毫秒转纳秒
while (isFull())
{
if (t <= 0)
{
return false;
}
t = tailWaits.awaitNanos(t); // 最多等待多少纳秒,返回值为剩余时间
}
arr[tail] = e;
if (++ tail == arr.length)
{
tail = 0;
}
c = size.getAndIncrement();
if (c < arr.length - 1)//若此次线程处理完后还有元素可以添加,那么通知等待的offer线程
{
tailWaits.signal();
}
}
finally
{
tailLock.unlock();
}
if (c == 0)//如果添加前是空的,那么通知等待的poll线程)
{
headLock.lock();
try
{
headWaits.signal();
}
finally
{
headLock.unlock();
}
}
return true;
}
@Override
public E poll() throws InterruptedException
{
headLock.lockInterruptibly();
E e;
int c;//移除前的元素个数
try
{
while (isEmpty())
{
headWaits.await();;
}
e =arr[head];
arr[head] = null;//help GC
if (++ head == arr.length)
{
head = 0;
}
c = size.getAndDecrement();
if (c > 1)//若此次线程处理完后还有元素可以移除,那么通知等待的poll线程
{
headWaits.signal();
}
}
finally
{
headLock.unlock();
}
if (c == arr.length)//如果移除前是满的,那么通知等待的offer线程
{
tailLock.lock();
try
{
tailWaits.signal();
}
finally
{
tailLock.unlock();
}
}
return e;
}
@Override
public String toString()
{
return Arrays.toString(arr);
}
}
现在我们改用级联唤醒,当后面还有元素可以添加时,让offer唤醒阻塞的offer,同样当还有元素可以删除时,让poll唤醒阻塞的poll。
当队列满了,就由poll去唤醒offer,当队列空了,就让offer去唤醒poll