【JDK源码】同步系列AQS之ReentrantLock VS synchronized

简介

可以先参考:ReentrantLocksynchronized

synchronized是Java原生提供的用于在多线程环境中保证同步的关键字,底层是通过修改对象头中的MarkWord来实现的。

ReentrantLock是Java语言层面提供的用于在多线程环境中保证同步的,底层是通过原子更新状态变量state来实现的。

ReentrantLock VS synchronized

功能 ReentrantLock synchronized
可重入 支持 支持
非公平 支持(默认) 支持
加锁/解锁方式 需要手动加锁、解锁,一般使用try…finally…保证锁能够释放 手动加锁,无需刻意解锁
按key锁 不支持,比如按用户id加锁 支持,synchronized加锁时需要传入一个对象
公平锁 支持,new ReentrantLock(true) 不支持
中断 支持,lockInterruptibly() 不支持
尝试加锁 支持,tryLock() 不支持
超时锁 支持,tryLock(timeout, unit) 不支持
获取当前线程获取锁的次数 支持,getHoldCount() 不支持
获取等待的线程 支持,getWaitingThreads() 不支持
检测是否被当前线程占有 支持,isHeldByCurrentThread() 不支持
检测是否被任意线程占有 支持,isLocked() 不支持
条件锁 可支持多个条件,condition.await(),condition.signal(),condition.signalAll() 只支持一个,obj.wait(),obj.notify(),obj.notifyAll()

对比测试

为了全面对比,这里把AtomicInteger和LongAdder也拿来一起对比了

public class ReentrantLockVsSynchronizedTest {
    
    
    public static AtomicInteger a = new AtomicInteger(0);
    public static LongAdder b = new LongAdder();
    public static int c = 0;
    public static int d = 0;
    public static int e = 0;

    public static final ReentrantLock fairLock = new ReentrantLock(true);
    public static final ReentrantLock unfairLock = new ReentrantLock();


    public static void main(String[] args) throws InterruptedException {
    
    
        System.out.println("-------------------------------------");
        testAll(1, 100000);
        System.out.println("-------------------------------------");
        testAll(2, 100000);
        System.out.println("-------------------------------------");
        testAll(4, 100000);
        System.out.println("-------------------------------------");
        testAll(6, 100000);
        System.out.println("-------------------------------------");
        testAll(8, 100000);
        System.out.println("-------------------------------------");
        testAll(10, 100000);
        System.out.println("-------------------------------------");
        testAll(50, 100000);
        System.out.println("-------------------------------------");
        testAll(100, 100000);
        System.out.println("-------------------------------------");
        testAll(200, 100000);
        System.out.println("-------------------------------------");
        testAll(500, 100000);
        System.out.println("-------------------------------------");
//        testAll(1000, 1000000);
        System.out.println("-------------------------------------");
        testAll(500, 10000);
        System.out.println("-------------------------------------");
        testAll(500, 1000);
        System.out.println("-------------------------------------");
        testAll(500, 100);
        System.out.println("-------------------------------------");
        testAll(500, 10);
        System.out.println("-------------------------------------");
        testAll(500, 1);
        System.out.println("-------------------------------------");
    }

    public static void testAll(int threadCount, int loopCount) throws InterruptedException {
    
    
        testAtomicInteger(threadCount, loopCount);
        testLongAdder(threadCount, loopCount);
        testSynchronized(threadCount, loopCount);
        testReentrantLockUnfair(threadCount, loopCount);
//        testReentrantLockFair(threadCount, loopCount);
    }

    public static void testAtomicInteger(int threadCount, int loopCount) throws InterruptedException {
    
    
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < loopCount; j++) {
    
    
                    a.incrementAndGet();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testAtomicInteger: result=" + a.get() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testLongAdder(int threadCount, int loopCount) throws InterruptedException {
    
    
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < loopCount; j++) {
    
    
                    b.increment();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testLongAdder: result=" + b.sum() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockFair(int threadCount, int loopCount) throws InterruptedException {
    
    
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < loopCount; j++) {
    
    
                    fairLock.lock();
                    // 消除try的性能影响
//                    try {
    
    
                        c++;
//                    } finally {
    
    
                        fairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockFair: result=" + c + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockUnfair(int threadCount, int loopCount) throws InterruptedException {
    
    
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < loopCount; j++) {
    
    
                    unfairLock.lock();
                    // 消除try的性能影响
//                    try {
    
    
                        d++;
//                    } finally {
    
    
                        unfairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockUnfair: result=" + d + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testSynchronized(int threadCount, int loopCount) throws InterruptedException {
    
    
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < loopCount; j++) {
    
    
                    synchronized (ReentrantLockVsSynchronizedTest.class) {
    
    
                        e++;
                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testSynchronized: result=" + e + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

}

运行后发现以下规律:

// 随着线程数的不断增加,synchronized的效率竟然比ReentrantLock非公平模式要高!

我的java版本是8,又使用Java7及以下的版本运行了,发现在Java7及以下版本中synchronized的效率确实比ReentrantLock的效率低一些。

总结

(1)synchronized是Java原生关键字锁;

(2)ReentrantLock是Java语言层面提供的锁;

(3)ReentrantLock的功能非常丰富,解决了很多synchronized的局限性;

(4)至于在非公平模式下,ReentrantLock与synchronized的效率孰高孰低,是随着Java版本的不断升级,synchronized的效率只会越来越高;

既然ReentrantLock的功能更丰富,而且效率也不低,是不是可以放弃使用synchronized了呢?

答:因为synchronized是Java原生支持的,随着Java版本的不断升级,Java团队也是在不断优化synchronized,所以在功能相同的前提下,最好还是使用原生的synchronized关键字来加锁,这样我们就能获得Java版本升级带来的免费的性能提升的空间。

另外,在Java8的ConcurrentHashMap中已经把ReentrantLock换成了synchronized来分段加锁了,这也是Java版本不断升级带来的免费的synchronized的性能提升。

猜你喜欢

转载自blog.csdn.net/qq_51998352/article/details/121380378