Concurrency: Performance Tuning
Contents
1. Comparing Mutex Technologies
Lock
的效率通常比synchronized
高,但使用synchronized
具有更好的可读性。如果加锁的代码比较耗时,耗费在加锁和解锁上的时间就会显得微不足道。
2. Lock-Free Containers
Java SE5加入了新的线程安全的容器类,不使用互斥锁,提高了性能。其基本策略是,对容器的修改和读取可以同时进行:对容器的修改会先在单独的副本上完成,修改完成后,被修改的副本会替换掉原容器中的对应内容;从容器中进行读取时,只能读取到已经完成的修改。
CopyOnWriteArrayList
会在被修改时创建整个数组的副本,原数组还会被保留,提供安全的读取。修改完成后,原数组会被替换为修改后的数组(这个替换是一个原子操作),之后的读取就可以获得修改后的数据。CopyOnWriteArrayList
不会在多个迭代器遍历和修改列表时抛出ConcurrentModificationException
。
CopyOnWriteArraySet
使用CopyOnWriteArrayList
实现Lock-Free。
ConcurrentHashMap
和ConcurrentLinkedQueue
也使用类似的方式来允许同时读写,但在写入时只会有部分内容被复制。
3. Optimistic Locking
部分Atomic
类允许乐观锁定(Optimistic Locking),在进行计算时不使用互斥锁,计算完成后,再通过compareAndSet()
更新Atomic
类。compareAndSet()
需要同时提供一个旧值和一个新值,如果旧值和当前Atomic
对象的值不同(其他任务也同时进行了修改),compareAndSet()
会失败。通常我们使用互斥锁来防止多个任务同时修改同一个对象;但有时候我们会乐观的认为要修改的对象不会同时被其他任务修改,不使用锁定,从而获得性能上的提升。
4. ReadWriteLocks
ReadWriteLock
可以在没有写入的时候允许多个任务同时读取共享资源。当有任务进行写入时,写锁定被打开,此时所有任务都无法读取,直到写锁定释放。
对于写入较少而有多个任务读取的场景,ReadWriteLock
会产生一定的优化效果。
一个例子如下:
import java.util.concurrent.*; import java.util.concurrent.locks.*; import java.util.*; public class ReaderWriterList<T> { private ArrayList<T> lockedList; // Make the ordering fair: private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); public ReaderWriterList(int size, T initialValue) { lockedList = new ArrayList<T>( Collections.nCopies(size, initialValue)); } public T set(int index, T element) { Lock wlock = lock.writeLock(); wlock.lock(); try { return lockedList.set(index, element); } finally { wlock.unlock(); } } public T get(int index) { Lock rlock = lock.readLock(); rlock.lock(); try { // Show that multiple readers // may acquire the read lock: if(lock.getReadLockCount() > 1) System.out.println(lock.getReadLockCount()); return lockedList.get(index); } finally { rlock.unlock(); } } public static void main(String[] args) throws Exception { new ReaderWriterListTest(30, 1); } } class ReaderWriterListTest { ExecutorService exec = Executors.newCachedThreadPool(); private final static int SIZE = 100; private static Random rand = new Random(47); private ReaderWriterList<Integer> list = new ReaderWriterList<Integer>(SIZE, 0); private class Writer implements Runnable { public void run() { try { for(int i = 0; i < 20; i++) { // 2 second test list.set(i, rand.nextInt()); TimeUnit.MILLISECONDS.sleep(100); } } catch(InterruptedException e) { // Acceptable way to exit } System.out.println("Writer finished, shutting down"); exec.shutdownNow(); } } private class Reader implements Runnable { public void run() { try { while(!Thread.interrupted()) { for(int i = 0; i < SIZE; i++) { list.get(i); TimeUnit.MILLISECONDS.sleep(1); } } } catch(InterruptedException e) { // Acceptable way to exit } } } public ReaderWriterListTest(int readers, int writers) { for(int i = 0; i < readers; i++) exec.execute(new Reader()); for(int i = 0; i < writers; i++) exec.execute(new Writer()); } }