1. 程式人生 > >併發集合(二)使用非阻塞執行緒安全的列表

併發集合(二)使用非阻塞執行緒安全的列表

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

使用非阻塞執行緒安全的列表

列表(list)是最基本的集合。一個列表有不確定的元素數量,並且你可以新增、讀取和刪除任意位置上的元素。併發列表允許不同的執行緒在同一時刻對列表的元素進行新增或刪除,而不會產生任何資料不一致(問題)。

在這個指南中,你將學習如何在你的併發應用程式中使用非阻塞列表。非阻塞列表提供這些操作:如果操作不能立即完成(比如,你想要獲取列表的元素而列表卻是空的),它將根據這個操作丟擲異常或返回null值。Java 7引進實現了非阻塞併發列表的ConcurrentLinkedDeque類。

我們將使用以下兩種不同任務來實現一個例子:

  • 大量新增資料到列表
  • 在同個列表中,大量刪除資料

準備工作…

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

如何做…

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

1.建立一個實現Runnable介面的AddTask類:

public class AddTask implements Runnable {

2.宣告一個私有的、ConcurrentLinkedDeque型別的、引數化為String類的屬性list。

private ConcurrentLinkedDeque<String> list;

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

public AddTask(ConcurrentLinkedDeque<String> list) {
this.list=list;
}

4.實現這個類的run()方法。它將在列表中儲存10000個正在執行任務的執行緒的名稱和一個數字的字串。

@Override
public void run() {
String name=Thread.currentThread().getName();
for (int i=0; i<10000; i++){
list.add(name+": Element "+i);
}
}

5.建立一個實現Runnable介面的PollTask類。

public class PollTask implements Runnable {

6.宣告一個私有的、ConcurrentLinkedDeque型別的、引數化為String類的屬性list。

private ConcurrentLinkedDeque<String> list;

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

public PollTask(ConcurrentLinkedDeque<String> list) {
this.list=list;
}

8.實現這個類的run()方法。它從列表中取出10000個元素(在一個迴圈5000次的迴圈中,每次取出2個元素)。

@Override
public void run() {
for (int i=0; i<5000; i++) {
list.pollFirst();
list.pollLast();
}
}

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

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

10.建立一個引數化為String、名為list的ConcurrentLinkedDeque物件。

ConcurrentLinkedDeque<String> list=new ConcurrentLinkedDeque<>();

11.建立一個儲存100個Thread物件的陣列threads。

Thread threads[]=new Thread[100];

12.建立100個AddTask物件,對於它們中的每一個用一個執行緒來執行。用之前建立的陣列來儲存每個執行緒,並啟動這些執行緒。

for (int i=0; i<threads.length ; i++){
AddTask task=new AddTask(list);
threads[i]=new Thread(task);
threads[i].start();
}
System.out.printf("Main: %d AddTask threads have been
launched\n",threads.length);

13.使用join()方法,等待這些執行緒的完成。

for (int i=0; i<threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

14.將列表的大小寫入控制檯。

System.out.printf("Main: Size of the List: %d\n",list.size());

15.建立100個PollTask物件,對於它們中的每一個用一個執行緒來執行。用之前建立的陣列來儲存每個執行緒,並啟動這些執行緒。

for (int i=0; i< threads.length; i++){
PollTask task=new PollTask(list);
threads[i]=new Thread(task);
threads[i].start();
}
System.out.printf("Main: %d PollTask threads have been
launched\n",threads.length);

16.使用join()方法,等待這些執行緒的完成。

for (int i=0; i<threads.length; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

17.將列表的大小寫入控制檯。

System.out.printf("Main: Size of the List: %d\n",list.size());

它是如何工作的…

在這個指南中,我們已使用引數化為String類的ConcurrentLinkedDeque物件來處理非阻塞併發列表的資料。以下截圖顯示這個例子執行的輸出:

1

首先,你已執行100個AddTask任務來給列表新增元素。每個任務使用add()方法新增10000個元素到列表。這個方法將新元素插入到列表的尾部。當這些任務全部完成,你已在控制檯列印這個列表元素的數量。此時,列表有1000000個元素。

然後,你執行100個PollTask任務從列表中刪除元素。每個任務使用pollFirst()和pollLast()方法刪除列表的10000個元素。pollFirst()方法返回並刪除列表的第一個元素,pollLast()方法返回並刪除列表的最後一個元素。如果列表為空,這些方法將返回一個null值。當這些任務全部完成,你已在控制檯列印這個列表元素的數量。此時,列表有0個元素。

你使用size()方法,列印列表元素的數量。你必須考慮到這個方法會返回一個並不真實的值,尤其是如果你使用這個方法,而有執行緒正在新增或刪除列表的資料。這個方法必須遍歷整個列表來計算元素而對於這個操作列表的內容可以改變。只有在沒有任何執行緒修改列表的情況下,你使用這個方法時,你將保證這個返回值是正確的。

不止這些…

ConcurrentLinkedDeque類提供更多方法來獲取列表的元素:

  • getFirst()和getLast():這些方法將分別返回列表的第一個和最後一個元素。它們不會從列表刪除返回的元素。如果列表為空,這些方法將丟擲NoSuchElementExcpetion異常。
  • peek()、peekFirst()和peekLast():這些方法將分別返回列表的第一個和最後一個元素。它們不會從列表刪除返回的元素。如果列表為空,這些方法將返回null值。
  • remove()、removeFirst()、 removeLast():這些方法將分別返回列表的第一個和最後一個元素。它們將從列表刪除返回的元素。如果列表為空,這些方法將丟擲NoSuchElementExcpetion異常。