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()方法。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭