`
jinnianshilongnian
  • 浏览: 21503878 次
  • 性别: Icon_minigender_1
博客专栏
5c8dac6a-21dc-3466-8abb-057664ab39c7
跟我学spring3
浏览量:2418622
D659df3e-4ad7-3b12-8b9a-1e94abd75ac3
Spring杂谈
浏览量:3008764
43989fe4-8b6b-3109-aaec-379d27dd4090
跟开涛学SpringMVC...
浏览量:5639453
1df97887-a9e1-3328-b6da-091f51f886a1
Servlet3.1规范翻...
浏览量:259915
4f347843-a078-36c1-977f-797c7fc123fc
springmvc杂谈
浏览量:1597308
22722232-95c1-34f2-b8e1-d059493d3d98
hibernate杂谈
浏览量:250214
45b32b6f-7468-3077-be40-00a5853c9a48
跟我学Shiro
浏览量:5858943
Group-logo
跟我学Nginx+Lua开...
浏览量:701997
5041f67a-12b2-30ba-814d-b55f466529d5
亿级流量网站架构核心技术
浏览量:785206
社区版块
存档分类
最新评论

线程中断、超时与降级——《亿级流量》内容补充

 
阅读更多

​最近一位朋友在公众号留言问一个关于熔断的问题:

使用hystrix进行httpclient超时熔断错误,我是顺序操作的(没有并发),发现hystrix会超时断开,但是会导致hystrix线程池不断增多,直到后面因线程池装不下拒绝?

 

而该问题跟线程中断、超时与降级等有关,因此本文将详细介绍导致这个问题背后的原因。

 

需要提前了解的知识:

你的Java代码可中断吗(1)

你的Java代码可中断吗(2)

 

当我们在线程中执行如用户请求任务时,比如HTTP处理线程,最担心的什么?

1、线程数无限增长;

2、线程执行时间长;

3、线程不可中断。

 

对于线程数无限增长,我们可以通过使用线程池来控制线程数量,控制线程不是无限增长的。

 

对于线程执行时间长,我们应设置合理的超时时间来保障线程执行时间可控,当超时时要么返回给用户错误页面,要么可以返回降级页面。

 

对于线程不可中断,我们应想办法将线程设计的可中断,从而在遇到问题可中断线程并降级处理。

 

线程池可以参考我新书《亿级流量》中的“第12章连接池线程池详解”。超时时间可以参考我新书《亿级流量》中的“第6章 超时与重试机制”。

 

接下来的部分将主要讲解线程中断。

 

线程中断是通过Thread.interrupt()方法来做,一般是在A线程来中断B线程。

首先我们来看下该方法的一些Javadoc描述:

  1. 如果线程被Object的wait()、wait(long)、wait(long, int) 或者Thread的join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)方法阻塞,执行线程中断,且抛出InterruptedException,但中断状态并清空重置,即Thread. isInterrupted()返回false;

  2. 如果线程被java.nio.channels.InterruptibleChannel上的一个I/O操作阻塞,执行线程中断,且该InterruptibleChannel将被关闭,抛出java.nio.channels.ClosedByInterruptException,线程中断状态会设置,即Thread. isInterrupted()返回true;

  3. 如果线程被java.nio.channels.Selector阻塞,执行线程中断,该Selector#select()方法将立即返回,相当于调用了java.nio.channels.Selector#wakeup(),不会抛出异常,但会设置中断状态,即Thread. isInterrupted()返回true;

  4. 如果不满足以上条件的,那么执行线程中断不会抛出异常,仅设置中断状态,即Thread. isInterrupted()返回true。也就是说我们代码要根据该状态来决定下一步怎么做。

 

从如上描述可以看出,如果方法异常描述上有抛出InterruptedException、ClosedByInterruptException异常的,说明该方法可以中断,如“public final native void wait(longtimeout) throws InterruptedException”,但是中断状态会被会被重置要看其Javadoc描述。其他情况基本都是设置中断状态而不会中断掉操作。

 

BIO(Blocking I/O)操作不可中断

如java.net.Socket读写网络I/O时是阻塞的,除了设置超时时间外,还应该考虑让它可中断或者尽早中断。可以参考《你的Java代码可中断吗》。还有如JDBC驱动mysql-connector-java、HttpClient等大部分都是使用BIO,它们也是不可中断的。

 

NIO(New I/O)操作可中断

NIO涉及到两部分:java.nio.channels.Selector和java.nio.channels.InterruptibleChannel,它们是可中断的。如java.nio.channels#SocketChannel实现了InterruptibleChannel,如下方法都是可中断的,并会抛出ClosedByInterruptException异常:

  • connect(SocketAddress remote)

  • read(ByteBuffer[] dsts, int offset, int length)

  • read(ByteBuffer[] dsts)

  • write(ByteBuffer src) 

 

线程、BIO与中断

我们使用BIO实现的HttpClient来做个实验,如下代码所示:

 

public class BlockingIOTest {
  
public static void main(String[] args) throws Exception {
    Thread threadA =
new Thread(()-> {
      
try {
       
//该阻塞5s
      
String url = "http://localhost:9090/ajax";
      
//HttpClientBIO,不可中断
      
HttpResponse response = HttpClientUtils.getHttpClient().execute(new HttpGet(url));
      System.
out.println("http status code : " + response.getStatusLine().getStatusCode());
      
//虽然在threadB执行了threadA线程中断
      //
但是仅仅是设置了中断状态为true
      //
并没有中断线程A的执行,该线程还是正常的执行完成了
      
System.out.println("threadA is interrupted: " + Thread.currentThread().isInterrupted());
     }
catch (Exception e) {
       e.printStackTrace();
     }
   });
    Thread threadB =
new Thread(()-> {
      
try {
       Thread.sleep(
2000L);
      
//休眠2s后,中断线程A
      
threadA.interrupt();
     }
catch (Exception e) {
     }
    });
    threadA.start();
    threadB.start();
    Thread.sleep(
15000L);
 }
}

 

如上代码的输出结果为:

http status code : 200

threadA is interrupted: true

 

如上代码的执行流程是这样的:

  1. 线程A通过BIO实现HttpClient远程调用http://localhost:9090/ajax获取数据,而该服务需要5s才能响应;

  2. 线程B在线程A执行2s后进行了中断处理,但是线程A调用的HttpClient是阻塞且不可中断的操作,仅仅是设置了线程A的中断状态为true,因此其一直等待网络I/O完成;

  3. 当线程A从远程获取到结果后继续执行,Thread.currentThread().isInterrupted()将输出true,表示线程A被设置了中断状态。

 

从而需要注意设置了中断状态与中断执行不是一回事。因此对于使用BIO,一定要设置好连接和读写的超时时间,另外可以参考《你的Java代码可中断吗》进行可中断设计。

 

线程池、Future与中断

我们往线程池提交一个HttpClient任务,并通过Future来等待执行结果,如下代码所示:

public class ThreadPoolTest {
 private static ExecutorService executorService = Executors.newFixedThreadPool(5);
 public static void main(String[] args) throws Exception {
   Future<Integer> futureA =
executorService.submit((Callable) () -> {
    
//url会阻塞5s
   
String url = "http://localhost:9090/ajax";
   
//HttpClientBIO,不可中断
   
HttpResponse response = HttpClientUtils.getHttpClient().execute(new HttpGet(url));
   Integer result = response.getStatusLine().getStatusCode();
   System.
out.println("thread a result : " + result);
   
return response.getStatusLine().getStatusCode();
   });
   Future<Integer> futureB =
executorService.submit((Callable) () -> {
     
//url会阻塞5s
    
String url = "http://localhost:9090/ajax";
    
//HttpClientBIO,不可中断
    
HttpResponse response = HttpClientUtils.getHttpClient().execute(new HttpGet(url));
    Integer result = response.getStatusLine().getStatusCode();
    System.
out.println("thread b result : " + result);
    
return result;
   });
   try {
   
Integer resultA = futureA.get(100, TimeUnit.MILLISECONDS);
   }
catch (TimeoutException e) {
    System.
out.println("future a timeout");
  }
  
try {
    Integer resultB = futureB.get(
100, TimeUnit.MILLISECONDS);
  }
catch (TimeoutException e) {
    System.
out.println("future b timeout");
   }
  
executorService.awaitTermination(10000L, TimeUnit.MILLISECONDS);
 }
}

如上代码的输出结果为:

future a timeout

future b timeout

thread a result : 200

thread b result : 200

 

如上代码的执行流程是这样的:

  1. 主线程往线程池提交了两个HttpClient阻塞调用任务,该任务响应时间为5s;

  2. 主线程阻塞在两个带超时的Future等待上,Future在等待线程池任务执行结束,Future的超时时间设置为100ms,所以很快就超时并返回了,主线程继续执行,在《亿级流量》中我们用到了很多这种方法进行并发获取数据和降级或熔断处理;

  3. 线程池中的两个任务其实并没有被中断,还是占用着线程池中的线程,在后台继续执行,直到完成。

 

从如上可以看出,使用Future时只是在主线程解除了阻塞,并没有连带把线程池任务取消掉,还是占用着线程并阻塞执行。

 

之前有位同学在公众号后台留言咨询:

使用hystrix进行httpclient超时熔断错误,我是顺序操作的(没有并发),发现hystrix会超时断开,但是会导致hystrix线程池不断增多,直到后面因线程池装不下拒绝?

 

看完如上示例,应该能解决该读者的疑惑。虽然熔断了,但是线程中的操作并没有真正的中断,而是还占着线程资源。

 

接下来我们可以简单看下Future其中的一个实现FutureTask:

 

超时等待方法get(long timeout, TimeUnit unit)伪代码:

while(true) {
if (Thread.interrupted()) {//如果当前线程中断了,处理现场,并抛出中断异常
   //some code
  throw new InterruptedException();
}
//判断剩余休眠时间
 nanos = deadline - System.nanoTime();
if (nanos <= 0L) {//如果没有休眠时间了,则处理线程,并终止执行
   //some code
   return state;
}
//休眠一段时间,内部实现为UNSAFE.park(false, nanos)
 LockSupport.parkNanos(this, nanos);
}

取消方法cancel(boolean mayInterruptIfRunning)伪代码:

if (mayInterruptIfRunning) {//中断当前线程
 Thread t = runner;
if (t != null)
t.interrupt();
}
//执行UNSAFE.unpark(thread)唤醒休眠的当前线程
LockSupport.unpark(t);

即当我们调用Future#cancel时,是通过唤醒Future所在线程实现,当然实际是比这个要复杂的。

 

回填结果方法set(V v)伪代码:

//修改Future状态为完成
//保持v的值,从而Future#get能获取到
//通过LockSupport.unpark(t)唤醒休眠的线程

当线程池中的线程执行完成后,是通过Future#set把值设置回Future,从而唤醒休眠的线程,即阻塞在Future#get的等待,然后获取到该结果。

 

锁与中断

synchronized和ReentrantLock#lock()在获取锁的过程中是不可中断的,假设出现了死锁将一直僵持在那,无法终止阻塞。但我们可以使用可中断的ReentrantLock#lockInterruptibly()方法或者ReentrantLock#tryLock(long timeout, TimeUnit unit)实现可中断。

 

总结

在设计高可用系统时,尽量使用线程池,而不是通过每个请求创建一个线程来实现,通过线程池的拒绝策略来优雅的拒绝无法处理的请求。

 

检查整个请求链路设置合理的超时时间,跟调用方协商合理的SLA、降级限流方案。更长的超时时间意味着出现问题时请求堆积的越多,越可能产生雪崩。

 

明确知道自己的服务是否可中断,如果不可中断,应该使用线程池和Future实现伪可中断,通过Future配置合理的超时时间,当超时时执行相应的降级策略。也要清楚的知道通过Future只是伪中断,线程池中的任务还是在后台执行,当Future超时后进行重试时,会对调用的服务产生更多的请求,从而造成一种DDos,一定要注意相应的处理策略。

 

池大小、超时时间和中断没有最优的配置策略,要根据自己的场景来动态调整,在系统遇到高并发或者异常时,我们要保护什么,放弃什么,要有权衡。

扫一扫,关注我的公众号 

 

购买地址

1
1
分享到:
评论

相关推荐

    线程,同步与锁————Lock你到底锁住了谁?.htm

    线程,同步与锁————Lock你到底锁住了谁?.htm

    CPU中断——实现多线程机制

    ### CPU中断——实现多线程机制 #### 一、引言 在计算机系统中,多线程机制是一种常见的技术手段,用于提高程序的执行效率和响应能力。它允许在一个进程中同时运行多个线程,每个线程都可以独立地执行任务。为了...

    Java中实现线程的超时中断方法实例

    概述:在 Java 中实现线程的超时中断是非常重要的,特别是在熔断降级组件中。熔断降级组件需要在指定的超时时间内中断请求线程,以避免请求长时间阻塞系统资源。在这篇文章中,我们将介绍如何在 Java 中实现线程的...

    java通过线程控制程序执行超时(新)

    总结起来,Java通过线程控制程序执行超时是通过结合线程机制、Future/Callable接口以及中断机制实现的。在设计和实现超时控制时,要考虑到基本数据类型和反射的应用,以满足各种复杂的需求。合理地使用这些工具,...

    Java线程超时监控

    如果超时,当前线程将恢复执行,并且可以通过调用`interrupt`来中断目标线程(但这并不意味着目标线程会立即停止,它可能需要在内部检查`isInterrupted()`或捕获`InterruptedException`来响应中断)。 在设计多线程...

    并发编程——认识java里的线程(csdn)————程序.pdf

    并发编程——认识 Java 里面的线程 在 Java 编程中,并发编程是一个非常重要的概念。Java 程序天生就是多线程的,main 方法开始执行后,按照既定的代码逻辑执行,看似没有其他线程参与,但实际上 Java 程序天生就是...

    Java多线程之定时任务 以及 SpringBoot多线程实现定时任务——异步任务

    1. SpringBoot 自定义线程池以及多线程间的异步调用(@Async、@EnableAsync) 2.Java多线程之定时任务 以及 SpringBoot多线程实现定时任务 3.@EnableScheduling 与 @Scheduled

    哈工大操作系统实验8——内核级线程

    哈工大操作系统实验8——内核级线程。本次的实验仅完成了用户态的实现。内核级要实现实在困难,耗费巨大精力也不见得能有好的成效,而且重要的是内核级仅占一个。

    开涛高可用高并发-亿级流量核心技术

    5.2.1 超时降级 95 5.2.2 统计失败次数降级 95 5.2.3 故障降级 95 5.2.4 限流降级 95 5.3 人工开关降级 96 5.4 读服务降级 96 5.5 写服务降级 97 5.6 多级降级 98 5.7 配置中心 100 5.7.1 应用层API封装 100 5.7.2 ...

    Visual C++高级编程技术——MFC与多线程篇.rar

    本教程“Visual C++高级编程技术——MFC与多线程篇”将深入探讨这两个关键概念。 MFC是C++面向对象编程的一个重要框架,它基于Windows API,将复杂的Win32 API函数封装为易于理解和使用的类。MFC包含了一系列的类,...

    vb.net C#线程锁超时控制代码

    ManualResetEvent是一个超时等待的线程锁,如果超时返回false,接收指令显示true,但是没法实现在超时后让他继续等待,这种需求我们可以用在以下场景: 控制线程超时方法 1.[主线程]:请求方发送请求,立即创建超时等待...

    操作系统实验报告——线程与进程同步

    操作系统实验报告——线程与进程同步,主要探讨了在Linux环境下如何实现进程和线程的同步,以解决经典的生产者-消费者问题。该实验旨在帮助学生掌握操作系统提供的同步机制,并深化对经典同步问题的理解。 实验内容...

    线程超时死掉

    解决线程的死掉问题和超时问题特别好使,在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent...

    Java 语法总结——线程(线程)

    3. 线程中断:通过Thread对象的interrupt()方法发送中断请求,线程内部可以通过isInterrupted()或interrupted()方法检查并响应中断。 4. 线程礼让:通过Thread.yield()方法让当前线程暂停,但不会释放CPU,而是让...

    Android线程结束——合理的结束你想结束的线程

    而AsyncTask是Android提供的轻量级异步任务框架,适合短时间、快速的任务,且能方便地与UI进行交互。 对于Thread的结束,有以下几点需要注意: 1. **不要直接调用Thread对象的stop()方法**:这个方法已经被弃用,...

    android——多线程

    标题"android——多线程"和描述"android——Handler与多线程应用范例"暗示我们将深入探讨如何在Android中使用Handler来管理多线程。 Android系统默认运行在一个单线程环境中,即主线程,也被称为UI线程。主线程主要...

    多线程编程——线程的同步

    在“多线程编程之四——线程的同步”这个文件中,可能包含了上述各种同步机制的具体实现示例和详细说明,这对于初学者来说是一份非常宝贵的参考资料。通过学习和理解这些例子,开发者可以更好地掌握如何在实际项目中...

    多线程编程之四——线程的同步-VC知识库文章[归纳].pdf

    多线程编程之四——线程的同步-VC知识库文章[归纳].pdf

    ZYNQ进阶之路14”博客对应源代码,该代码通过FIFO阈值触发中断和超时中断实现ZYNQ PS端uart接收不定长数据.zip

    在本文中,我们将深入探讨如何使用ZYNQ处理器系统(PS)通过FIFO阈值触发中断和超时中断来实现在UART(通用异步收发传输器)接口上接收不定长数据。首先,我们需要理解ZYNQ架构以及UART在嵌入式系统中的作用。 ZYNQ...

    第10章 线程.ppt————电子版_ppt版

    预防和解决死锁是多线程编程中的重要问题,可以通过避免循环等待、超时机制和死锁检测来防止死锁的发生。 总的来说,理解和掌握线程是成为专业IT人员的必备技能,它涉及到程序并发执行、资源管理、性能优化等多个...

Global site tag (gtag.js) - Google Analytics