生产环境典型问题实录第二期
- 往期回顾
- 第一期 : https://mp.weixin.qq.com/s/Vm7-k2pbpdkw2ZWFzl0-Hg
案例六:方法体内部创建线程池
"pool-2494-thread-1" prio=10 tid=0x00007f885014c800 nid=0xc06d waiting on condition [0x00007f86d20fe000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000608e437c8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
- 线程名称
pool-2494-thread-1
透露出来的信息是这已经是第2494个线程池,每个线程池里只有1个线程。 -
WAITING
表示这个线程池的任务队列为空,所以阻塞在LinkedBlockingQueue.take
方法上。 - 线程池数量已经到2000多个,基本上可以说明这个线程池是在方法内部创建的;线程数量是1,大概率是调用的
Executors.newSingleThreadExecutor
; 阻塞在LinkedBlockingQueue.take
上,说明这个线程池用完后没有关闭。 - 总结:不建议在方法内部创建私有的线程池,这个等同于在方法内部创建线程,并发量高的时候,线程数会暴增,可能出现
OOM:Unable to create new thread
,而且大量的线程并不会带来性能的提高,相反由于上下文切换反而会带来性能损耗,就好比只有两个核酸检测人员(两核),你排成2000条队(2000个线程),并不能提高核酸检测效率,还会由于2000条队列的调度拖慢效率。
案例七:慎用反射
反射调用是一个较慢的操作,通过Class.forName
进行反射调用时,会调用ClassLoader.loadClass
,这是一个synchronized
的方法,在并发量大时,会出现严重性能瓶颈。
final Class<?> loadClass(Module module, String name) {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(module.getName(), name);
}
if (c != null && c.getModule() == module) {
return c;
} else {
return null;
}
}
}
我们常用的两个框架logback
和hibernate
也是反射的重度使用者。
- logback在输出日志时如果要输出jar包名称,会调用
PackageDataCalculation
进行反射调用,建议关闭此特性。- 参考 https://jira.qos.ch/browse/LOGBACK-730
"qtp1747988071-86163" prio=10 tid=0x00007f8bd809d800 nid=0x193e7 waiting for monitor entry [0x00007f8b2bd7a000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:390)
- waiting to lock <0x0000000731400000> (a org.eclipse.jetty.webapp.WebAppClassLoader)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:383)
at ch.qos.logback.classic.spi.PackagingDataCalculator.loadClass(PackagingDataCalculator.java:207)
- 通过hibernate写hql时,hibernate需要大量使用反射来将数据库表、字段与PO、属性进行反射调用,在hibernate3.X版本中有严重性能问题,hibernate5.X做了优化。
- 参考 https://hibernate.atlassian.net/browse/HHH-4959
"qtp1884473012-36570" prio=10 tid=0x00007ff7d8323000 nid=0x134a waiting for monitor entry [0x00007ff7275b2000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:390)
- waiting to lock <0x00000007315a3620> (a org.eclipse.jetty.webapp.WebAppClassLoader)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:383)
at org.hibernate.util.ReflectHelper.classForName(ReflectHelper.java:95)
at org.hibernate.impl.SessionFactoryImpl.getImplementors(SessionFactoryImpl.java:683)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1543)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:283)
- 总结 : 反射调用时在评估一下调用次数,超过一定量会引起系统锁竞争爆发,大量线程Block。
案例八:线程池需考虑任务队列大小和拒绝策略
在使用线程池时,会使用阻塞队列BlockingQueue
做为其任务队列。
-
ArrayBlockingQueue
必须设置其固定大小,有使用者误以为其capacity
类似于ArrayList
的initialCapacity
,其实不然。ArrayBlockingQueue是固定大小的,不是像ArrayList
会动态扩容。 - BlockingQueue及其子类应该都是固定大小的,不排除有些自定义的
BlockingQueue
可以动态扩容。 - BlockingQueue的容量大小(
capacity
)要考虑内存占用和任务的消费速度,既要避免内存占用过多导致OOM,也要考虑队列容量过小导致系统阻塞。
"qtp1975260912-1059" - Thread t@1059
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <4a3136f1> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(Unknown Source)
at java.util.concurrent.LinkedBlockingQueue.put(Unknown Source)
at com.thunisoft.summer.component.ssoserver.asyn.LogRecordWriterImpl.writeLog(LogRecordWriterImpl.java:49)
- 线程池的拒绝策略不要选择
DiscardPolicy
。DiscardPolicy
不会更新Future
的任务状态,导致后续调用Future.get
会无限阻塞。- 推荐使用
AbortPolicy
或者给Future.get
设置超时时间。 - 参考 https://mp.weixin.qq.com/s/2VEGYqIHhlG7l-9CZUP9qg
- 推荐使用
案例九:读写锁,需考虑写锁的耗时
java提供了读写锁ReentrantReadWriteLock
,读锁与读锁之间互相不阻塞,读锁与写锁、写锁与写锁之间阻塞,因此在使用写锁时要考虑写锁的阻塞耗时,尽量不要超过1秒,避免写锁阻塞时间过长,导致系统无响应。
Thread 102250: (state = BLOCKED)
- java.lang.Object.wait(long) @bci=0 (Compiled frame; information may be imprecise)
- java.lang.Object.wait() @bci=2, line=485 (Compiled frame)
- EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock$ReaderLock.acquire() @bci=34 (Compiled frame)
- com.thunisoft.summer.util.cache.AbstractCacheImpl.readLock() @bci=7, line=34 (Compiled frame)
- com.thunisoft.summer.component.config.sysConfig.SysConfigCache$$EnhancerByCGLIB$$9bf9c44$$FastClassByCGLIB$$ec264ca4.invoke(int, java.lang.Object, java.lang.Object[]) @bci=723 (Compiled frame)
案例十:不要用System.out.println
System.out
用的是PrintStream
,其println
源码如下:文章来源:https://www.toymoban.com/news/detail-422425.html
public void println(boolean x) {
synchronized (this) {
print(x);
newLine();
}
}
可以看到println
为synchonzied
方法,当并发较大或者往控制台输出信息过多时,其方法就阻塞住。文章来源地址https://www.toymoban.com/news/detail-422425.html
- 总结
- 不要显式调用
System.out
。注:sonar可以检查出来。 - 使用logback时,生产环境不要使用
ConsoleAppender
,其内部就是System.out
"qtp1349006843-290" - Thread t@290 java.lang.Thread.State: BLOCKED at java.io.PrintStream.write(PrintStream.java:429) - waiting to lock <58526d71> (a XXX.server.module.log.TASPrintStream) owned by "qtp1349006843-36" t@36 at XXX.server.module.log.TASPrintStream.write(TASPrintStream.java:55) at java.io.FilterOutputStream.write(FilterOutputStream.java:80) at ch.qos.logback.core.joran.spi.ConsoleTarget$1.write(ConsoleTarget.java:36) at ch.qos.logback.core.encoder.LayoutWrappingEncoder.doEncode(LayoutWrappingEncoder.java:135) at ch.qos.logback.core.OutputStreamAppender.writeOut(OutputStreamAppender.java:194) at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:219) at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:103) at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:88) at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:48)
- 不要显式调用
到了这里,关于生产环境典型问题实录第二期的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!