併發集合(六)使用執行緒安全的NavigableMap
宣告:本文是《 Java 7 Concurrency Cookbook 》的第六章,作者: Javier Fernández González 譯者:許巧輝 校對:方騰飛
使用執行緒安全的NavigableMap
Java API 提供的有趣的資料結構,並且你可以在併發應用程式中使用,它就是ConcurrentNavigableMap介面的定義。實現ConcurrentNavigableMap介面的類儲存以下兩部分元素:
- 唯一標識元素的key
- 定義元素的剩餘資料
每部分在不同的類中實現。
Java API 也提供了這個介面的實現類,這個類是ConcurrentSkipListMap,它實現了非阻塞列表且擁有ConcurrentNavigableMap的行為。在內部實現中,它使用Skip List來儲存資料。Skip List是基於並行列表的資料結構,它允許我們獲取類似二叉樹的效率。使用它,你可以得到一個排序的資料結構,這比排序數列使用更短的訪問時間來插入、搜尋和刪除元素。
注意:在1990年,由William Pugh引入Skip List。
當你往map中插入資料時,它使用key來排序它們,所以,所有元素將是有序的。除了返回具體的元素,這個類也提供了獲取map的子map的方法。
在這個指南中,你將學習如何使用ConcurrentSkipListMap類來實現一個通訊錄的map。
準備工作…
這個指南的例子使用Eclipse IDE實現。如果你使用Eclipse或其他IDE,如NetBeans,開啟它並建立一個新的Java專案。
如何做…
按以下步驟來實現的這個例子:
1.建立一個Contact類。
public class Contact {
2.宣告兩個私有的、String型別的屬性name和phone。
private String name; private String phone;
3.實現這個類的構造器,並初始化它的屬性。
public Contact(String name, String phone) { this.name=name; this.phone=phone; }
4.實現返回name和phone屬性值的方法。
public String getName() { return name; } public String getPhone() { return phone; }
5.建立一個Task類,並指定它實現Runnable介面。
public class Task implements Runnable {
6.宣告一個私有的、引數化為String類和Contact類的ConcurrentSkipListMap型別的屬性map。
private ConcurrentSkipListMap<String, Contact> map;
7.宣告一個私有的、String型別的屬性id,用來儲存當前任務的ID。
private String id; [/code[ 8.實現這個類的構造器,用來儲存它的屬性。 public Task (ConcurrentSkipListMap<String, Contact> map, String id) { this.id=id; this.map=map; }
9.實現run()方法。使用任務的ID和建立Contact物件的增長數,在map中儲存1000個不同的通訊錄。使用put()方法新增通訊錄到map中。
@Override public void run() { for (int i=0; i<1000; i++) { Contact contact=new Contact(id, String.valueOf(i+1000)); map.put(id+contact.getPhone(), contact); } }
10.通過建立Main類,並新增main()方法來實現這個例子的主類。
public class Main { public static void main(String[] args) {
11.建立一個引數化為String類和Contact類的ConcurrentSkipListMap物件map。
ConcurrentSkipListMap<String, Contact> map; map=new ConcurrentSkipListMap<>();
12.建立一個有25個Thread物件的陣列,用來儲存你將要執行的所有任務。
Thread threads[]=new Thread[25]; int counter=0;
13.建立和啟動25個任務,對於每個任務指定一個大寫字母作為ID。
for (char i='A'; i<'Z'; i++) { Task task=new Task(map, String.valueOf(i)); threads[counter]=new Thread(task); threads[counter].start(); counter++; }
14.使用join()方法等待執行緒的結束。
for (int i=0; i<25; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } }
15.使用firstEntry()方法獲取map的第一個實體,並將它的資料寫入到控制檯。
System.out.printf("Main: Size of the map: %d\n",map.size()); Map.Entry<String, Contact> element; Contact contact; element=map.firstEntry(); contact=element.getValue(); System.out.printf("Main: First Entry: %s: %s\n",contact. getName(),contact.getPhone());
16.使用lastEntry()方法獲取map的最後一個實體,並將它的資料寫入到控制檯。
element=map.lastEntry(); contact=element.getValue(); System.out.printf("Main: Last Entry: %s: %s\n",contact. getName(),contact.getPhone());
17.使用subMap()方法獲取map的子map,並將它們的資料寫入到控制檯。
System.out.printf("Main: Submap from A1996 to B1002: \n"); ConcurrentNavigableMap<String, Contact> submap=map. subMap("A1996", "B1002"); do { element=submap.pollFirstEntry(); if (element!=null) { contact=element.getValue(); System.out.printf("%s: %s\n",contact.getName(),contact. getPhone()); } } while (element!=null); }
它是如何工作的...
在這個指南中,我們已實現Task類來儲存Contact物件到NavigableMap 中。每個通訊錄都有一個名稱(建立它的任務的ID的)和電話號碼(1000到2000之間的數字)。我們已使用這些值的連續值作為通訊錄的key。每個Task物件建立1000個通訊錄,並使用put()方法將它們儲存到NavigableMap中。
注意:如果你插入的key已存在,那麼這個key的元素將被新的元素取代。
Main類的main()方法建立25個Task物件,並使用A-Z的字母作為IDs。然後,你已使用一些方法從map中獲取資料。firstEntry()方法返回map第一個元素的Map.Entry物件,且不會刪除這個元素。這個物件包含key和元素。你已呼叫getValue()方法來獲取元素。你可以使用getKey()來獲取元素的key。
lastEntry()方法返回map最後一個元素的Map.Entry物件,subMap()方法返回map的部分元素的ConcurrentNavigableMap物件。在這個例子中,元素擁有A1996到B1002之間的key。在這種情況下,你可以使用pollFirst()方法來處理subMap()方法返回的這些元素。這個方法將返回並刪除submap中的第一個Map.Entry物件。
以下截圖顯示了程式執行的輸出:
不止這些...
ConcurrentSkipListMap類有其他有趣的方法,這些方法如下:
- headMap(K toKey):K是引數化ConcurrentSkipListMap物件的Key值的類。返回此對映的部分檢視,其鍵值小於 toKey。
- tailMap(K fromKey):K是引數化ConcurrentSkipListMap物件的Key值的類。返回此對映的部分檢視,其鍵大於等於 fromKey。
- putIfAbsent(K key, V Value):如果key不存在map中,則這個方法插入指定的key和value。
- pollLastEntry():這個方法返回並刪除map中最後一個元素的Map.Entry物件。
- replace(K key, V Value):如果這個key存在map中,則這個方法將指定key的value替換成新的value。
參見
- 在第6章,併發集合中的使用非阻塞執行緒安全的數列指南