併發集合(四)用優先順序對使用阻塞執行緒安全的列表排序
宣告:本文是《 Java 7 Concurrency Cookbook 》的第六章,作者: Javier Fernández González 譯者:許巧輝 校對:方騰飛
用優先順序對使用阻塞執行緒安全的列表排序
一個典型的需求是,當你需要使用一個有序列表的資料結構時,Java提供的PriorityBlockingQueue類就擁有這種功能。
你想要新增到PriorityBlockingQueue中的所有元素必須實現Comparable介面。這個介面有一個compareTo()方法,它接收同樣型別的物件,你有兩個比較的物件:一個是執行這個方法的物件,另一個是作為引數接收的物件。如果本地物件小於引數,則該方法返回小於0的數值。如果本地物件大於引數,則該方法返回大於0的數值。如果本地物件等於引數,則該方法返回等於0的數值。
PriorityBlockingQueue使用compareTo()方法決定插入元素的位置。(校注:預設情況下)較大的元素將被放在佇列的尾部。
阻塞資料結構(blocking data structure)是PriorityBlockingQueue的另一個重要特性。它有這樣的方法,如果它們不能立即進行它們的操作,則阻塞這個執行緒直到它們的操作可以進行。
在這個指南中,你將學習如何使用PriorityBlockingQueue類實現一個例子,你將在相同的列表上使用不同的優先順序儲存大量事件(event),然後檢查佇列的排序是否是你想要的。
準備工作…
這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。
如何做…
按以下步驟來實現的這個例子:
1.實現Event類,並指定它實現引數化為Event類的Comparable介面。
public class Event implements Comparable<Event> {
2.宣告一個私有的、int型別的屬性thread,用來儲存已建立事件的執行緒數。
private int thread;
3.宣告一個私有的、int型別的屬性priority,用來儲存事件的優先順序。
private int priority;
4.實現這個類的構造器,並初始化它的屬性。
public Event(int thread, int priority){ this.thread=thread; this.priority=priority; }
5.實現getThread()方法,用來返回thread屬性的值。
public int getThread() { return thread; }
6.實現getPriority()方法,用來返回priority屬性的值。
public int getPriority() { return priority; }
7.實現compareTo()方法。它接收Event作為引數,並且比較當前事件與引數的優先順序。如果當前事件的優先順序更大,則返回-1,如果這兩個優先順序相等,則返回0,如果當前事件的優先順序更小,則返回1。注意,這與大多數Comparator.compareTo()的實現是相反的。
@Override public int compareTo(Event e) { if (this.priority>e.getPriority()) { return -1; } else if (this.priority<e.getPriority()) { return 1; } else { return 0; } }
8.建立一個Task類,並指定它實現Runnable介面。
public class Task implements Runnable {
9.宣告一個私有的、int型別的屬性id,用來儲存任務的數字標識。
private int id;
10.宣告一個私有的、引數化為Event類的PriorityBlockingQueue型別的屬性queue,用來儲存任務產生的事件。
private PriorityBlockingQueue<Event> queue;
11.實現這個類的構造器,並初始化它的屬性。
public Task(int id, PriorityBlockingQueue<Event> queue) { this.id=id; this.queue=queue; }
12.實現run()方法。它儲存100個事件到佇列,使用它們的ID來標識建立事件的任務,並給予不斷增加的數作為優先順序。使用add()方法新增事件到佇列中。
@Override public void run() { for (int i=0; i<1000; i++){ Event event=new Event(id,i); queue.add(event); } }
13.實現這個例子的主類,通過建立Main類,並實現main()方法。
public class Main{ public static void main(String[] args) {
14.建立一個引數化為Event類的PriorityBlockingQueue物件。
PriorityBlockingQueue<Event> queue=new PriorityBlockingQueue<>();
15.建立一個有5個Thread物件的陣列,用來儲存執行5個任務的執行緒。
Thread taskThreads[]=new Thread[5];
16.建立5個Task物件。儲存前面建立的執行緒陣列。
for (int i=0; i<taskThreads.length; i++){ Task task=new Task(i,queue); taskThreads[i]=new Thread(task); }
17.啟動前面建立的5個執行緒。
for (int i=0; i<taskThreads.length ; i++) { taskThreads[i].start(); }
18.使用join()方法,等待這5個執行緒的結束。
for (int i=0; i<taskThreads.length ; i++) { try { taskThreads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } }
19.將列隊真實大小和儲存在它裡面的事件寫入到控制檯。使用poll()方法從佇列中取出事件。
System.out.printf("Main: Queue Size: %d\n",queue.size()); for (int i=0; i<taskThreads.length*1000; i++){ Event event=queue.poll(); System.out.printf("Thread %s: Priority %d\n",event. getThread(),event.getPriority()); }
20.將佇列最後的大小寫入到控制檯。
System.out.printf("Main: Queue Size: %d\n",queue.size()); System.out.printf("Main: End of the program\n");
它是如何工作的…
在這個指南中,你已使用PriorityBlockingQueue實現Event物件的一個優先順序佇列。正如我們在引言中提到的,所有儲存在PriorityBlockingQueue的元素必須實現Comparable介面,所以,你已在Event類中實現compareTo()方法。
所有事件都有一個優先順序屬性。擁有更高優先順序的元素將成為佇列的第一個元素。當你已實現compareTo()方法,如果執行這個方法的事件擁有比作為引數傳入的事件更高的優先順序時,它將返回-1。在其他情況下,如果執行這個方法的事件擁有比作為引數傳入的事件更低的優先順序時,它將返回1。如果這兩個物件擁有相同優先順序,compareTo()方法將返回0。在這種情況下,PriorityBlockingQueue類並不能保證元素的順序。
我們已實現Task類來新增Event物件到優先順序佇列中。每個任務物件使用add()方法往佇列新增1000個事件(0到99種優先順序)。
Main類的main()方法建立5個Task物件,並用相應的執行緒執行它們。當所有的執行緒完成它們的執行,你已將所有元素寫入到控制檯。我們使用poll()方法從佇列中獲取元素。這個方法返回並刪除佇列的第一個元素。
以下截圖顯示執行這個程式的部分輸出:
你可以看出這個佇列如何有5000個元素,第一個元素如何擁有最大的優先順序值。
不止這些…
PriorityBlockingQueue類提供其他有趣的方法,以下是其中一些方法的描述:
- clear():這個方法刪除佇列中的所有元素。
- take():這個方法返回並刪除佇列中的第一個元素。如果佇列是空的,這個方法將阻塞執行緒直到佇列有元素。
- put(E e):E是用來引數化PriorityBlockingQueue類的類。這個方法將作為引數傳入的元素插入到佇列中。
- peek():這個方法返回列隊的第一個元素,但不刪除它。
參見