我們談一下實(shí)際的場(chǎng)景吧。我們?cè)陂_發(fā)中,有如下場(chǎng)景
a) 關(guān)閉空閑連接。服務(wù)器中,有很多客戶端的連接,空閑一段時(shí)間之后需要關(guān)閉之。
b) 緩存。緩存中的對(duì)象,超過了空閑時(shí)間,需要從緩存中移出。
c) 任務(wù)超時(shí)處理。在網(wǎng)絡(luò)協(xié)議滑動(dòng)窗口請(qǐng)求應(yīng)答式交互時(shí),處理超時(shí)未響應(yīng)的請(qǐng)求。
一種笨笨的辦法就是,使用一個(gè)后臺(tái)線程,遍歷所有對(duì)象,挨個(gè)檢查。這種笨笨的辦法簡(jiǎn)單好用,但是對(duì)象數(shù)量過多時(shí),可能存在性能問題,檢查間隔時(shí)間不好設(shè)置,間隔時(shí)間過大,影響精確度,多小則存在效率問題。而且做不到按超時(shí)的時(shí)間順序處理。
這場(chǎng)景,使用DelayQueue最適合了。
DelayQueue
是java.util.concurrent中提供的一個(gè)很有意思的類。很巧妙,非常棒!但是java doc和Java SE
5.0的source中都沒有提供Sample。我最初在閱讀ScheduledThreadPoolExecutor源碼時(shí),發(fā)現(xiàn)DelayQueue
的妙用。隨后在實(shí)際工作中,應(yīng)用在session超時(shí)管理,網(wǎng)絡(luò)應(yīng)答通訊協(xié)議的請(qǐng)求超時(shí)處理。
本文將會(huì)對(duì)DelayQueue做一個(gè)介紹,然后列舉應(yīng)用場(chǎng)景。并且提供一個(gè)Delayed接口的實(shí)現(xiàn)和Sample代碼。
DelayQueue是一個(gè)BlockingQueue,其特化的參數(shù)是Delayed。(不了解BlockingQueue的同學(xué),先去了解BlockingQueue再看本文)
Delayed擴(kuò)展了Comparable接口,比較的基準(zhǔn)為延時(shí)的時(shí)間值,Delayed接口的實(shí)現(xiàn)類getDelay的返回值應(yīng)為固定值(final)。DelayQueue內(nèi)部是使用
PriorityQueue實(shí)現(xiàn)的。
DelayQueue = BlockingQueue + PriorityQueue + Delayed
DelayQueue的關(guān)鍵元素BlockingQueue、PriorityQueue、Delayed??梢赃@么說,DelayQueue是一個(gè)使用優(yōu)先隊(duì)列(PriorityQueue)實(shí)現(xiàn)的BlockingQueue,
優(yōu)先隊(duì)列的比較基準(zhǔn)值是時(shí)間。他們的基本定義如下
public interface Comparable<T> {
public int compareTo(T o);
}
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
public class DelayQueue<E extends Delayed> implements BlockingQueue<E> {
private final PriorityQueue<E> q = new PriorityQueue<E>();
}
DelayQueue內(nèi)部的實(shí)現(xiàn)使用了一個(gè)優(yōu)先隊(duì)列。當(dāng)調(diào)用DelayQueue的offer方法時(shí),把Delayed對(duì)象加入到優(yōu)先隊(duì)列q中。如下:
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
q.offer(e);
if (first == null || e.compareTo(first) < 0)
available.signalAll();
return true;
} finally {
lock.unlock();
}
}
DelayQueue的take方法,把優(yōu)先隊(duì)列q的first拿出來(peek),如果沒有達(dá)到延時(shí)閥值,則進(jìn)行await處理。如下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null) {
available.await();
} else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay > 0) {
long tl = available.awaitNanos(delay);
} else {
E x = q.poll();
assert x != null;
if (q.size() != 0)
available.signalAll(); // wake up other takers
return x;
}
}
}
} finally {
lock.unlock();
}
}
-------------------
以下是Sample,是一個(gè)緩存的簡(jiǎn)單實(shí)現(xiàn)。共包括三個(gè)類Pair、DelayItem、Cache。如下:
public class Pair<K, V> {
public K first;
public V second;
public Pair() {}
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
}
--------------
以下是Delayed的實(shí)現(xiàn)
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class DelayItem<T> implements Delayed {
/** Base of nanosecond timings, to avoid wrapping */
private static final long NANO_ORIGIN = System.nanoTime();
/**
* Returns nanosecond time offset by origin
*/
final static long now() {
return System.nanoTime() - NANO_ORIGIN;
}
/**
* Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied
* entries.
*/
private static final AtomicLong sequencer = new AtomicLong(0);
/** Sequence number to break ties FIFO */
private final long sequenceNumber;
/** The time the task is enabled to execute in nanoTime units */
private final long time;
private final T item;
public DelayItem(T submit, long timeout) {
this.time = now() + timeout;
this.item = submit;
this.sequenceNumber = sequencer.getAndIncrement();
}
public T getItem() {
return this.item;
}
public long getDelay(TimeUnit unit) {
long d = unit.convert(time - now(), TimeUnit.NANOSECONDS);
return d;
}
public int compareTo(Delayed other) {
if (other == this) // compare zero ONLY if same object
return 0;
if (other instanceof DelayItem) {
DelayItem x = (DelayItem) other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
}
}
以下是Cache的實(shí)現(xiàn),包括了put和get方法,還包括了可執(zhí)行的main函數(shù)。
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Cache<K, V> {
private static final Logger LOG = Logger.getLogger(Cache.class.getName());
private ConcurrentMap<K, V> cacheObjMap = new ConcurrentHashMap<K, V>();
private DelayQueue<DelayItem<Pair<K, V>>> q = new DelayQueue<DelayItem<Pair<K, V>>>();
private Thread daemonThread;
public Cache() {
Runnable daemonTask = new Runnable() {
public void run() {
daemonCheck();
}
};
daemonThread = new Thread(daemonTask);
daemonThread.setDaemon(true);
daemonThread.setName("Cache Daemon");
daemonThread.start();
}
private void daemonCheck() {
if (LOG.isLoggable(Level.INFO))
LOG.info("cache service started.");
for (;;) {
try {
DelayItem<Pair<K, V>> delayItem = q.take();
if (delayItem != null) {
// 超時(shí)對(duì)象處理
Pair<K, V> pair = delayItem.getItem();
cacheObjMap.remove(pair.first, pair.second); // compare and remove
}
} catch (InterruptedException e) {
if (LOG.isLoggable(Level.SEVERE))
LOG.log(Level.SEVERE, e.getMessage(), e);
break;
}
}
if (LOG.isLoggable(Level.INFO))
LOG.info("cache service stopped.");
}
// 添加緩存對(duì)象
public void put(K key, V value, long time, TimeUnit unit) {
V oldValue = cacheObjMap.put(key, value);
if (oldValue != null)
q.remove(key);
long nanoTime = TimeUnit.NANOSECONDS.convert(time, unit);
q.put(new DelayItem<Pair<K, V>>(new Pair<K, V>(key, value), nanoTime));
}
public V get(K key) {
return cacheObjMap.get(key);
}
// 測(cè)試入口函數(shù)
public static void main(String[] args) throws Exception {
Cache<Integer, String> cache = new Cache<Integer, String>();
cache.put(1, "aaaa", 3, TimeUnit.SECONDS);
Thread.sleep(1000 * 2);
{
String str = cache.get(1);
System.out.println(str);
}
Thread.sleep(1000 * 2);
{
String str = cache.get(1);
System.out.println(str);
}
}
}
運(yùn)行Sample,main函數(shù)執(zhí)行的結(jié)果是輸出兩行,第一行為aaa,第二行為null。