Java多线程下系统时区全局锁对性能的影响
起因
在考量MySql时间格式时,看到有文章提到高并发下timestamp调用系统时区转换格式时会因为全局锁导致线程上下文切换致使cpu使用率暴涨。
测试过程
在查询数据库时,MyBatis或者JPA等框架大都会将timestamp转换为Java中对应的时间戳格式(Timestamp或者Instant),在这一过程中并不存在时区转换的问题,而在业务代码尤其是返回前端数据时,经常需要将时间戳转换为具体的时间字符串,此时才是应该探究的全局锁对性能影响的关键点。
设计两个对照组:
- 是否使用系统时区对性能的影响
- 不同并发量下对性能影响
使用系统时区的Callable:
private static final Callable<Long> useSystemZoneCallable = () -> {
long startTime = System.currentTimeMillis();
for (int k = 0; k < MAX_EX_NUM; k++) {
ZoneId.systemDefault().toString();
}
return System.currentTimeMillis() - startTime;
};
不使用系统时区的Callable:
private static final Callable<Long> notUseSystemZoneCallable = () -> {
long startTime = System.currentTimeMillis();
for (int k = 0; k < MAX_EX_NUM; k++) {
ZoneOffset.UTC.toString();
}
return System.currentTimeMillis() - startTime;
};
其中MAX_EX_NUM设置为1000000000
开启不同数量的线程执行上述Callable,并统计每个线程执行时长,计算平均值。
/**
* 使用系统时区
*
* @param maxThreadNum
* @throws InterruptedException
* @throws ExecutionException
*/
private static void useSystemZone(int maxThreadNum) throws InterruptedException, ExecutionException {
CountDownLatchFuture<Long>[] futureTasks = new CountDownLatchFuture[maxThreadNum];
CountDownLatch countDownLatch = new CountDownLatch(maxThreadNum);
for (int i = 0; i < maxThreadNum; i++) {
futureTasks[i] = new CountDownLatchFuture<>(useSystemZoneCallable, countDownLatch);
new Thread(futureTasks[i]).start();
}
countDownLatch.await();
long totalSpendTime = 0;
for (FutureTask<Long> futureTask : futureTasks) {
totalSpendTime += futureTask.get();
}
System.out.printf("ThreadNum: %s,\tuseSystemZone method take %s milliseconds(average).\n", maxThreadNum,
(totalSpendTime / maxThreadNum));
}
不使用系统时区的执行方法同上。
测试结果
ThreadNum: 1, useSystemZone method take 1024 milliseconds(average).
ThreadNum: 2, useSystemZone method take 1021 milliseconds(average).
ThreadNum: 3, useSystemZone method take 655 milliseconds(average).
ThreadNum: 4, useSystemZone method take 735 milliseconds(average).
ThreadNum: 5, useSystemZone method take 806 milliseconds(average).
ThreadNum: 6, useSystemZone method take 837 milliseconds(average).
ThreadNum: 7, useSystemZone method take 880 milliseconds(average).
ThreadNum: 8, useSystemZone method take 916 milliseconds(average).
ThreadNum: 9, useSystemZone method take 969 milliseconds(average).
ThreadNum: 10, useSystemZone method take 1020 milliseconds(average).
ThreadNum: 11, useSystemZone method take 1096 milliseconds(average).
ThreadNum: 12, useSystemZone method take 1195 milliseconds(average).
ThreadNum: 13, useSystemZone method take 1239 milliseconds(average).
ThreadNum: 14, useSystemZone method take 1334 milliseconds(average).
ThreadNum: 15, useSystemZone method take 1374 milliseconds(average).
ThreadNum: 16, useSystemZone method take 1445 milliseconds(average).
ThreadNum: 17, useSystemZone method take 1559 milliseconds(average).
ThreadNum: 18, useSystemZone method take 1640 milliseconds(average).
ThreadNum: 19, useSystemZone method take 1665 milliseconds(average).
ThreadNum: 20, useSystemZone method take 1712 milliseconds(average).
ThreadNum: 21, useSystemZone method take 1760 milliseconds(average).
ThreadNum: 22, useSystemZone method take 1849 milliseconds(average).
ThreadNum: 23, useSystemZone method take 2079 milliseconds(average).
ThreadNum: 24, useSystemZone method take 2247 milliseconds(average).
ThreadNum: 1, notUseSystemZone method take 4 milliseconds(average).
ThreadNum: 2, notUseSystemZone method take 4 milliseconds(average).
ThreadNum: 3, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 4, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 5, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 6, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 7, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 8, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 9, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 10, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 11, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 12, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 13, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 14, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 15, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 16, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 17, notUseSystemZone method take 0 milliseconds(average).
ThreadNum: 18, notUseSystemZone method take 0 milliseconds(average).
ake 0 milliseconds(average).
结论
在高并发环境下,时间戳调用系统时区时,由于全局锁的存在会导致大量线程的上下文切换,容易造成CPU满载的情况。所以在使用时间戳格式时,在情况允许的前提下最好传递给外部系统特定时区(UTC,东八区….)的ISO-8601时间格式,而不是系统默认时区的时间格式。