Lock的实现类ReentrantLock的构造函数提供了两种锁获取公平性选择,公平与非公平,默认为非公平,这相比传统的内部锁只使用非公平机制提供了更灵活的选择。


何谓公平与非公平?简单的说就是非公平竞争机制允许插队,而公平竞争机制不允许插队。公平竞争机制下,所有线程都必须排队按顺序来获得锁,即先到先服务。非公平竞争机制下,新线程在请求锁时如果锁刚好释放,可以及时抢占锁,而不让给排在队头的线程,但如果锁已经被占用新线程也只能排队,排在队列中的线程还是先到先服务,所以保证每个线程都会最后得到锁以做到相对公平。


公平竞争机制下,刚开始锁可用:

  1. A请求锁,因为锁可用所以A直接占有锁。

  2. B请求锁,因为锁已经被占用所以B进入等待队列。

  3. C请求锁同时A释放锁,因为B已经在等待队列,所以C直接被放入等待队列,而B被唤醒并获取锁。


非公平竞争机制下,刚开始锁可用:

  1. A请求锁,因为锁可用所以A直接占有锁。

  2. B请求锁,以为锁已经被占用所以B进入等待队列。

  3. C请求锁同时A释放锁,此时虽然有B再等待队列而且会被唤醒,但C会直接参与竞争获取锁,一般情况下C会先获取到锁,而B则继续等待,这对B不公平,但B只能眼睁睁的看见锁被占。但恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟,所以如果C占用锁的时间不长,B被唤醒后C已经释放锁,此时B可以直接占用锁,这造成一种两全其美的局面,C线程见缝插针充分利用锁资源,大大提高系统吞吐量,何乐不为?这也是在竞争激烈的场景下,非公平锁的性能比公平锁好的原因。


那么实际应用中究竟选择公平锁还是非公平锁?据统计(本人的测试结果与统计相左),一般场景下非公平锁的性能好于公平锁,而且除非算法依赖于线程公平排队顺序,正常场景下非公平锁都能使程序正常运行,所以无特殊情况推荐使用非公平锁。哪些情况使用非公平锁:

  1. 算法依赖于公平的排队算法

  2. 线程持有锁的时间比较长

  3. 线程请求锁的平均间隔时间比较长


最后,谈谈自己对内部锁、公平锁、非公平锁的性能测试结果。测试环境为Win7 64bbit + JDK7 + 4GB + i5 4*2.67G,从测试结果看,线程少时(小于1000),性能相差无几,公平锁和内部锁反而微胜非公平锁,这也验证了内部锁的性能已经大大提升的说法;但当线程越多时竞争越激烈就能体现出非公平锁的优势。因为公平锁主要用于特殊场景,所以比较它们的性能意义不大,本测试代码只供参考。


import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLockFairness {
    private static final int JOB_NUM = 100;
    private static final int JOB_LOAD_FACTOR = 500;
                                        
    private Lock lock;
    private volatile boolean isUsingLock;
    private CountDownLatch countDownLatch;
                                        
    private void doShortJobWithLock(){
        lock.lock();
        try{
            for(int i=0; i<100; i++){
                ;;
            }
        }finally{
            lock.unlock();
        }
                                            
        for(int i=0; i<10000; i++){
            ;;
        }
    }
                                        
    private void doLongJobWithLock(){
        lock.lock();
        try{
            try {
                TimeUnit.MICROSECONDS.sleep(JOB_LOAD_FACTOR);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }finally{
            lock.unlock();
        }
                                            
        for(int i=0; i<10000; i++){
            ;;
        }
    }
                                        
    private void doShortJob(){
        synchronized(this){
            for(int i=0; i<100; i++){
                ;;
            }
        }
                                                    
        for(int i=0; i<10000; i++){
            ;;
        }
    }
                                        
    private void doLongJob(){    
        synchronized(this){
            try {
                TimeUnit.MICROSECONDS.sleep(JOB_LOAD_FACTOR);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
                                                        
        for(int i=0; i<10000; i++){
            ;;
        }
    }
                                        
    private class ShortJobWorker implements Runnable{    
        @Override
        public void run() {
            for(int i=0; i<JOB_NUM; i++){
                if(isUsingLock){
                    doShortJobWithLock();
                }
                else{
                    doShortJob();
                }
            }
                                                
            countDownLatch.countDown();
        }    
    }
                                        
    private class LongJobWorker implements Runnable{
        @Override
        public void run() {
            for(int i=0; i<JOB_NUM; i++){
                if(isUsingLock){
                    doLongJobWithLock();
                }
                else{
                    doLongJob();
                }
            }
                                                
            countDownLatch.countDown();
        }    
    }
    public static void main(String[] args) throws InterruptedException {
        TestLockFairness testLockFairness = new TestLockFairness();
        System.out.println("Type\tThreads\tTime");
        testLockFairness.test("synchronized", false, 10, null);
        testLockFairness.test("unfair Lock", true, 10, new ReentrantLock(false));
        testLockFairness.test("fair Lock  ", true, 10, new ReentrantLock(true));
                                            
        testLockFairness.test("synchronized", false, 100, null);
        testLockFairness.test("unfair Lock", true, 100, new ReentrantLock(false));
        testLockFairness.test("fair Lock  ", true, 100, new ReentrantLock(true));
                                            
        testLockFairness.test("synchronized", false, 500, null);
        testLockFairness.test("unfair Lock", true, 500, new ReentrantLock(false));
        testLockFairness.test("fair Lock  ", true, 500, new ReentrantLock(true));
    }
    private void test(String type, boolean isUsingLock, int threadNum, Lock lock) throws InterruptedException {
        this.isUsingLock = isUsingLock;
        this.lock = lock;
        countDownLatch = new CountDownLatch(2*threadNum);    
                                            
        long beginTime = System.currentTimeMillis();
                                            
        for(int i=0; i<threadNum; i++){
            new Thread(new LongJobWorker()).start();
            new Thread(new ShortJobWorker()).start();
        }
                                            
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(type + "        \t" + 2*threadNum + "   \t" + (endTime - beginTime));
    }
}


参考:《Java Currency in Practice》