並發集合(五)使用線程安全的、帶有延遲元素的列錶

杜老師說 2022-01-07 14:42:25 阅读数:413

集合 使用 安全 元素

聲明:本文是《 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的實際日期秒數。

@Overridepublic 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():這具方法檢索並删除隊列的第一個元素。如果隊列中沒有任何激活的元素,執行這個方法的線程將被阻塞,直到隊列有一些激活的元素。

參見

FavoriteLoading添加本文到我的收藏
版权声明:本文为[杜老師說]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071442249191.html