JAVA线程同步锁性能评测与对比分析

线程同步是JAVA多线程高并发编程的重要话题,常常用到的同步机制包括synchronized、ReentrantLock(可重入锁)、Semephore(信号灯)、CyclicBarrier(循环栅栏)、CountDownLatch(倒计时锁)等,今天对比较常用的synchronized的各种加锁方式与ReentrantLock进行性能评测。

  • 目录结构
  1. 性能评测方案
  2. 性能评测结论
  3. 性能评测源码
  4. 同步机制浅析
  5. synchronized与Lock比较

1 性能评测方案

测评任务:用50个线程,对一个静态变量从0数到5亿
测评目的:比较synchonized与ReentrantLock的性能差异
方案思路:50个线程、加剧同步竞争;5亿次加锁、任务量次数够多;简单的+1运算、CPU消耗型任务、降低内存I/O等干扰;随机不同锁类型的执行顺序,降低不确定性因素干扰
测评次数:10次,消除随机因素影响


  • 加锁设计
    1 对象实例方法synchronized同步,如public synchronized void addCounter()
    2 对象静态方法synchronized同步,如public static synchronized void addCounter()
    3 代码块内synchronized Class<?>同步, 如synchronized(LockObject.class)
    4 代码块内ReentrantLock同步

2 性能评测结论

2.1 测试结果

序号 实例方法(ms) Class<?>(ms) 可重入锁(ms) 静态方法(ms) 平均时间(ms)
1 15,569 16,485 11,289 73,919 29,315
2 15,252 16,781 11,630 74,147 29,452
3 15,906 16,634 11,545 77,101 30,296
4 15,483 16,293 11,497 74,853 29,531
5 15,544 16,195 11,394 77,199 30,083
6 14,831 16,614 11,471 78,771 30,421
7 15,382 16,557 11,499 77,011 30,112
8 15,559 16,363 11,432 78,120 30,368
9 15,512 16,574 11,503 77,801 30,347
10 15,643 16,372 11,543 73,269 29,206
平均 15,468 16,486 11,480 76,219 29,913

2.2 测评结论

分析评测数据,经过10轮测试、总计50亿次锁同步,可重入锁ReentrantLock平均完成时间11.48秒,实例方法synchronized平均完成时间15.47秒,Class<?>类型synchronized平均完成时间16.49秒,静态方法synchronized平均完成时间76.22秒。

测评冠军:可重入锁ReentrantLock,性能优于任何形式的synchronized

性能排序:可重入锁 > 对象方法 > Class<?> 静态方法

最佳实践
可重入锁性能最优,高并发尽量采用可重入锁,尽量少在大量调用的静态方法上采用synchronized同步

3 性能评测代码

第一步 添加测试执行类

package LockStudy.test;


import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestManager {
    protected class LockObject {}

    protected int lockType;
    protected static long counter = 0;
    protected long maxCounter = 0;
    protected Lock lockObj = new ReentrantLock(false);

    protected Date tmStart;
    protected Date tmFinish;

    public TestManager(int lockType){
        this.lockType = lockType;
    }

    /**
     * 启动计数工作线程
     * @param initWorker 工作线程数量
     * @param maxCounter 最大计数上限
     * @throws Exception
     */
    public void init(int initWorker, long maxCounter) throws Exception {
        this.maxCounter = maxCounter;
        counter = 0;

        tmStart = new Date();
        for(int i=0; i<initWorker; i++){
            TestRunner obj = new TestRunner(this);

            Thread th = new Thread(obj);
            th.start();
            //Thread.sleep(50);
        }
    }

    /**
     * 记录任务完成时间
     */
    protected void onTaskComplete(){
        tmFinish = new Date();
    }

    /**
     * 返回任务完成时间
     * @return 完成时间,单位毫秒ms
     */
    public long getTimeElapsed(){
        long temp = tmFinish.getTime() - tmStart.getTime();
        return temp;
    }

    public boolean isGameOver(){
        if(counter>maxCounter){
            synchronized (this){
                if(tmFinish==null){
                    onTaskComplete();
                }
            }
            return true;
        }
        return (counter>maxCounter);
    }

    protected void addCounter(){
        switch (lockType){
            case 0:
                addCounterWithSyn();
                break;
            case 1:
                addCounterWithClass();
                break;
            case 2:
                addCounterWithLock();
                break;
            case 3:
                addCounterWithStatic();
                break;
        }
    }

    /**
     * 对象方法synchronized
     */
    protected synchronized void addCounterWithSyn(){
        counter++;
    }

    /**
     * 对象类型synchronized
     */
    protected void addCounterWithClass(){
        synchronized (LockObject.class){
            counter++;
        }
    }

    /**
     * ReentrantLock同步
     */
    protected void addCounterWithLock(){
        lockObj.lock();
        counter++;
        lockObj.unlock();
    }

    /**
     * 静态方法synchronized
     */
    protected static synchronized void addCounterWithStatic(){
        counter++;
    }

    /**
     * 计数工作线程
     */
    protected class TestRunner implements Runnable{
        protected TestManager manager;

        public TestRunner(TestManager pManager){
            this.manager = pManager;
        }

        @Override
        public void run(){
            long count = 0;
            while(!manager.isGameOver()){
                manager.addCounter();
                count++;
            }
            //System.out.println("ThreadId=" + Thread.currentThread().getId() + ",\tcount=" + count);
        }
    }
}

TestManager类的addCounter根据同步锁类型,调用四个不同加锁机制的方法,四个方法逻辑都是对counter静态变量执行+1操作。

第二步 编写测试方案主控逻辑类

package LockStudy;

import LockStudy.test.TestManager;

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 同步锁性能对比
 * @author 明月照我行@简书
 */
public class App
{
    protected class LockObject {}

    public static void main( String[] args ) throws Exception
    {
        System.out.println( "Test start..." );

        //doTestSynWithException();
        //doTestLockWithException();

        int count = 0;
        Random rand = new Random();
        long[][] results = new long[11][5];
        ArrayList lockTypeList = new ArrayList(4);
        while(count<10) {
            /**
             * lockType:锁类型
             * 0=synchronized method 1=synchronized class
             * 2=ReentrantLock 3=synchronized static method
             */
            for(int i=0; i<4; i++){
                lockTypeList.add(i);
            }

            System.out.println(String.format("---第%d轮测试开始----", count+1));
            while(lockTypeList.size()>0){
                /**
                 * 随机测试顺序,确保不会因为执行顺序干扰评测结果
                 */
                int index = rand.nextInt(lockTypeList.size());
                int lockType = (int)lockTypeList.get(index);
                lockTypeList.remove(index);

                long timeElapsed = doTest(lockType);
                results[count][lockType] = timeElapsed;
            }
            System.out.println(String.format("---第%d轮测试结束----", count+1));
            count++;
        }

        /**
         * 求每一轮次平均时间消耗
         */
        for(int row=0; row<10; row++){
            long temp = 0;
            for(int i=0; i<4; i++){
                temp += results[row][i];
            }
            results[row][4] = temp/4;
        }

        /**
         * 求每一类型锁平均时间消耗
         */
        for(int col=0; col<5; col++){
            long temp=0;
            for(int i=0; i<10; i++){
                temp += results[i][col];
            }
            results[10][col] = temp / 10;
        }

        /**
         * 输出评测结果矩阵
         */
        System.out.println( "-----------Output Results ----------" );
        for(int row=0; row<11; row++){
            System.out.print("|" + (row+1));
            for(int i=0; i<5; i++){
                System.out.print("|" + results[row][i]);
            }
            System.out.println("|");
            System.out.println( "----------------------------------" );
        }
        System.out.println( "Test completed..." );
    }

    protected static long doTest(int lockType) throws Exception {
        /**
         * 50个线程,加锁5亿次
         */
        int threads = 50;
        long maxCounter = 5 * 10000;
        maxCounter *= 10000;

        TestManager myobj = new TestManager(lockType);
        myobj.init(threads, maxCounter);
        while (!myobj.isGameOver()) {
            Thread.sleep(100);
        }

        long result = myobj.getTimeElapsed();
        if (lockType == 0) {
            System.out.println("synchronized method, time elapsed " + result + "ms");
        } else if (lockType == 1) {
            System.out.println("synchronized class, time elapsed " + result + "ms");
        } else if (lockType == 2) {
            System.out.println("reentrantlock, time elapsed " + result + "ms");
        } else {
            System.out.println("synchronized static method, time elapsed " + result + "ms");
        }
        myobj = null;
        Thread.sleep(1000);
        return result;
    }

    /**
     * 测试synchronized在代码块内发生异常后是否正常释放
     * @throws Exception
     */
    protected static void doTestSynWithException() throws Exception {
        int a = 5, b = 0;
        try {
            synchronized (LockObject.class) {
                a = a / b;
            }
        }catch(Exception ex){
        }

        /**
         * 启动新线程,锁具有可重入特性
         */
        Thread thobj = new Thread(new Runnable(){
            public void run(){
                synchronized (LockObject.class){
                    System.out.println("synchronized released successfully after exception.");
                }
            }
        });
        thobj.start();
        Thread.sleep(5000);
    }
}

附送:doTestSynWithException函数,验证在synchronized代码块内发生异常,锁能够正常释放。

4 同步机制浅析

4.1 synchronized

分析上述代码,synchronized同步应用在对象方法、静态方法、Class<?>与对象实例上,本质上都是对JAVA对象加锁。静态方法同步是在方法所在类的类型Class<?>上加锁,对象方法同步是在该对象实例的对象头上加锁
根据知乎网友的《Java synchronized原理总结》一文分享,synchronized通过JAVA对象头锁标记,实现从偏向锁、轻量级锁到重量级锁的升级降级来优化锁性能,重量级锁采用操作系统的Mutex-互斥锁实现。

  • 优化思路
  1. 控制JAVA对象头锁标记,比较是否加锁以及持有锁的线程ID,对相同线程多次加锁支持可重入访问;
  2. 避免从JAVA虚拟机切换到系统态执行,逐步升级到重量级锁Mutex;
  3. 利用操作系统线程调度的CPU时间碎片分配机制,线程获得执行权后,一段时间内拥有CPU内核,这个是偏向锁升级机制的基础。

4.2 ReentrantLock

可重入锁ReentrantLock是Lock接口的一个实现,提供比synchronized更精细和扩展的锁同步功能,底层调用LockSupport的静态方法park/unpark,LockSupport类调用操作系统native本地方法实现锁同步操作。JAVA常用同步锁的源码我计划专门写一篇博文分析。
Lock锁对象,要注意异常处理,避免因异常导致锁无法正常释放,JDK推荐代码写法如下:

Lock lockObj = ...
lockObj.lock();
try{
    ...
}finally{
  lockObj.unlock();
}

5 synchronized与Lock比较

性能:可重入锁ReentrantLock性能优于任何形式的synchronized同步。

方便性:synchronized比Lock方便,不用特地创建锁对象,可以方便在对象类型、方法、对象实例上同步。

灵活性:Lock对象高于synchronized,Lock对象实例可以作为参数传递,可以在不同代码块lock/unlock。

复杂性:synchronized简单,不用Lock对象的费心lock/unlock逻辑,同时,synchronized代码块内发生异常,同步对象的锁标记自动复位。

控制精度:Lock对象支持在加锁时指定超时等待;Lock对象支持tryLock操作,不成功及时返回。

最佳实践:1) 高并发关键代码,尽量使用Lock对象,要注意lock/unlock中间异常的处理; 2) 尽量避免在静态方法采用synchronized同步; 3) synchronized对象非静态方法或对象实例上同步方便高效,对并发度要求不高、锁逻辑复杂的地方可多食用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 多线程三个特征:原子性、可见性以及有序性. 同步锁 /并发锁/ 读写锁,显示锁, ReentrantLock与Co...
    架构师springboot阅读 6,073评论 0 5
  • 线程安全 当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或...
    闽越布衣阅读 4,114评论 0 6
  • 多线程同步 为何要使用同步? java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改...
    关耳木水阅读 3,641评论 0 0
  • 乐观锁认为读多写少,遇到并发的可能性低,拿数据的时候认为别人不会修改,所以不上锁,但是更新数据时,会判断一下有没有...
    繁星追逐阅读 2,999评论 0 0
  • 感恩成莉老师的排毒操 感恩六六老师的悉心教导 感恩母亲辛苦做早饭 感恩环卫工人清洁城市 感恩种子群关护佑众生 感恩...
    爱和健康的种子阅读 1,036评论 0 0

友情链接更多精彩内容