Concurrency: Performance Tuning

1. Comparing Mutex Technologies

  Lock的效率通常比synchronized高,但使用synchronized具有更好的可读性。如果加锁的代码比较耗时,耗费在加锁和解锁上的时间就会显得微不足道。

2. Lock-Free Containers

  Java SE5加入了新的线程安全的容器类,不使用互斥锁,提高了性能。其基本策略是,对容器的修改和读取可以同时进行:对容器的修改会先在单独的副本上完成,修改完成后,被修改的副本会替换掉原容器中的对应内容;从容器中进行读取时,只能读取到已经完成的修改。

  CopyOnWriteArrayList会在被修改时创建整个数组的副本,原数组还会被保留,提供安全的读取。修改完成后,原数组会被替换为修改后的数组(这个替换是一个原子操作),之后的读取就可以获得修改后的数据。CopyOnWriteArrayList不会在多个迭代器遍历和修改列表时抛出ConcurrentModificationException

  CopyOnWriteArraySet使用CopyOnWriteArrayList实现Lock-Free。

  ConcurrentHashMapConcurrentLinkedQueue也使用类似的方式来允许同时读写,但在写入时只会有部分内容被复制。

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());
    }
}