Java 并发编程之一——天生的多线程语言
《Java 并发编程》专栏旨在从头讲解Java 并发编程的相关知识。为初学者和相关开发的同学提供一个由浅入深、由内到外的学习方向。如果文章中存在错误或者讲解不清楚的地方,欢迎大家互相讨论和指正!
问题背景
我相信,很多人都听说过一个结论:Java 能够很好的支持并发编程,它是一个支持多线程的编程语言。那么,如果我们真的开始讨论起来并发编程,就会质疑这句话:真的如此吗?我简单的执行一个 main
函数,看打印出来的,也只有1个线程。此时的多线程体现在哪里呢? 如果你有相关的疑问。那么我要先恭喜你,因为你有了足够的思考,而且开始意识到:你必须开始深入的学习或者尝试使用 Java 并发编程了。
程序测试
我们简单的看一下下面的程序片段:
public static void main(String[] args) throws Exception{
// 获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.
getThreadName());
}
Thread.sleep(100000);
}
该函数的功能是获取Java线程管理MXBean,并通过MXBean获取当前所有线程的信息,然后遍历线程信息,仅打印线程ID和线程名称信息。
我们执行一下代码,会发现控制台打印了以下的信息:
[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
与我们想象的不一样,一个简单的main函数,竟然打印出来了5条不同的线程。那么这五条线程,分别代表着什么,各自有什么作用呢?
结果分析
我们从下到上,依次分析main函数的结果:
-
main
正常的main函数线程。它是Java 程序的主线程,程序的入口点。
-
Reference Handler
Java 虚拟机的垃圾回收器(Garbage Collector)中的线程之一,负责处理引用对象的清理工作。(例如JVM 中的四种引用:强、软、弱、虚)
-
Finalizer
也是垃圾回收器中的线程之一,用于执行对象的
finalize
方法。finalize
方法是在对象被垃圾回收前执行的方法。注意:
- 只有当开始一轮垃圾收集的时候,才会开始调用
fnalize()
方法; - JVM 在垃圾收集的时候,把失去引用的对象封装到我们的 Finalizer对象(Reference实现,放入我们的一个 F-queue中。由 Finalizer 线程执行
finalize()
方法); - 在JVM虚拟中,它属于daemon prio=8 的高优先级的守护线程。
- 只有当开始一轮垃圾收集的时候,才会开始调用
-
Signal Dispatcher
信号分发器线程,用于分发信号给 Java 虚拟机的处理器。通过cmd发送 jstack命令,传到 jvm进程,信号分发就要发挥作用。
-
Attach Listener
附加监听器。是 Java 虚拟机中的一个附属线程。它负责接收来自外部工具的命令,允许这些工具动态地连接到正在运行的 Java 虚拟机实例。JDK 的工作类,提供JVM 进程之间通信的工具。 例如执行的操作 : java -version jstack jamp dump。
注意:
Attach Listener
有两种触发方式:- JVM 参数 -XX:StartAttachListner
- 延迟开启(被动触发)
-
Monitor Ctrl-Break
这是 Windows 平台上的一个特定线程,用于处理用户键盘输入产生的中断信号。因为我使用的是IDEA,因此也可以认为这个线程是IDEA自带的。通过反射,用来监听线程状态`
dump 内容分析
接下来我们继续dump这段线程,查询对应的虚拟机状态,并且根据我们之前学到的知识,增加相关注释:
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
"Attach Listener" #3230 daemon prio=9 os_prio=0 tid=0x00007f99c0001000 nid=0xce3 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
//(延迟开启 或着直接开启)
"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f9a04202800 nid=0x16 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f9a041d3000 nid=0x15 in Object.wait() [0x00007f99d2d17000]
java.lang.Thread.State: WAITING (on object monitor)
//(专注垃圾收集-- 并行收集,不阻碍用户线程,低优先级线程。 prio=8 是一个守护线程
//并没有真正的开启,不足以发生 minorgc 或者 fullgc)
at java.lang.Object.wait(Native Method)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000072853e5c8> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f9a041ce000 nid=0x14 in Object.wait() [0x00007f99f40ef000]
java.lang.Thread.State: WAITING (on object monitor)
//(GC相关线程,引用处理线程:GC很重要,优先级很高)
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000072853e608> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
我们可以清楚看到,对应的dump文件中,存在着相关的线程。
因此,我们可以轻易地得出结论:哪怕是简单的main
函数,在Java 中,也是多个线程协调执行的结果。
总结
Java 是一门支持并发编程的语言,它通过提供内置的多线程支持使得程序能够执行多任务并且提高效率。在一个简单的 Java 程序中,就算是最基本的 main
函数运行时,实际上也会有多个系统级别的线程同时运行,提供不同的服务。执行结果显示了除了主线程 (main
) 之外,还有一些如垃圾回收器的辅助线程 (Finalizer
、Reference Handler
),信号分发线程 (Signal Dispatcher
)等,这些线程都是 Java 虚拟机为了优化执行和管理工作而自动创建的。
通过本文的分享,大家可以深刻的理解为什么Java是天生的多线程编程语言,并在后续的并发编程的学习中,拥有并发线程的相关概念。
转载自:https://juejin.cn/post/7333631025757650963