Java多线程调用静态方法的线程安全和性能分析

Java多线程调用静态方法的线程安全和性能分析

线程安全

多个线程调用静态方法,是否会出现并发问题取决于,静态方法内部是否需要引用共享区内的静态变量。当线程调用静态方法时,都会创建一套临时变量,可见性是在这个线程内部,所以当多个线程调用静态方法时,并且这个静态方法没有引用外部静态变量的。不会有线程并发的问题。

效率

由上面可知,"当线程调用静态方法时,都会创建一套临时变量",所以效率是比较低的,具体分析如下:

https://blog.csdn.net/sfhcdsn/article/details/45845665


因为项目需要, 在开发过程中写了大量的工具类, 而且为了调用方便, 工具类中的方法全部是静态方法。今天,脑袋里灵光一闪,突然就想,如果在高并发的情况下,同时去调用工具类的静态方法,会不会导致严重的性能问题?

有了想法,就要付诸于行动,于是,本人本着精益求精的精神,写了几个测试类如下:

COMUT 类是主类,负责生成大量的线程实例。

import java.util.Date;
 
/**
 * @Title: COMUT.java
 * @Copyright (C) 2015 <span>卓盐</span>
 * @Description:
 * @Revision History:
 * @Revision 1.0 2015-5-19  卓盐
 */
 
/**
 * @ClassName: COMUT
 * @Description: Description of this class
 * @author <a href="mailto:songfuhong@szlongtu.com">卓盐</a>于 2015-5-19 下午03:15:40
 */
 
public class COMUT {
 
    public COMUT() {
 
    }
 
    public static void main(String[] args) {
        int times = 5000;
        int type = 1;
        try {
            Date setTime = new Date();
            // 设定为启动程序后的2s执行
            setTime.setTime(setTime.getTime() + 5000);
            // 测试多线程情况下的性能问题
            Thread[] thread = new Thread[times];
            for (int i = 0; i < times; i++) {
                thread[i] = new Thread(new ThreadTest("thread" + i, setTime, type));
            }
            for (int i = 0; i < times; i++) {
                thread[i].start();
            }
 
            for (int i = 0; i < times; i++) {
                thread[i].join();
            }
            // 等待所有子线程执行完毕后,输出下面的内容
            System.out.println("本次测试方案:" + type + "。最快:" + CulculateUtil.getMin() + "ms。最慢:" + CulculateUtil.getMax()
                    + "ms。平均:" + CulculateUtil.getSum() / CulculateUtil.getTimes() + "ms.");
        } catch (Exception e) {
 
            e.printStackTrace();
 
        }
 
    }
}

ICUtils 是工具类,其中的两个静态方法,一个是将 15 位身份证转化为 18 位,另一个是校验 15 位身份证号码的正确性。

/**
 * @Title: IdentiFierChanslateUtils.java
 * @Copyright (C) 2015 <span>卓盐</span>
 * @Description:
 * @Revision History:
 * @Revision 1.0 2015-5-19  卓盐
 */
 
/**
 * @ClassName: IdentiFierChanslateUtils
 * @Description: Description of this class
 * @author <a href="mailto:songfuhong@szlongtu.com">卓盐</a>于 2015-5-19 下午03:17:16
 */
 
public class ICUtils {
    /**
     * 修补15位居民身份证号码为18位
     * @param personIDCode
     * @return
     */
 
    public static String fixPersonIDCode(String personIDCode)
 
    {
 
        if (personIDCode == null || personIDCode.trim().length() != 15) {
 
            return personIDCode;
 
        }
 
        String id17 = personIDCode.substring(0, 6) + "19" + personIDCode.substring(6, 15); // 15为身份证补\'19\'
 
        // char[] code =
        // {\'1\',\'0\',\'X\',\'9\',\'8\',\'7\',\'6\',\'5\',\'4\',\'3\',\'2\'};
        // //11个
        char[] code = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' }; // 11个
 
        int[] factor = {0, 2, 4, 8, 5, 10, 9, 7, 3, 6, 1, 2, 4, 8, 5, 10, 9, 7 }; // 18个;
 
        int[] idcd = new int[18];
 
        int i;
 
        int j;
 
        int sum;
 
        int remainder;
 
        for (i = 1; i < 18; i++)
 
        {
 
            j = 17 - i;
 
            idcd[i] = Integer.parseInt(id17.substring(j, j + 1));
 
        }
 
        sum = 0;
 
        for (i = 1; i < 18; i++)
 
        {
 
            sum = sum + idcd[i] * factor[i];
 
        }
 
        remainder = sum % 11;
 
        String lastCheckBit = String.valueOf(code[remainder]);
 
        return id17 + lastCheckBit;
 
    }
 
    /**
     * 判断是否是有效的18位或15位居民身份证号码
     * @param identityId :18位或15位居民身份证号码
     * @return:true: 有效的18位或15位居民身份证号码
     */
 
    public static boolean isIdentityId(String identityId) {
 
        if (isEmpty(identityId))
            return false;
 
        try {
 
            if (identityId.length() == 18) {
 
                String identityId15 = identityId.substring(0, 6) + identityId.substring(8, 17);
 
                // System.out.println("the identityId15 is : "+identityId15);
 
                if (fixPersonIDCode(identityId15).equalsIgnoreCase(identityId)) {
 
                    return true;
 
                } else {
 
                    return false;
 
                }
 
            } else if (identityId.length() == 15) {
 
                try {
 
                    Long.parseLong(identityId);
 
                    return true;
 
                } catch (Exception ex) {
 
                    return false;
 
                }
 
            } else {
 
                return false;
 
            }
 
        } catch (Exception ex) {
 
            return false;
 
        }
 
    }
 
    /**
     * 判断是否为空串""
     */
 
    public static boolean isEmpty(String sValue) {
 
        if (sValue == null)
            return true;
 
        return sValue.trim().equals("") ? true : false;
 
    }
}

ICUtilsInstance 中的内容和 ICUtils 中完全一致,只是其中的方法都是实例化方法。

/**
 * @Title: IdentiFierChanslateUtils.java
 * @Copyright (C) 2015 <span>卓盐</span>
 * @Description:
 * @Revision History:
 * @Revision 1.0 2015-5-19  卓盐
 */
 
/**
 * @ClassName: IdentiFierChanslateUtils
 * @Description: Description of this class
 * @author <a href="mailto:songfuhong@szlongtu.com">卓盐</a>于 2015-5-19 下午03:17:16
 */
 
public class ICUtilsInstance {
    /**
     * 修补15位居民身份证号码为18位
     * @param personIDCode
     * @return
     */
 
    public String fixPersonIDCode(String personIDCode)
 
    {
 
        if (personIDCode == null || personIDCode.trim().length() != 15) {
 
            return personIDCode;
 
        }
 
        String id17 = personIDCode.substring(0, 6) + "19" + personIDCode.substring(6, 15); // 15为身份证补\'19\'
 
        // char[] code =
        // {\'1\',\'0\',\'X\',\'9\',\'8\',\'7\',\'6\',\'5\',\'4\',\'3\',\'2\'};
        // //11个
        char[] code = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' }; // 11个
 
        int[] factor = {0, 2, 4, 8, 5, 10, 9, 7, 3, 6, 1, 2, 4, 8, 5, 10, 9, 7 }; // 18个;
 
        int[] idcd = new int[18];
 
        int i;
 
        int j;
 
        int sum;
 
        int remainder;
 
        for (i = 1; i < 18; i++)
 
        {
 
            j = 17 - i;
 
            idcd[i] = Integer.parseInt(id17.substring(j, j + 1));
 
        }
 
        sum = 0;
 
        for (i = 1; i < 18; i++)
 
        {
 
            sum = sum + idcd[i] * factor[i];
 
        }
 
        remainder = sum % 11;
 
        String lastCheckBit = String.valueOf(code[remainder]);
 
        return id17 + lastCheckBit;
 
    }
 
    /**
     * 判断是否是有效的18位或15位居民身份证号码
     * @param identityId :18位或15位居民身份证号码
     * @return:true: 有效的18位或15位居民身份证号码
     */
 
    public boolean isIdentityId(String identityId) {
 
        if (isEmpty(identityId))
            return false;
 
        try {
 
            if (identityId.length() == 18) {
 
                String identityId15 = identityId.substring(0, 6) + identityId.substring(8, 17);
 
                // System.out.println("the identityId15 is : "+identityId15);
 
                if (fixPersonIDCode(identityId15).equalsIgnoreCase(identityId)) {
 
                    return true;
 
                } else {
 
                    return false;
 
                }
 
            } else if (identityId.length() == 15) {
 
                try {
 
                    Long.parseLong(identityId);
 
                    return true;
 
                } catch (Exception ex) {
 
                    return false;
 
                }
 
            } else {
 
                return false;
 
            }
 
        } catch (Exception ex) {
 
            return false;
 
        }
 
    }
 
    /**
     * 判断是否为空串""
     */
 
    public boolean isEmpty(String sValue) {
 
        if (sValue == null)
            return true;
 
        return sValue.trim().equals("") ? true : false;
 
    }
}

最后一个类 ThreadTest,是线程类,构造函数需传入两个参数,一个是线程名,一个是设定的时间。线程主体 run 方法中,while 循环负责校验当前时间是否已到设定时间。到了设定时间就去调用工具类中的两个方法。并将调用的时间记录下来,并输出。

/**
 * @Title: ThreadTest.java
 * @Copyright (C) 2015 <span>卓盐</span>
 * @Description:
 * @Revision History:
 * @Revision 1.0 2015-5-19  卓盐
 */
 
/**
 * @ClassName: ThreadTest
 * @Description: Description of this class
 * @author <a href="mailto:songfuhong@szlongtu.com">卓盐</a>于 2015-5-19 下午03:43:45
 */
 
public class ThreadTest implements Runnable {
    /**
     * @Fields threadName : Description
     */
    private String threadName;
    /**
     * @Fields setTime : Description
     */
    private Date setTime;
 
    /**
     * @Fields ic : Description
     */
    ICUtilsInstance ic;
    /**
     * @Fields type : Description
     */
    int type;
 
    /**
     * .
     * <p>
     * Title: run
     * </p>
     * <p>
     * Description:
     * </p>
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        while (true) {
            Date d = new Date();
            if (d.after(setTime))
                break;
        }
        Date d1 = new Date();
        if (type == 1) {
            ICUtils.fixPersonIDCode("650103760113073");
            ICUtils.isIdentityId("650103760113073");
        } else if (type == 2) {
            ic = new ICUtilsInstance();
            ic.fixPersonIDCode("650103760113073");
            ic.isIdentityId("650103760113073");
        } else if (type == 3) {
            ic.fixPersonIDCode("650103760113073");
            ic.isIdentityId("650103760113073");
        }
        Date d2 = new Date();
        long dif = d2.getTime() - d1.getTime();
        System.out.println("线程名为:" + threadName + "的线程,运行时间为:" + dif + "ms.");
        CulculateUtil.finish(dif);
    }
 
    /**
     * 创建一个新的实例 ThreadTest.
     * @param threadName
     */
    public ThreadTest(String threadName, Date setTime, int type) {
        super();
        this.threadName = threadName;
        this.setTime = setTime;
        this.type = type;
        if (type == 3) {
            ic = new ICUtilsInstance();
        }
    }
 
}

还有一个类,用来统计分析用。

/**
 * @Title: CulculateUtil.java
 * @Copyright (C) 2015 <span>卓盐</span>
 * @Description:
 * @Revision History:
 * @Revision 1.0 2015-5-19  卓盐
 */
 
/**
 * @ClassName: CulculateUtil
 * @Description: Description of this class
 * @author <a href="mailto:songfuhong@szlongtu.com">卓盐</a>于 2015-5-19 下午04:10:06
 */
 
public class CulculateUtil {
    /**
     * @Fields sum : Description
     */
    private static long sum = 0;
    /**
     * @Fields max : Description
     */
    private static long max = 0;
    /**
     * @Fields min : Description
     */
    private static long min = 999999999;
    /**
     * @Fields times : 调用次数
     */
    private static int times = 0;
 
    /**
     * 某个线程结束时需要回掉的方法.
     * @param dif 花费时间
     * @throws
     */
    public static void finish(long dif) {
        times++;
        if (dif < min) {
            min = dif;
        }
        if (dif > max) {
            max = dif;
        }
        sum += dif;
    }
 
    /**
     * @return max
     */
    public static long getMax() {
        return max;
    }
 
    /**
     * @param max 要设置的 max
     */
    public static void setMax(long max) {
        CulculateUtil.max = max;
    }
 
    /**
     * @return min
     */
    public static long getMin() {
        return min;
    }
 
    /**
     * @param min 要设置的 min
     */
    public static void setMin(long min) {
        CulculateUtil.min = min;
    }
 
    /**
     * @return times
     */
    public static int getTimes() {
        return times;
    }
 
    /**
     * @param times 要设置的 times
     */
    public static void setTimes(int times) {
        CulculateUtil.times = times;
    }
 
    /**
     * @return sum
     */
    public static long getSum() {
        return sum;
    }
 
    /**
     * @param sum 要设置的 sum
     */
    public static void setSum(long sum) {
        CulculateUtil.sum = sum;
    }
 
}

本人在测试时,分别用 100、1000 和 5000 个线程进行了 9 次测试。在不同线程数情况下,分别调用了静态方法,在方法体中实例化并调用实例方法,及在构造方法中实例化并调用实例方法,测试结果如下:

线程数 静态方法 方法中实例化 构造方法中实例化
最快 平均 最慢 最快 平均 最慢 最快 平均 最慢
100 1+0+0=1 7+5+3=25 15+16+11=42 2+0+0=2 6+4+9=19 13+14+17=44 0+0+0=0 1+1+0=2 13+15+5=33
1000 0+0+0=0 24+11+26=61 68+63+109=240 0+0+0=0 3+22+2=27 65+68+66=199 0+0+0=0 24+14+26=64 52+47+65=164
5000 0+0+0=0 7+17+76=100 94+162+310=566 0+0+0=0 12+15+7=34 267+114+102=483 0+0+0=0 0+0+4=4 55+78+81=214
总和 1 186 848 2 80 726 0 70 411

总结: 在并发数较少的情况下,在构造方法中实例化并在方法中调用略占优,但三者性能无太大差别。当并发数较多时,尤其大并发情况下, 优先推荐构造方法中实例化,再其次是方法中实例化,最后再是静态方法。高并发情况下,静态方法对性能影响比较大

附件中上传源码及 excel 格式的测试结果,供大家参考。

posted @ 2020-05-22 17:51  别再闹了  阅读(1553)  评论(0)    收藏  举报