糟了,银行线上跑了一年的代码出事故了

新闻
周末在水群的时候,发现有个小伙伴遇到了一个线上问题,线程池中线程的状态只有一个为RUNNABLE,其他都为WAITING,问有可能是哪些原因造成的?

[[343153]]

 介绍

周末在水群的时候,发现有个小伙伴遇到了一个线上问题

线程池中线程的状态只有一个为RUNNABLE,其他都为WAITING,问有可能是哪些原因造成的?

线程池有25个线程,只有一个线程卡在网络读取上面,状态为RUNNABLE,其他线程都为WAITING。

可能有小伙伴们没用过这个工具,简单介绍一下这个性能监测工具JMC,JMC是源自JRockit JVM的一套监控和管理工具,Oracle在发布JAVA 7u4(Java 7 Update 40)时将其包含在JDK中,用户不再需要单独下载

只需要在命令中执行jmc即可

应用启动配置如下参数

  1. -Dcom.sun.management.jmxremote.port=7091  
  2. -Dcom.sun.management.jmxremote.authenticate=false  
  3. -Dcom.sun.management.jmxremote.ssl=false 

连接到配置的JMC就能看到各种监测指标。

本来我想让这个小伙伴把代码发过来看看的,可他却说自己做的是银行的项目,连不上外网,只能用手机开视频对着电脑让我看个大概。我复原一下这个代码的场景,估计很多小伙伴一下就能发现问题了,因为我把多余的代码都省略了,只留了会造成问题的代码

  1. public class BankDemo { 
  2.  
  3.     public ExecutorService service = Executors.newFixedThreadPool(5); 
  4.  
  5.     public static class Task implements Runnable { 
  6.  
  7.         private CountDownLatch latch; 
  8.  
  9.         public void setLatch(CountDownLatch latch) { 
  10.             this.latch = latch; 
  11.         } 
  12.  
  13.         @SneakyThrows 
  14.         @Override 
  15.         public void run() { 
  16.             // 建立一个Socket连接发送数据 
  17.             Socket socket = new Socket("127.0.0.1",10006); 
  18.             // ... 
  19.             // 执行最后调用如下方法 
  20.             latch.countDown(); 
  21.         } 
  22.     } 
  23.  
  24.     // 真实的代码这里的过程为,每次往线程池里面放一批任务,这一批任务执行完毕,再放下一批任务 
  25.     // 即循环调用如下方法 
  26.     @SneakyThrows 
  27.     public void runTask(List<Task> taskList) { 
  28.         CountDownLatch latch = new CountDownLatch(5); 
  29.         taskList.forEach(item -> { 
  30.             item.setLatch(latch); 
  31.             service.submit(item); 
  32.         }); 
  33.         latch.await(); 
  34.     } 

提示一下WAITING状态的线程阻塞在LockSupport.park()方法上(用了上图的JMC工具)

写个小插曲,这个小伙伴一直和我强调这个代码已经在线上跑了一年了,一直没发生问题。怎么到自己这就发生问题了,所以他的解决方案是一直看自己修改了哪些部分,但是始终没看出来问题。

而我的思路就和他不一样了,因为有些bug只有在特定场景下才会出现,不要坚信之前的代码就没有问题,要从问题本身着手

Java线程状态

在发现问题的时候基础知识还是很重要的,回顾一下

简易的线程状态如下图

Java Thread线程内部有一个枚举内部类State,定义了Java语言线程状态的枚举值

  1. NEW(初始化状态)
  2. RUNNABLE (可运行/运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING (无时限等待)
  5. TIMED_WAITING(有时限等待)
  6. TERMINATED(终止状态)

Java将操作系统层面的阻塞状态细分为BLOCK,WAITING,TIMED_WAITING三种状态

NEW:新建状态,线程被创建但未启动的状态。创建线程有三种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

我们最常用的是通过实现接口这种方式,Runnable和Callable接口的区别如下

  1. Runnable无法获取返回值,而Callable可以获取返回值
  2. Runnable无法抛出异常,而Callable可以抛出异常

RUNNABLE(就绪状态):调用start之后运行之前的状态RUNNING(运行状态):线程正在运行BLOCKED(阻塞状态):进入以下状态,有以下几种情况

  1. BLOCK(同步阻塞):锁被其他线程占用,如等待进入synchronized方法或者代码块
  2. WAITING(主动阻塞):执行Object.wait(),Thread.join()等
  3. TIMED_WAITING(等待阻塞):执行Object.wait(long),Thread.sleep(long)等

DEAD(终止状态):线程执行完毕 最后将各种方法补充到线程状态图上

场景还原

造成线程WAITING,一般是调用了如下3种方法之一

  1. Object.wait()
  2. Thread.join()
  3. LockSupport.park()

排查问题的过程如下

  1. 在明确了代码中没有调用Object.wait()和Thread.join()后,那基本就确定了是调用了java.util.concurrent包下面的工具类导致的线程阻塞,因为java.util.concurrent包下的工具类频繁使用了LockSupport.park()
  2. 接着就可以确定是使用CountDownLatch造成的问题了,其他的线程已经结束了,只有一个线程在运行,此时其他线程就阻塞等待
  3. 那这个RUNNABLE的线程做啥了,为啥一直没有结束?此时文章最开始的一张图指明了方向,这个线程阻塞在网络读取上了。
  4. 既然卡在网络读取上,肯定就是没有设置连接的超时时间,或者读取的超时时间。一问,果然和我想的一样,没有设置

设置完后,他在本地跑了一下,刚开始还正常运行,后来就直接抛出异常了

SocketTimeoutException: connect timed out(连接服务端超时) SocketException: Connection reset(服务端关闭了连接,但是客户端还在从连接中读取数据)

那为什么刚开始程序能正常跑?后面就开始报这种连接异常了呢?

  1. 服务端确实并发太大了
  2. 服务端的网路请求用BIO实现的,一个请求创建一个线程,本身就支持不了高并发

至于是哪种原因?我让小伙伴找服务端的开发人员确认了 一下,服务端居然是使用BIO实现的。网络请求居然不用Netty,还是你们任性!

期待我后续的Netty文章哈,这种事情坚决不能再发生。

本文转载自微信公众号「 Java识堂」,可以通过以下二维码关注。转载本文请联系 Java识堂公众号。

 

责任编辑:武晓燕 来源: Java识堂
相关推荐

2022-10-10 08:05:34

线程池OOM问题

2022-07-11 13:58:14

数据库业务流程系统

2022-06-06 11:31:31

MySQL数据查询

2023-01-16 14:49:00

MongoDB数据库

2022-09-07 09:09:13

高并发架构

2022-04-08 08:48:16

线上事故日志订阅者

2020-05-07 11:00:24

Go乱码框架

2015-03-09 17:49:40

SDN

2023-07-11 08:39:16

React前端

2021-01-14 11:39:05

云计算

2020-11-16 12:35:25

线程池Java代码

2020-05-27 08:31:33

CPU寄存器缓存

2023-12-31 12:06:51

2015-07-02 11:12:19

2022-03-28 14:32:29

review代码

2019-03-22 15:35:33

自动驾驶Uber伊莱恩·赫茨伯格

2020-05-11 13:14:13

CPU寄存器缓存

2020-03-06 10:14:18

IT支出IT投资

2023-02-16 08:55:13

2020-07-14 13:31:07

程序员代码Java
点赞
收藏

51CTO技术栈公众号