Java線程學習筆記之併發集合類

  

BlockingQueue

BlockingQueue接口表示它是一個Queue,意思是它的項以先入先出(FIFO)順序存儲。在特定順序插入的項以相同的順序檢索,但是需要附加保證,從空隊列檢索一個項的任何嘗試都會阻塞調用線程,直到這個項準備好被檢索。同理,想要將一個項插入到滿隊列的嘗試也會導致阻塞調用線程,直到隊列的存儲空間可用。BlockingQueue乾淨利落地解決了如何將一個線程收集的項“傳遞”給另一線程用於處理的問題,無需考慮同步問題。

  • ArrayBlockingQueue :一個由數組支持的有界隊列。
  • LinkedBlockingQueue :一個由鏈接節點支持的可選有界隊列。
  • PriorityBlockingQueue :一個由優先級堆支持的無界優先級隊列。
  • DelayQueue :一個由優先級堆支持的、基於時間的調度隊列。
  • SynchronousQueue :一個利用 BlockingQueue 接口的簡單聚集(rendezvous)機制。


前兩個類 ArrayBlockingQueueLinkedBlockingQueue幾乎相同,只是在後備存儲器方面有所不同, LinkedBlockingQueue並不總是有容量界限。無大小界限的LinkedBlockingQueue類在添加元素時永遠不會有阻塞隊列的等待(至少在其中有Integer.MAX_VALUE 元素之前不會)。

PriorityBlockingQueue是具有無界限容量的隊列,它利用所包含元素的 Comparable排序順序來以邏輯順序維護元素。可以將它看作TreeSet的可能替代物。例如,在隊列中加入字符串 One、Two、Three 和 Four 會導致 Four 被第一個取出來。對於沒有天然順序的元素,可以爲構造函數提供一個Comparator。不過對 PriorityBlockingQueue 有一個技巧。從 iterator() 返回的 terator實例不需要以優先級順序返回元素。如果必須以優先級順序遍歷所有元素,那麼讓它們都通過toArray() 方法並自己對它們排序,像Arrays.sort(pq.toArray())

DelayQueue實現可能是其中最有意思(也是最複雜)的一個。加入到隊列中的元素必須實現新的 Delayed接口。因爲隊列的大小沒有界限,使得添加可以立即返回,但是在延遲時間過去之前,不能從隊列中取出元素。如果多個元素完成了延遲,那麼最早失效/失效時間最長的元素將第一個取出。實際上沒有聽上去這樣複雜。

之前實現的生產者消費者就可以實現得更優雅了,效率應該也會更高的吧(沒做性能測試,猜測)。

代碼如下:MyBlockingQueue.Java

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
 
class Producer implements Runnable {
	private BlockingQueue drop;
	List messages = Arrays.asList("Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?");
 
	public Producer(BlockingQueue d) {
		this.drop = d;
	}
 
	public void run() {
		try {
			for (String s : messages)
				drop.put(s);
			drop.put("DONE");
		} catch (InterruptedException intEx) {
			System.out.println("Interrupted! " + "Last one out, turn out the lights!");
		}
	}
}
 
class Consumer implements Runnable {
	private BlockingQueue drop;
 
	public Consumer(BlockingQueue d) {
		this.drop = d;
	}
 
	public void run() {
		try {
			String msg = null;
			while (!((msg = drop.take()).equals("DONE")))
				System.out.println(Thread.currentThread().getName() + " cost: " + msg);
		} catch (InterruptedException intEx) {
			System.out.println("Interrupted! " + "Last one out, turn out the lights!");
		}
	}
}
 
public class MyBlockingQueue {
	public static void main(String[] args) {
		BlockingQueue drop = new ArrayBlockingQueue(1, true);
		(new Thread(new Producer(drop), "Producer")).start();
		(new Thread(new Consumer(drop), "Consumer1")).start();
		(new Thread(new Consumer(drop), "Consumer2")).start();
	}
}


DelayQueue的示例代碼,MyDelay.java

 

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
 
class NanoDelay implements Delayed {
	long trigger;
 
	NanoDelay(long i) {
		trigger = System.nanoTime() + i;
	}
 
	public boolean equals(Object other) {
		return ((NanoDelay) other).trigger == trigger;
	}
 
	public boolean equals(NanoDelay other) {
		return ((NanoDelay) other).trigger == trigger;
	}
 
	public long getTriggerTime() {
		return trigger;
	}
 
	public String toString() {
		return String.valueOf(trigger);
	}
 
	@Override
	public int compareTo(Delayed o) {
		long i = trigger;
		long j = ((NanoDelay) o).trigger;
		if (i < j) 			return -1; 		if (i > j)
			return 1;
 
		return 0;
	}
 
	@Override
	public long getDelay(TimeUnit unit) {
		long n = trigger - System.nanoTime();
		return unit.convert(n, TimeUnit.NANOSECONDS);
	}
}
 
public class MyDelay {
 
	public static void main(String args[]) throws InterruptedException {
		Random random = new Random();
		BlockingQueue queue = new DelayQueue();
 
		for (int i = 0; i < 5; i++) {
			queue.add(new NanoDelay(random.nextInt(1000)));
		}
 
		long last = 0;
		for (int i = 0; i < 5; i++) {
			NanoDelay delay = queue.take();
			long tt = delay.getTriggerTime();
			System.out.println("Trigger time: " + tt);
			if (i != 0) {
				System.out.println("Delta: " + (tt - last));
			}
			last = tt;
		}
	}
}


 

SynchronousQueue的示例代碼,MySynchronousQueues.java

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
 
class Producer implements Runnable {
	private BlockingQueue drop;
	List messages = Arrays.asList("Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?");
 
	public Producer(BlockingQueue d) {
		this.drop = d;
	}
 
	public void run() {
		try {
			for (String s : messages)
				drop.put(s);
			drop.put("DONE");
		} catch (InterruptedException intEx) {
			System.out.println("Interrupted! " + "Last one out, turn out the lights!");
		}
	}
}
 
class Consumer implements Runnable {
	private BlockingQueue drop;
 
	public Consumer(BlockingQueue d) {
		this.drop = d;
	}
 
	public void run() {
		try {
			String msg = null;
			while (!((msg = drop.take()).equals("DONE")))
				System.out.println(Thread.currentThread().getName() + " cost: " + msg);
		} catch (InterruptedException intEx) {
			System.out.println("Interrupted! " + "Last one out, turn out the lights!");
		}
	}
}
 
public class MySynchronousQueues {
	public static void main(String[] args) {
		BlockingQueue drop = new SynchronousQueue();
		(new Thread(new Producer(drop), "Producer")).start();
		(new Thread(new Consumer(drop), "Consumer1")).start();
		(new Thread(new Consumer(drop), "Consumer2")).start();
	}
}


 

ConcurrentMap

Map有一個微妙的併發 bug,ConcurrentMap是最容易的解決方案。當一個Map被從多個線程訪問時,通常使用containsKey() 或者get() 來查看給定鍵是否在存儲鍵/值對之前出現。但是即使有一個同步的Map,線程還是可以在這個過程中潛入,然後奪取對 Map的控制權。問題是,在對put()的調用中,鎖在 get()開始時獲取,然後在可以再次獲取鎖之前釋放。它的結果是個競爭條件:這是兩個線程之間的競爭,結果也會因誰先運行而不同。如果兩個線程幾乎同時調用一個方法,兩者都會進行測試,調用 put,在處理中丟失第一線程的值。ConcurrentHashMap實現只能在鍵不存在時將元素加入到map中,只有在鍵存在並映射到特定值時才能從map中刪除一個元素。有一個新的putIfAbsent() 方法用於在 map 中進行添加。這個方法以要添加到 ConcurrentMap實現中的鍵的值爲參數,就像普通的 put() 方法,但是只有在map不包含這個鍵時,才能將鍵加入到map中。如果map已經包含這個鍵,那麼這個鍵的現有值就會保留。 putIfAbsent()方法是原子的。

CopyOnWriteArrayList/CopyOnWriteArraySet

這是ArrayList的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的複製來實現的。這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更有效。在不能或不想進行同步遍歷,但又需要從併發線程中排除衝突時,它也很有用。“快照”風格的迭代器方法在創建迭代器時使用了對數組狀態的引用。此數組在迭代器的生存期內不會更改,因此不可能發生衝突,並且迭代器保證不會拋出ConcurrentModificationException。創建迭代器以後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操作(remove、set 和 add)不受支持。這些方法將拋出UnsupportedOperationException

有一點需要注意的是,調用add()方法都要引起數組拷貝,所以儘量使用addAll(),避免在循環中使用add()方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章