JDK1.2引入最有争议性的改变是将集合类默认为不是Thread安全性的。
一、Collection Class的概述
1. 具有Threadsafe 的Collection Class:
java.util.Vector(List) 列表集合,通过索引操作。
java.util.Stack(List) 继承自Vector,提供LIFO的顺序操作push进入,pop出元素。
java.util.Hashtable(Map) 一个简单、无序的key与value的映射。
java.util.concurrent.ConcurrentHashMap 一个实现无序的map的类,比Hashtable使用更少的同步机制。
java.util.concurrent.CopyOnWriteArrayList 提供无同步的Iterator。
java.util.concurrent.ConcurrentLinkedQueue 无限的FIFO队列。
2. Thread-Notification Collection Class
java.util.concurrent包下的线程安全的Queue:
ArrayBlockingQueue:有限的FIFO队列。
LinkedBlockingQueue:可以有限或无限的FIFO 队列。
SynchronousQueue:有限的FIFO队列。一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。
PriorityBlockingQueue:一个*阻塞队列,它使用与类 PriorityQueue 相同的顺序规则,并且提供了阻塞获取操作。
DelayQueue:Delayed 元素的一个*阻塞队列,只有在延迟期满时才能从中提取元素。
二、同步与Collection Class
使用Vector确保线程安全性例子:
import java.util.*; public class CharacterEventHandler { private Vector listeners = new Vector(); public void addCharacterListener(CharacterListener cl) { listeners.add(cl); } public void removeCharacterListener(CharacterListener cl) { listeners.remove(cl); } public void fireNewCharacter(CharacterSource source, int c) { CharacterEvent ce = new CharacterEvent(source, c); CharacterListener[] cl = (CharacterListener[] ) listeners.toArray(new CharacterListener[0]); for (int i = 0; i < cl.length; i++) cl[i].newCharacter(ce); } }
使用非线程安全的ArrayList,使用synchronized method来确保线程安全的例子:
import java.util.*; public class CharacterEventHandler { private ArrayList listeners = new ArrayList(); public synchronized void addCharacterListener(CharacterListener cl) { listeners.add(cl); } public synchronized void removeCharacterListener(CharacterListener cl) { listeners.remove(cl); } public synchronized void fireNewCharacter(CharacterSource source, int c) { CharacterEvent ce = new CharacterEvent(source, c); CharacterListener[] cl = (CharacterListener[] ) listeners.toArray(new CharacterListener[0]); for (int i = 0; i < cl.length; i++) cl[i].newCharacter(ce); } }
使用非线程安全的ArrayList,使用synchronized 块来确保线程安全的例子:
import java.util.*; public class CharacterEventHandler { private ArrayList listeners = new ArrayList(); public void addCharacterListener(CharacterListener cl) { synchronized(listeners) { listeners.add(cl); } } public void removeCharacterListener(CharacterListener cl) { synchronized(listeners) { listeners.remove(cl); } } public void fireNewCharacter(CharacterSource source, int c) { CharacterEvent ce = new CharacterEvent(source, c); CharacterListener[] cl; synchronized(listeners) { cl = (CharacterListener[] ) listeners.toArray(new CharacterListener[0]); } for (int i = 0; i < cl.length; i++) cl[i].newCharacter(ce); } }
复杂的同步
使用线程安全的集合类,就不会出现任何的线程安全问题,例如:竞态条件吗?
答案是否定的,依然会出现问题,当一个方法中涉及两次对同一个集合进行多次操作时就可能出现异常。
import java.util.*; import javax.swing.*; import javax.swing.table.*; public class CharCounter { public HashMap correctChars = new HashMap(); public HashMap incorrectChars = new HashMap(); private AbstractTableModel atm; public void correctChar(int c) { synchronized(correctChars) { Integer key = new Integer(c); Integer num = (Integer) correctChars.get(key); if (num == null) correctChars.put(key, new Integer(1)); else correctChars.put(key, new Integer(num.intValue() + 1)); if (atm != null) atm.fireTableDataChanged(); } } public int getCorrectNum(int c) { synchronized(correctChars) { Integer key = new Integer(c); Integer num = (Integer) correctChars.get(key); if (num == null) return 0; return num.intValue(); } } public void incorrectChar(int c) { synchronized(incorrectChars) { Integer key = new Integer(c); Integer num = (Integer) incorrectChars.get(key); if (num == null) incorrectChars.put(key, new Integer(-1)); else incorrectChars.put(key, new Integer(num.intValue() - 1)); if (atm != null) atm.fireTableDataChanged(); } } public int getIncorrectNum(int c) { synchronized(incorrectChars) { Integer key = new Integer(c); Integer num = (Integer) incorrectChars.get(key); if (num == null) return 0; return num.intValue(); } } public void addModel(AbstractTableModel atm) { this.atm = atm; } }
三、生产者/消费者模式
以切割不同组的Thread的请求来进行异步处理数据。生产者是产生需要被处理的请求的Thread。消费者是接受奈尔请求并予以相应的Thread。这种模式提供了一种清楚的分类让Thread能有更好的设计且能够更容易地调试。
只需提供安全的方法从生产者传递数据给消费者,数据只需要在生产者与消费者之间传递的很短时间中确保线程安全性即可。
可以使用线程安全的集合类:vector、list、queue。
生产者:
import java.util.*; import java.util.concurrent.*; public class FibonacciProducer implements Runnable { private Thread thr; private BlockingQueue<Integer> queue; public FibonacciProducer(BlockingQueue<Integer> q) { queue = q; thr = new Thread(this); thr.start(); } public void run() { try { for(int x=0;;x++) { Thread.sleep(1000); queue.put(new Integer(x)); System.out.println("Produced request " + x); } } catch (InterruptedException ex) { } } }
消费者:
import java.util.concurrent.*; public class FibonacciConsumer implements Runnable { private Fibonacci fib = new Fibonacci(); private Thread thr; private BlockingQueue<Integer> queue; public FibonacciConsumer(BlockingQueue<Integer> q) { queue = q; thr = new Thread(this); thr.start(); } public void run() { int request, result; try { while (true) { request = queue.take().intValue(); result = fib.calculateWithCache(request); System.out.println("Calculated result of " + result + " from " + request); } } catch (InterruptedException ex) { } } }
生产者与消费者是去耦的,生产者绝不会直接调用消费者。
四、使用Collection Class
使用哪个集合类最好呢?
使用没有同步化的集合类会有小小的性能提升。
对许多有竞争的算法,考虑改用并发的Collection
生产者/消费者考虑使用Queue替代集合类。
尽量减少同步的使用