1. 程式人生 > >併發集合(五)使用執行緒安全的、帶有延遲元素的列表

併發集合(五)使用執行緒安全的、帶有延遲元素的列表

宣告:本文是《 Java 7 Concurrency Cookbook 》的第六章,作者: Javier Fernández González     譯者:許巧輝 校對:方騰飛

使用執行緒安全的、帶有延遲元素的列表

DelayedQueue類是Java API提供的一種有趣的資料結構,並且你可以用在併發應用程式中。在這個類中,你可以儲存帶有啟用日期的元素。方法返回或抽取佇列的元素將忽略未到期的資料元素。它們對這些方法來說是看不見的。

為了獲取這種行為,你想要儲存到DelayedQueue類中的元素必須實現Delayed介面。這個介面允許你處理延遲物件,所以你將實現儲存在DelayedQueue物件的啟用日期,這個啟用時期將作為物件的剩餘時間,直到啟用日期到來。這個介面強制實現以下兩種方法:

  • compareTo(Delayed o):Delayed介面繼承Comparable介面。如果執行這個方法的物件的延期小於作為引數傳入的物件時,該方法返回一個小於0的值。如果執行這個方法的物件的延期大於作為引數傳入的物件時,該方法返回一個大於0的值。如果這兩個物件有相同的延期,該方法返回0。
  • getDelay(TimeUnit unit):該方法返回與此物件相關的剩餘延遲時間,以給定的時間單位表示。TimeUnit類是一個列舉類,有以下常量:DAYS、HOURS、 MICROSECONDS、MILLISECONDS、 MINUTES、 NANOSECONDS 和 SECONDS。


在這個例子中,你將學習如何使用DelayedQueue類來儲存一些具有不同啟用日期的事件。

準備工作…

這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。

如何做…

按以下步驟來實現的這個例子:

1.建立一個實現Delayed介面的Event類。

public class Event implements Delayed 

2.宣告一個私有的、Date型別的屬性startDate。

 private Date startDate; 

3.實現這個類的構造器,並初始化它的屬性。

public Event (Date startDate) {this.startDate=startDate;}

4.實現compareTo()方法。它接收一個Delayed物件作為引數。返回當前物件的延期與作為引數傳入物件的延期之間的差異。

<br /><br />@Override<br />public int compareTo(Delayed o) {<br />long result=this.getDelay(TimeUnit.NANOSECONDS)-o.<br />getDelay(TimeUnit.NANOSECONDS);<br />if (result&lt;0) {<br />return -1;<br />} else if (result&gt;0) {<br />return 1;<br />}<br />return 0;<br />}<br /><br />

5.實現getDelay()方法。返回物件的startDate與作為引數接收的TimeUnit的真實日期之間的差異。

public long getDelay(TimeUnit unit) {
Date now=new Date();
long diff=startDate.getTime()-now.getTime();
return unit.convert(diff,TimeUnit.MILLISECONDS);
}

6.建立一個實現Runnable介面的Task類。

public class Task implements Runnable {

7.宣告一個私有的、int型別的屬性id,用來儲存任務的標識數字。

private int id;

8.宣告一個私有的、引數化為Event類的DelayQueue型別的屬性queue。

<br /><br />private DelayQueue&lt;Event&gt; queue;<br /><br />

9.實現這個類的構造器,並初始化它的屬性。

public Task(int id, DelayQueue<Event> queue) {
this.id=id;<br />this.queue=queue;
}

10.實現run()方法。首先,計算任務將要建立的事件的啟用日期。新增等於物件ID的實際日期秒數。

@Override
public void run() {
Date now=new Date();
Date delay=new Date();
delay.setTime(now.getTime()+(id*1000));
System.out.printf("Thread %s: %s\n",id,delay);

11.使用add()方法,在佇列中儲存100個事件。

for (int i=0; i&lt;100; i++) {
Event event=new Event(delay);
queue.add(event);
}

12.通過建立Main類,並實現main()方法,來實現這個例子的主類。

public class Main {
public static void main(String[] args) throws Exception {

13.建立一個引數化為Event類的DelayedQueue物件。

DelayQueue<Event> queue=new DelayQueue<>();

14.建立一個有5個Thread物件的陣列,用來儲存將要執行的任務。

Thread threads[]=new Thread[5];

15.建立5個具有不同IDs的Task物件。

for (int i=0; i&lt;threads.length; i++){<br />Task task=new Task(i+1, queue);<br />threads[i]=new Thread(task);
}

16.開始執行前面建立的5個任務。

for (int i=0; i<threads.length; i++) {
threads[i].start();
}

17.使用join()方法等待任務的結束。

for (int i=0; i<threads.length; i++) {
threads[i].join();
}

18.將儲存在佇列中的事件寫入到控制檯。當佇列的大小大於0時,使用poll()方法獲取一個Event類。如果它返回null,令主執行緒睡眠500毫秒,等待更多事件的啟用。

do {
int counter=0;
Event event;
do {
event=queue.poll();
if (event!=null) counter++;
} while (event!=null);
System.out.printf("At %s you have read %d events\n",new Date(),counter);
TimeUnit.MILLISECONDS.sleep(500);
}while (queue.size()>0);
}
}

它是如何工作的…

在這個指南中,我們已實現Event類。這個類只有一個屬性(表示事件的啟用日期),實現了Delayed介面,所以,你可以在DelayedQueue類中儲存Event物件。

getDelay()方法返回在實際日期和啟用日期之間的納秒數。這兩個日期都是Date類的物件。你已使用getTime()方法返回一個被轉換成毫秒的日期,你已轉換那個值為作為引數接收的TimeUnit。DelayedQueue類使用納秒工作,但這一點對於你來說是透明的。

對於compareTo()方法,如果執行這個方法的物件的延期小於作為引數傳入的物件的延期,該方法返回小於0的值。如果執行這個方法的物件的延期大於作為引數傳入的物件的延期,該方法返回大於0的值。如果這兩個物件的延期相等,則返回0。

你同時實現了Task類。這個類有一個整數屬性id。當一個Task物件被執行,它增加一個等於任務ID的秒數作為實際日期,這是被這個任務儲存在DelayedQueue類的事件的啟用日期。每個Task物件使用add()方法儲存100個事件到佇列中。

最後,在Main類的main()方法中,你已建立5個Task物件,並用相應的執行緒來執行它們。當這些執行緒完成它們的執行,你已使用poll()方法將所有元素寫入到控制檯。這個方法檢索並刪除佇列的第一個元素。如果佇列中沒有任務到期的元素,這個方法返回null值。你呼叫poll()方法,並且如果它返回一個Evnet類,你增加計數器。當poll()方法返回null值時,你寫入計數器的值到控制檯,並且令執行緒睡眠半秒等待更多的啟用事件。當你獲取儲存在佇列中的500個事件,這個程式執行結束。

以下截圖顯示程式執行的部分輸出:

3

你可以看出這個程式當它被啟用時,只獲取100個事件。

注意:你必須十分小心size()方法。它返回列表中的所有元素數量,包含啟用與未啟用元素。

不止這些…

DelayQueue類提供其他有趣方法,如下:

  • clear():這個方法刪除佇列中的所有元素。
  • offer(E e):E是代表用來引數化DelayQueue類的類。這個方法插入作為引數傳入的元素到佇列中。
  • peek():這個方法檢索,但不刪除佇列的第一個元素。
  • take():這具方法檢索並刪除佇列的第一個元素。如果佇列中沒有任何啟用的元素,執行這個方法的執行緒將被阻塞,直到佇列有一些啟用的元素。

參見