Java线程
Contents
💠
-
- 6.1. Quasar
- 6.2. Virtual Threads
💠 2024-09-13 10:39:04
Java线程
Java并发 当开始使用多线程时,就要开始考虑并发安全了
参考: 面试官:Java如何绑定线程到指定CPU上执行?
Thread Affinity底层优化选项:更多复用缓存以及减少线程的上下文切换
还可以将应用做强定制化,网卡绑定CPU,计算绑定CPU。能避免调度开销,但同时这是双刃剑,资源没有经过操作系统统一调度无法做到资源的有效共享。类似于虚拟化和物理机的一种权衡,虚拟化可以让资源共享,但是降低了CPU执行效率。物理机可以独占CPU,没法让CPU资源充分利用。
生命周期
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方法源码
|
|
Thread类的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()?
线程优先级: 多个线程同时运行时,由线程调度器来决定哪些线程运行,哪些等待以及线程切换的时间点. 由于各个操作系统的线程调度器的实现各不相同, 所以依赖JDK来设置线程优先级策略是错误和平台不可移植性的.
yield
join
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
设计: ThreadLocalMap 线程对象做key的一个封装Map(但是未实现Map接口),一个线程可以有多个ThreadLocal
Alibaba TTL 使用场景
可看作ThreadLocal的一种特殊实现
- 主要流程: com.alibaba.ttl.TtlRunnable#run
- 提交任务时对run方法封装,先复制当前 TransmittableThreadLocal
- 等待要调度执行时,重放复制的TransmittableThreadLocal值,从而实现父子线程间上下文的传递
- 注意:因为只是处理了TransmittableThreadLocal,所以其他ThreadLocal值需要做传递时,需要通过装饰器去手动复制,例如SpringSecurity的SecurityContextHolder, slf4j的MDC
Hook
- 注册Hook:
Runtime.getRuntime().addShutdownHook(Thread thread)
- 在JVM正常退出时会调用已注册的Hook逻辑
- 例如 System.exit(), 或者 Java 进程收到退出的信号 SIGTERM SIGINT SIGQUIT 等等
- 但是 SIGKILL、 Runtime.halt()、断电、系统Crash 等情况下,
没有时机执行Hook
。 - 不能在Hook逻辑中调用
System.exit()
, 否则会阻塞JVM退出,但是可以调用Runtime.halt()
- 不能在Hook逻辑中增删Hook
- 在
System.exit()
执行后才注册的Hook逻辑不会被执行 Hook逻辑执行时完整性不可控
操作系统可控制当对JVM发出TERM(15)
信号后一段时间未结束时可强制结束KILL(9)
,此时Hook逻辑可能才执行了一半- 注册的Hook是按先后执行的,但是其中任意一个Hook抛出未处理的异常时会中断自身及后续Hook逻辑
优雅关机
Java层面
- 线程池设置关闭时等待已有任务线程执行完成
- 但是通常等待是会有限制(容器的健康检查等)的,所以还是会造成任务的中断,队列中任务的丢失
- 手动接收信号量 追加资源关闭逻辑:MQ,缓存,数据库
环境层面
当关闭服务器A时,先将该服务器的入口流量屏蔽,防止新的请求进入,然后等服务器完成原有请求的响应,以及一些资源清理行为后,完全关闭。
参考: Kubernetes 中如何保证优雅地停止 Pod
参考: JVM安全退出(如何优雅的关闭java服务)
CompletableFuture
线程池
协程
R大: JVM虚拟机未明确定义JVM线程和OS线程的关系,即可以1:1, N:1, M:N。 只是Hotspot实现为1:1。并且很早期的JDK就是N:1的绿色线程实现,后面才改成1:1和系统线程绑定
Quasar
Virtual Threads
Virtual Threads 19预览 21Release | 来源于 OpenJDK: Loom
项目目标高吞吐量,轻量级并发模型,结构化并发&调度
试用总结:如果要引入生产,需要关注整个JEP的文档,调试确认细节后才能使用,不然就会陷入到各种诡异的问题上。
特性:
- 依赖一个公用的ForkJoin线程池执行任务 即 不推荐执行CPU密集型任务,只建议用来执行io密集类任务(21对有可能阻塞的api都加上了特定处理代码)从而提高吞吐量
- 正常线程内代码无法感知 协程内代码的异常,反之也是一样,线程和协程间的局部变量也是隔离的
- 协程的线程栈存储在堆内存中,为了规避大量协程导致的栈溢出
虚拟线程:Java的新利器? Java19 正式 GA!看虚拟线程如何大幅提高系统吞吐量
虚拟线程 - VirtualThread源码透视
Author Kuangcp
LastMod 2018-11-21