/*
* Copyright (c) 2012 NTT DATA Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jp.terasoluna.fw.collector.concurrent;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* AbstractCollector用ArrayBlockingQueueサブクラス。
* <p>
* {@link ArrayBlockingQueue#peek()}と {@link ArrayBlockingQueue#isEmpty()}に、 キューが空であれば、キューに要素が入るか、キューイング終了フラグが上がるまで 待つ機能をつけている。<br>
* ArrayBlockingQueue内の、ブロック制御を行っているConditionフィールドは サブクラスに公開されていないため、 このクラスではArrayBlockingQueueと冗長な実装をしている。<br>
* </p>
* <p>
* 実装はAbstractCollectorに使用されるものに絞っているため、 すべてのメソッドが使用できるわけではない。<br>
* このクラスでオーバーライドしているメソッド以外で、 キューの状態を変更するメソッドや、待ちが発生するメソッドを実行してはならない。
* </p>
* <p>
* キューに要素を詰め終わった後は、キューに要素を詰めるスレッドで、必ずfinishQueueingメソッドを実行すること。
* </p>
* @param <E> コレクション内に存在する要素の型
*/
public class ArrayBlockingQueueEx<E> extends ArrayBlockingQueue<E>
implements
NotificationBlockingQueue<E> {
/**
* serialVersionUID
*/
private static final long serialVersionUID = 7441765139909417804L;
/**
* キューの操作を同期化するロック。
*/
protected final ReentrantLock queueLock = new ReentrantLock();
/**
* キューが空でなくなったときに送信されるシグナル。
*/
protected final Condition notEmpty = queueLock.newCondition();
/**
* キューがFullでなくなったときに送信されるシグナル。
*/
protected final Condition notFull = queueLock.newCondition();
/**
* キューサイズ。
*/
protected final int capacity;
/**
* キューイング終了フラグ。
*/
protected volatile boolean finishQueueingFlag = false;
/**
* 指定された (固定) 容量および指定されたアクセスポリシーを使用して、ArrayBlockingQueue を作成する。
* @param capacity キューの容量
* @param fair true の場合、挿入または削除時にブロックされたスレッドに対するキューアクセスは、FIFO の順序で処理される。 false の場合、アクセス順序は指定されない。
* @see ArrayBlockingQueue#ArrayBlockingQueue(int, boolean)
*/
public ArrayBlockingQueueEx(int capacity, boolean fair) {
super(capacity, fair);
this.capacity = capacity;
}
/**
* 指定された (固定) 容量およびデフォルトのアクセスポリシーを使用して、ArrayBlockingQueue を作成する。
* @param capacity キューの容量
* @see ArrayBlockingQueue#ArrayBlockingQueue(int)
*/
public ArrayBlockingQueueEx(int capacity) {
super(capacity);
this.capacity = capacity;
}
/**
* キューイングの終了を通知する。
* <p>
* キューに要素が入るのを待っているスレッドがいる場合、そのブロックを解除する。 キューに要素を詰めるスレッドは、キューイングが完了したあとで、必ずこのメソッドを実行すること。
* </p>
*/
@Override
public void finishQueueing() {
queueLock.lock();
try {
finishQueueingFlag = true;
// 要素の入り待ちを行っているスレッドのブロックを解除する
notEmpty.signalAll();
} finally {
queueLock.unlock();
}
}
/**
* 指定された要素をこのキューの末尾に挿入する。必要に応じ、空間が利用可能になるのを指定された時間まで待機する。
* <p>
* このメソッドの定義は、{@link ArrayBlockingQueue#offer(Object, long, TimeUnit)}と同じ。
* </p>
* @param o 追加する要素
* @param timeout 処理を中止するまでの待機時間。単位は unit
* @param unit timeout パラメータの解釈方法を指定する TimeUnit
* @return 成功した場合は true、空間が利用可能になる前に指定された待機時間が経過した場合は false
* @throws InterruptedException 待機中に割り込みが発生した場合
* @throws NullPointerException 指定された要素が null である場合
* @see ArrayBlockingQueue#offer(Object, long, TimeUnit)
*/
@Override
public boolean offer(E o, long timeout, TimeUnit unit)
throws InterruptedException {
if (o == null) {
throw new NullPointerException();
}
long nanos = unit.toNanos(timeout);
queueLock.lockInterruptibly();
try {
while (size() == capacity) {
// キューが空くのを待つ
nanos = notFull.awaitNanos(nanos);
if (nanos <= 0) {
// タイムアウト
return false;
}
}
boolean success = super.offer(o);
if (success) {
// 要素の入り待ちを行っているスレッドのブロックを解除する
notEmpty.signal();
}
return success;
} finally {
queueLock.unlock();
}
}
/**
* 可能であれば、このキューの末尾に指定された要素を挿入する。このキューがいっぱいである場合には、即座に返す。
* <p>
* このメソッドの定義は、{@link ArrayBlockingQueue#offer(Object)}と同じ。
* </p>
* @param o 追加する要素
* @return 要素をこのキューに追加可能な場合は true、そうでない場合は false
* @throws NullPointerException 指定された要素が null である場合
* @see ArrayBlockingQueue#offer(Object)
*/
@Override
public boolean offer(E o) {
queueLock.lock();
try {
if (size() == capacity) {
return false;
}
boolean success = super.offer(o);
if (success) {
// 要素の入り待ちを行っているスレッドのブロックを解除する
notEmpty.signal();
}
return success;
} finally {
queueLock.unlock();
}
}
/**
* 指定された要素をこのキューの末尾に追加する。必要に応じ、空間が利用可能になるまで待機する。
* @param o 追加する要素
* @throws InterruptedException 待機中に割り込みが発生した場合
* @throws NullPointerException 指定された要素が null である場合
*/
@Override
public void put(E o) throws InterruptedException {
if (o == null) {
throw new NullPointerException();
}
queueLock.lock();
try {
while (size() == capacity) {
// キューが空くのを待つ
notFull.await();
}
super.put(o);
// 要素の入り待ちを行っているスレッドのブロックを解除する
notEmpty.signal();
} finally {
queueLock.unlock();
}
}
/**
* キューの先頭を取得するが、削除しない。
* <p>
* 拡張仕様:<b> キューが空の場合は、キューに要素が入るか、キューイングの終了が通知されるまで待つ。<br>
* キューイングの終了が通知された後、キューが空の場合は null を返す。
* </p>
* <p>
* キューに要素がある場合や、キューイングの終了が通知された後の仕様は、 {@link ArrayBlockingQueue#peek()}と同じ。
* </p>
* @return キューの先頭。キューイング終了後にキューが空の場合は null
*/
@Override
public E peek() {
queueLock.lock();
try {
while (!finishQueueingFlag && size() == 0) {
try {
// キューに要素が入るのをのを待つ
notEmpty.await();
} catch (InterruptedException e) {
return null;
}
}
return super.peek();
} finally {
queueLock.unlock();
}
}
/**
* このキューの先頭を取得および削除する。このキューに要素が存在しない場合は、必要に応じて指定された時間だけ待機する。
* <p>
* 拡張仕様:<b> キューイングの終了が通知された後、キューが空の場合は、タイムアウトを待たずに null を返す。
* </p>
* <p>
* キューイングの終了が通知される前の仕様は、 {@link ArrayBlockingQueue#poll(long, TimeUnit)}と同じ。
* </p>
* @param timeout 処理を中止するまでの待機時間。単位は unit
* @param unit timeout パラメータの解釈方法を指定する TimeUnit
* @return このキューの先頭。指定された待機時間が経過、あるいはキューイングの終了が通知された後も要素が存在しない場合は null
* @throws InterruptedException 待機中に割り込みが発生した場合
*/
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
queueLock.lock();
try {
while (!finishQueueingFlag && size() == 0) {
// キューに要素が入るのをのを待つ
nanos = notEmpty.awaitNanos(nanos);
if (nanos <= 0) {
// タイムアウト
return null;
}
}
if (finishQueueingFlag && size() == 0) {
// キューイングの終了が通知された後、かつ、キューが空
return null;
}
E elm = super.poll(timeout, unit);
if (elm != null) {
// キューの空き待ちを行っているスレッドのブロックを解除する
notFull.signal();
}
return elm;
} finally {
queueLock.unlock();
}
}
/**
* このキューの先頭を取得および削除する。
* @return このキューの先頭。要素が存在しない場合は null
*/
@Override
public E poll() {
queueLock.lock();
try {
E elm = super.poll();
if (elm != null) {
// キューの空き待ちを行っているスレッドのブロックを解除する
notFull.signal();
}
return elm;
} finally {
queueLock.unlock();
}
}
/**
* キューに要素がない場合に true を返す。
* <p>
* 拡張仕様:<b> キューが空の場合は、キューに要素が入るか、キューイングの終了が通知されるまで待つ。<br>
* キューイングの終了が通知された後、キューが空の場合は true を返す。
* </p>
*/
@Override
public boolean isEmpty() {
queueLock.lock();
try {
while (!finishQueueingFlag && size() == 0) {
try {
// キューに要素が入るのをのを待つ
notEmpty.await();
} catch (InterruptedException e) {
return true;
}
}
return super.isEmpty();
} finally {
queueLock.unlock();
}
}
}