💠

💠 2024-09-13 10:39:04


Java线程

个人学习代码

Java并发 当开始使用多线程时,就要开始考虑并发安全了


参考: 面试官:Java如何绑定线程到指定CPU上执行?
Thread Affinity底层优化选项:更多复用缓存以及减少线程的上下文切换

还可以将应用做强定制化,网卡绑定CPU,计算绑定CPU。能避免调度开销,但同时这是双刃剑,资源没有经过操作系统统一调度无法做到资源的有效共享。类似于虚拟化和物理机的一种权衡,虚拟化可以让资源共享,但是降低了CPU执行效率。物理机可以独占CPU,没法让CPU资源充分利用。


生命周期

参考博客 | Blog: 线程详解 | 参考Java-learning仓库

java.lang.Thread.State

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
    • Object.wait()
    • Thread.join()
    • LockSupport.park()
  • TIMED_WAITING
    • Thread.sleep()
    • Object.wait(timeout)
    • Thread.join(timeout)
    • LockSupport.parkNanos()
    • LockSupport.parkUntil()
  • TERMINATED
    • 终止态,不管是正常执行结束还是异常中断。

创建

  • 创建线程有三种创建方式: 继承,实现接口,实例化匿名方法。示例代码

Thread类源码 Runnable,target,run,start 关系

  • Runnable是一个接口
  • target是Thread类中类型为Runnable,名为target的属性
  • run是Thread类实现了Runnable的接口,重写的方法。
  • start是启动线程的方法 native
  • 在Thread类中,调用关系为:start->start0->run->target.run

Thread类的run方法源码

1
2
3
4
5
    public void run() {
        if (target != null) {
            target.run();
        }
    }

Thread类的target属性

1
2
    /* What will be run. */
    private Runnable target;
  • target属性由 private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc)方法初始化。
    • init方法在Thread类的构造方法里被调用

控制

  • 当调用 join() 时,当前调用线程将会阻塞,直到目标线程完成为止。

Object.wait 转为两种Waiting状态

LockSupport.park

Can LockSupport.park() replace Object.wait()?

thread states

线程优先级: 多个线程同时运行时,由线程调度器来决定哪些线程运行,哪些等待以及线程切换的时间点. 由于各个操作系统的线程调度器的实现各不相同, 所以依赖JDK来设置线程优先级策略是错误和平台不可移植性的.

yield

join

interrupt

Oracle interrupt

  • java.lang.Thread#interrupt
    • 这个方法仅仅是标记下状态,在一些阻塞类方法调用时会检查该状态值(sleep wait join yield 等等), 如果线程一直在循环跑CPU计算,那这个线程不会停止
  • java.lang.InterruptedException
    1
    2
    3
    
        // 判断当前线程是否已发生中断
       if (Thread. interrupted())  // Clears interrupted status!
       throw new InterruptedException();
    

中断机制是通过一个称为中断状态的内部标志来实现的。调用 Thread.interrupt 会设置该标志。当线程通过调用静态方法 Thread.interrupted 检查中断时,中断状态会被清除。一个线程用来查询另一个线程中断状态的非静态 isInterrupted 方法不会改变中断状态标志。
按照惯例,任何通过抛出 InterruptedException 而退出的方法在退出时都会清除中断状态。不过,中断状态总是有可能被另一个调用中断的线程立即再次设置。


Signal

由于Java是跨平台语言,主要考虑Window和unix系平台,后者在生产中使用居多,因此重点关注

Linux的Signal
快速理解:

  • Kill 9信号: 无法监听和屏蔽
  • TERM 15信号:默认退出进程信号
  • INT 2信号: IDEA中停止JVM时发出的就是该信号

相关JVM参数 -Xrs 忽略(1,2,3,4,5,6,7,8,11,15) oracle java command注意Linux和Windows实现及信号量不一样

  • 忽略的逻辑实现为:JVM接收信号量然后什么都不做。
  • 注意此时Java应用代码无法手动监听对应的信号量,注册监听时会报错

销毁

观测异常

java.lang.Thread.UncaughtExceptionHandler Interface for handlers invoked when a Thread abruptly terminates due to an uncaught exception.

通过设置静态属性 Thread.setDefaultUncaughtExceptionHandler(),可以观测由于未捕获的异常导致Thread被销毁的情况,可加入监控和告警的逻辑


ThreadLocal

Oracle: ThreadLocal

设计: ThreadLocalMap 线程对象做key的一个封装Map(但是未实现Map接口),一个线程可以有多个ThreadLocal

Alibaba TTL 使用场景可看作ThreadLocal的一种特殊实现

  • 主要流程: com.alibaba.ttl.TtlRunnable#run
    • 提交任务时对run方法封装,先复制当前 TransmittableThreadLocal
    • 等待要调度执行时,重放复制的TransmittableThreadLocal值,从而实现父子线程间上下文的传递
    • 注意:因为只是处理了TransmittableThreadLocal,所以其他ThreadLocal值需要做传递时,需要通过装饰器去手动复制,例如SpringSecurity的SecurityContextHolder, slf4j的MDC

一次「找回」TraceId的问题分析与过程思考


Hook

  • 注册Hook:Runtime.getRuntime().addShutdownHook(Thread thread)
  • 在JVM正常退出时会调用已注册的Hook逻辑
    1. 例如 System.exit(), 或者 Java 进程收到退出的信号 SIGTERM SIGINT SIGQUIT 等等
    2. 但是 SIGKILL、 Runtime.halt()、断电、系统Crash 等情况下, 没有时机执行Hook
    3. 不能在Hook逻辑中调用System.exit(), 否则会阻塞JVM退出,但是可以调用Runtime.halt()
    4. 不能在Hook逻辑中增删Hook
    5. System.exit()执行后才注册的Hook逻辑不会被执行
    6. Hook逻辑执行时完整性不可控 操作系统可控制当对JVM发出TERM(15)信号后一段时间未结束时可强制结束KILL(9),此时Hook逻辑可能才执行了一半
    7. 注册的Hook是按先后执行的,但是其中任意一个Hook抛出未处理的异常时会中断自身及后续Hook逻辑

优雅关机

Java层面

  1. 线程池设置关闭时等待已有任务线程执行完成
    • 但是通常等待是会有限制(容器的健康检查等)的,所以还是会造成任务的中断,队列中任务的丢失
  2. 手动接收信号量 追加资源关闭逻辑:MQ,缓存,数据库

环境层面

当关闭服务器A时,先将该服务器的入口流量屏蔽,防止新的请求进入,然后等服务器完成原有请求的响应,以及一些资源清理行为后,完全关闭。

参考: Kubernetes 中如何保证优雅地停止 Pod
参考: JVM安全退出(如何优雅的关闭java服务)


CompletableFuture

CompletableFutureTest


线程池

Note: 线程池


协程

R大: JVM虚拟机未明确定义JVM线程和OS线程的关系,即可以1:1, N:1, M:N。 只是Hotspot实现为1:1。并且很早期的JDK就是N:1的绿色线程实现,后面才改成1:1和系统线程绑定

Quasar

Github: Quasar

Virtual Threads

Virtual Threads 19预览 21Release | 来源于 OpenJDK: Loom项目目标高吞吐量,轻量级并发模型,结构化并发&调度

试用总结:如果要引入生产,需要关注整个JEP的文档,调试确认细节后才能使用,不然就会陷入到各种诡异的问题上。

特性:

  • 依赖一个公用的ForkJoin线程池执行任务 即 不推荐执行CPU密集型任务,只建议用来执行io密集类任务(21对有可能阻塞的api都加上了特定处理代码)从而提高吞吐量
  • 正常线程内代码无法感知 协程内代码的异常,反之也是一样,线程和协程间的局部变量也是隔离的
  • 协程的线程栈存储在堆内存中,为了规避大量协程导致的栈溢出

虚拟线程:Java的新利器? Java19 正式 GA!看虚拟线程如何大幅提高系统吞吐量
虚拟线程 - VirtualThread源码透视