春节放假安排
2月10日至17日放假调休,共8天。2月4日(星期日)、2月18日(星期日)上班。鼓励各单位结合带薪年休假等制度落实,安排职工在除夕(2月9日)休息。
是的,没错,除夕不放假!!! 但是问题不大,还可以调休或者休年假嘛,可这却勾起我记忆深处......
故事的开始
那是一个风雪交加的夜晚......回归正题,那是2021年年末,由于快过年了,这个时候也没有什么大的项目,就想着把之前遗留的问题解决一下,说干就干!当时我们在线设备量(2000w+台)的增加,设备上报接口响应越来越慢了,于是就对这个接口进行优化升级,在测试过程中发现有效的效果非常理想,于是就迫不及待的上线,接下来就是一场血雨腥风,差点让我除夕不能回家过年!!!
上线后,在日常时间效果达到预期,但是在每日高峰期,表现的远远不如优化前,甚至出现了请求超时的情况!!!这是什么情况???婶婶不能忍,叔叔更不能忍啊!于是就拉了相关代码进行review,在对业务逻辑进行检查时,发现没有什么问题,这就奇怪了,本次优化是减少数据库操作,同时对非必要业务进行同步变异步操作,怎么会在高峰期出现了负优化呢?就在我百思不得其解时,一条语句引起了我的注意,没错,就是sout。在这次优化中,开发人员为了方便在本地调试,用大量的sout来打印日志,上线的时候也忘记删除或者修改了。就是System.out.println在高峰期时严重影响了性能!
这不是一条打印日志的语句吗?这能引起什么问题? 没错,这是一个非常简单的日志打印语句,大家在刚刚接触java时,都会用到它,它也非常好用,但是它却有很大的性能隐患,接下来请听我娓娓道来。
一探究竟
我们线上同时在线的设备最多的时候大概在200w台左右,接下我们就简单模拟一下使用System.out.println打印200w条日志和不打印日志的耗时。
/**
* @author liaobuqi
* @date : 2023/10/23 21:03
*/
public class SoutTest {
private static final Integer TIMES = 2000000;
public static void main(String[] args) {
//开始时间
long startTime1 = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
System.out.println("i="+i);
}
//结束时间
long endTime1 = System.currentTimeMillis();
System.out.println("使用System.out.print输出语句总耗时为:"+(endTime1-startTime1) + "ms");
//开始时间
long startTime2 = System.currentTimeMillis();
for (int i = 0; i < TIMES; i++) {
}
//结束时间
long endTime2 = System.currentTimeMillis();
System.out.println("不使用System.out.print输出语句总耗时为:"+(endTime2-startTime2)+ "ms");
}
}
输出结果:
使用System.out.print输出语句总耗时为:74745ms
不使用System.out.print输出语句总耗时为:4ms
从打印结果我们可以看到,循环200w次的打印时间需要耗时74745毫秒,没有打印的循环几乎等于0毫秒。
原理分析
为什么System.out.println语句会这么耗费性能呢?
我们看一下System.out.println语句的源码就知道答案了。
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
从源码可以看到,在一开始就使用synchronized同步锁给锁起来了,所以System.out.println是一个同步方法,在高并发的情况下,会严重影响性能。
总结
为了避免System.out.println()对系统运行效率的影响,我们可以采用以下几种方法:
1.使用日志框架:日志框架(log4j2、logback)可以实现更加灵活的日志输出方式,可以根据需要输出到文件、数据库等,而不是仅仅输出到控制台。
2.加强代码审查:项目上线前也要进行全局扫描,防止有人误提交带有System.out.println的代码。
3.合理使用输出语句:在代码中合理使用输出语句,避免过多的输出操作,尽可能减少对系统运行效率的影响。