JAVA多线程基础
java是一个天生就支持多线程的语言。
Java线程分类
java线程分为守护线程和非守护线程。
- 守护线程:和主线程一起结束的线程,叫守护线程,我们的垃圾回收线程就是一个守护线程。
- 非守护线程:主线程的结束不影响线程的执行的线程,也叫用户线程。
注意:
- 调用
t.setDaemon(true)方法可以将用户线程设置成守护线程。- 守护线程结束时不能保证finally语句块一定执行。
/**
* 守护线程
*
* @author yuhao.wang3
*/
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ " 任务执行 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(50);
}
System.out.println(Thread.currentThread().getName()
+ " 任务中断 " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("...........finally");
}
});
thread.setDaemon(true);
thread.start();
Thread.sleep(500);
}
}
输出结果:
"C:\Program Files\Java\jdk1.8.0_112\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:53547,suspend=y,server=n -Dvisualvm.id=210666216419015 -javaagent:C:\Users\yuhao.wang3\.IntelliJIdea2018.3\system\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_112\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_112\jre\lib\rt.jar;D:\workspace\spring-boot-student\spring-boot-student-concurrent\target\classes;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-web\1.5.13.RELEASE\spring-boot-starter-web-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\1.5.13.RELEASE\spring-boot-starter-tomcat-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\8.5.31\tomcat-embed-core-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\tomcat-annotations-api\8.5.31\tomcat-annotations-api-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\8.5.31\tomcat-embed-el-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\8.5.31\tomcat-embed-websocket-8.5.31.jar;C:\Users\yuhao.wang3\.m2\repository\org\hibernate\hibernate-validator\5.3.6.Final\hibernate-validator-5.3.6.Final.jar;C:\Users\yuhao.wang3\.m2\repository\javax\validation\validation-api\1.1.0.Final\validation-api-1.1.0.Final.jar;C:\Users\yuhao.wang3\.m2\repository\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.8.11.1\jackson-databind-2.8.11.1.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.8.0\jackson-annotations-2.8.0.jar;C:\Users\yuhao.wang3\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.8.11\jackson-core-2.8.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-web\4.3.17.RELEASE\spring-web-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-aop\4.3.17.RELEASE\spring-aop-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-beans\4.3.17.RELEASE\spring-beans-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-context\4.3.17.RELEASE\spring-context-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-webmvc\4.3.17.RELEASE\spring-webmvc-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-expression\4.3.17.RELEASE\spring-expression-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter\1.5.13.RELEASE\spring-boot-starter-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot\1.5.13.RELEASE\spring-boot-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\1.5.13.RELEASE\spring-boot-autoconfigure-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\boot\spring-boot-starter-logging\1.5.13.RELEASE\spring-boot-starter-logging-1.5.13.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-classic\1.1.11\logback-classic-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\ch\qos\logback\logback-core\1.1.11\logback-core-1.1.11.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jcl-over-slf4j\1.7.25\jcl-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\jul-to-slf4j\1.7.25\jul-to-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\log4j-over-slf4j\1.7.25\log4j-over-slf4j-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\org\springframework\spring-core\4.3.17.RELEASE\spring-core-4.3.17.RELEASE.jar;C:\Users\yuhao.wang3\.m2\repository\org\yaml\snakeyaml\1.17\snakeyaml-1.17.jar;C:\Users\yuhao.wang3\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\yuhao.wang3\.m2\repository\com\h2database\h2\1.4.197\h2-1.4.197.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar" com.xiaolyuh.DaemonThread
Connected to the target VM, address: '127.0.0.1:53547', transport: 'socket'
Thread-0 任务执行 2019-03-20 20:39:43_667
Thread-0 任务执行 2019-03-20 20:39:43_731
Thread-0 任务执行 2019-03-20 20:39:43_781
Thread-0 任务执行 2019-03-20 20:39:43_832
Thread-0 任务执行 2019-03-20 20:39:43_883
Thread-0 任务执行 2019-03-20 20:39:43_934
Thread-0 任务执行 2019-03-20 20:39:43_985
Thread-0 任务执行 2019-03-20 20:39:44_036
Thread-0 任务执行 2019-03-20 20:39:44_089
Thread-0 任务执行 2019-03-20 20:39:44_140
Disconnected from the target VM, address: '127.0.0.1:53547', transport: 'socket'
Process finished with exit code 0
我们可以发现finally 语句块就没执行。当thread.setDaemon(true);这句去掉时,即使主线程结果了,子线程也会一直运行下去。
查看JVM内的线程
public static void main(String[] args) {
//虚拟机线程管理的接口
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos =
threadMXBean.dumpAllThreads(false, false);
for(ThreadInfo threadInfo:threadInfos) {
System.out.println("["+threadInfo.getThreadId()+"]"+" "
+threadInfo.getThreadName());
}
}
[8] JDWP Command Reader
[7] JDWP Event Helper Thread
[6] JDWP Transport Listener: dt_socket
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main
我们发现启动一个main方法,JVM虚拟机会给我们启动8个线程(包含JVM 内置后台守护线程和JDWP 调试相关线程),主要分为三类:
- 业务承载:
main线程执行应用业务逻辑; - JVM 内部管理守护线程:
Reference Handler(引用处理)、Finalizer(终结方法执行)、Signal Dispatcher(信号分发)、Attach Listener(工具附加)保障 JVM 正常运行和可观测性; - JDWP 调试协议相关线程:3 个 JDWP 线程实现 JVM 与调试器的通信,支撑断点、单步执行等调试功能。
各线程作用解析
1. main(主线程)
- 核心作用:Java 应用程序的入口线程,执行
main(String[] args)方法,承载应用的核心业务逻辑(如初始化 Spring 容器、启动服务、执行业务代码等)。 - 关键特性:
- 属于非守护线程(用户线程),主线程执行完毕后,若没有其他非守护线程存活,JVM 会触发退出流程;
- 主线程可以创建其他子线程(守护线程/非守护线程),子线程的生命周期不依赖于主线程(除非主线程是唯一的非守护线程)。
2. Reference Handler(引用处理线程)
- 核心作用:JVM 内置的高优先级守护线程,负责处理 Java 引用类型(软引用、弱引用、虚引用)的后续清理逻辑,维护引用队列(
ReferenceQueue)的正常工作。 - 工作机制:
- 当对象被 GC 标记为可回收,且该对象关联了引用类型(如
WeakReference)时,JVM 会将该引用对象放入对应的ReferenceQueue; Reference Handler线程会循环监听ReferenceQueue,取出引用对象并执行后续处理(如清理关联资源、通知业务逻辑等);- 优先级为
Thread.MAX_PRIORITY(最高优先级),确保引用处理的及时性,避免内存泄漏。
- 当对象被 GC 标记为可回收,且该对象关联了引用类型(如
3. Finalizer(终结器线程)
- 核心作用:JVM 内置的低优先级守护线程,负责执行对象的
finalize()方法(对象的终结方法)。 - 工作机制:
- 当对象被 GC 第一次标记为可回收(且未覆盖
finalize()或已执行过finalize()除外),JVM 会将该对象放入Finalizer队列; Finalizer线程会逐个从队列中取出对象,调用其finalize()方法(该方法仅会被执行一次);- 执行完
finalize()后,对象会等待下一次 GC 才会被真正回收(finalize()可能让对象重新可达,避免被回收);
- 当对象被 GC 第一次标记为可回收(且未覆盖
- 关键注意:
finalize()方法性能低下且不可靠,JDK 9 后已标记为过时,推荐使用try-with-resources或手动释放资源替代。
4. Signal Dispatcher(信号分发线程)
- 核心作用:JVM 内置的守护线程,负责接收并分发操作系统发送给 JVM 进程的信号,将系统信号转换为 JVM 内部的处理逻辑。
- 工作场景:
- 接收操作系统信号(如
kill -3 <pid>生成线程 Dump、kill -15 <pid>触发 JVM 优雅退出、Ctrl+C中断进程等); - 将接收到的信号分发给 JVM 对应的处理模块(如信号
SIGQUIT触发线程 Dump 输出,信号SIGTERM触发 JVM 执行关闭钩子ShutdownHook); - 保障 JVM 能正确响应操作系统的外部指令,实现优雅关闭或故障诊断。
- 接收操作系统信号(如
5. Attach Listener(附加监听器线程)
- 核心作用:JVM 内置的守护线程,负责监听外部工具的附加(Attach)请求,建立外部工具与 JVM 进程的通信通道。
- 工作场景:
- 当使用
jstack(查看线程栈)、jmap(查看内存快照)、jstat(监控 JVM 状态)、Arthas(阿里诊断工具)等工具连接 JVM 进程时,这些工具会发送 Attach 请求; Attach Listener线程接收请求后,创建通信链路,允许外部工具读取 JVM 内部数据(线程信息、内存信息)或执行操作(如类热更新);
- 当使用
- 关键特性:该线程不是 JVM 启动时默认创建的,而是首次收到 Attach 请求时动态创建,之后一直存活等待后续请求。
6. JDWP Transport Listener: dt_socket(JDWP 传输监听器线程)
- 核心作用:JDWP(Java Debug Wire Protocol,Java 调试线协议)相关线程,负责监听调试器通过 Socket 传输发送的连接请求,建立调试器与 JVM 的调试通信链路。
- 工作场景:
- 当 Java 应用以 Debug 模式启动(如添加
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000参数)时,该线程会启动; - 监听指定的 Socket 端口(如 8000),等待 IDEA/Eclipse 等调试器连接,建立 TCP/IP 通信通道,为后续调试指令传输提供支撑;
dt_socket表示采用 Socket 作为调试数据的传输方式,此外还有dt_shmem(共享内存,仅本地调试支持)。
- 当 Java 应用以 Debug 模式启动(如添加
7. JDWP Event Helper Thread(JDWP 事件辅助线程)
- 核心作用:JDWP 调试相关线程,负责收集、整理 JVM 内部的调试事件,并将事件传递给调试器。
- 工作场景:
- 当 JVM 触发调试事件(如断点命中、线程启动/终止、类加载/卸载、方法进入/退出等)时,该线程会收集事件信息;
- 对事件进行格式化、过滤处理后,通过调试通信链路发送给调试器(如 IDEA 会在断点命中时暂停线程,显示当前变量值);
- 同时处理调试器发送的事件查询请求,确保调试器能实时感知 JVM 的运行状态。
8. JDWP Command Reader(JDWP 命令读取线程)
- 核心作用:JDWP 调试相关线程,负责从调试通信链路中读取调试器发送的调试命令,并分发给 JVM 内部的调试处理模块。
- 工作场景:
- 调试器(如 IDEA)会发送各类调试命令(如设置断点、移除断点、获取线程信息、执行表达式、暂停/恢复线程等);
- 该线程持续读取通信链路中的命令数据,解析命令类型后,调用 JVM 对应的调试接口执行命令;
- 命令执行完成后,将结果(如变量值、线程栈信息)返回给调试器,支撑调试器的交互操作(如查看变量、单步执行)。
新建线程的3种方式
- 继承
Thread类(Thread也实现了Runnable接口) - 实现
Runnable接口 - 实现
Callable接口
Callable和Runnable的区别
- Callable有返回值,Runnable没有。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
- Runnable的执行 new Thread(runnable).start(); Callable的执行需要用到FutureTask。
Runnable接口
@Test
public void runnableTest() {
// JDK 1.7
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "::Runnable异步任务1");
}
}).start();
// JDK 1.8
new Thread(() -> System.out.println(Thread.currentThread().getName() + "::Runnable异步任务2")).start();
}
输出结果:
Thread-3::Runnable异步任务1
Thread-4::Runnable异步任务2
Callable接口
@Test
public void callableTest() {
ExecutorService service = Executors.newFixedThreadPool(10);
// JDK 1.7
FutureTask<String> futureTask1 = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + "::Callable异步任务1";
}
});
// JDK 1.8
FutureTask<String> futureTask2 = new FutureTask<>(() -> Thread.currentThread().getName() + "::Callable异步任务2");
service.submit(futureTask1);
service.submit(futureTask2);
try {
System.out.println(futureTask1.get(5, TimeUnit.SECONDS));
System.out.println(futureTask2.get(5, TimeUnit.SECONDS));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
输出结果:
pool-1-thread-1::Callable异步任务1
pool-1-thread-2::Callable异步任务2
Callable任务如果提交给线程池异步执行必须封装为FutureTask,FutureTask他实现了Runnable接口。
停止一个线程
- 线程自然终止(自然执行完或抛出未处理异常)
- stop():这是一个抢占式方法不建议使用,stop()是直接停止线程,而不管该线程的资源是否已经释放掉了。
- suspend(),resume(): 线程的挂起与恢复,也不建议使用,因为suspend在调用过程中不会释放其占用的共享资源如:锁,容易引起死锁。
- interrupt():该方法是一个协作式方法,并不会强制关闭线程,他只是将线程的中断标志位设置成true(中断线程),默认是false(不中断线程),线程是否中断由线程自己决定,推荐使用这种方式去关闭线程。
使用
interrupt中断线程,我们必须在线程内部对线程的中断状态进行处理,否则即使调用了interrupt()方法也不会有效果。有两个方法可以知道线程是否处于中断状态:
isInterrupted():它是一个无副作用的方法,可以重复调用。- 静态的
interrupted():它是一个有无副作用的方法,在调用的时候会同时将中断标志为设置成false。
注意: 方法里如果抛出
InterruptedException异常,线程的中断标志位会被复位成false,如果确实是需要中断线程,我们需要自己在catch语句块里再次调用interrupt()方法。
/**
* 中断线程有异常
*
* @author yuhao.wang3
*/
public class HasInterrputException {
public static void main(String[] args) throws InterruptedException {
Thread endThread = new Thread(() -> {
String threadName = Thread.currentThread().getName();
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("UseThread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(threadName + " catch interrput flag is " + Thread.currentThread().isInterrupted() +
" at " + (LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS"))));
Thread.currentThread().interrupt();
e.printStackTrace();
}
System.out.println(threadName);
}
System.out.println(threadName + " interrput flag is " + Thread.currentThread().isInterrupted());
});
endThread.start();
System.out.println("Main:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
Thread.sleep(800);
System.out.println("Main begin interrupt thread:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss_SSS")));
endThread.interrupt();
}
}
输出结果:
Main:2019-03-20 19:57:35_221
thread:2019-03-20 19:57:35_221
Main begin interrupt thread:2019-03-20 19:57:36_035
Thread-0 catch interrput flag is false at 2019-03-20 19:57:36_035
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.xiaolyuh.HasInterrputException.lambda$main$0(HasInterrputException.java:18)
at java.lang.Thread.run(Thread.java:745)
Thread-0
Thread-0 interrput flag is true
Process finished with exit code 0
如果将 catch里面的Thread.currentThread().interrupt();去掉,那么线程就会永远执行。
线程的几种状态

线程一共有5个状态,如上图所示:
yield():方法也是一个协作式方法,他会让出CPU执行权,但是下一次CPU调度的时候该线程依然有可能再次获取到CPU执行权,并执行。sleep():阻塞线程,让出CPU执行权,但是不会释放锁,到了时间后会继续执行;状态切换到阻塞和可运行都会产生上下文切换;多用于使当前线程等待;这是Thread的方法,不需要获取到锁就可以执行。wait():阻塞线程,让出CPU执行权,会释放锁,唤醒后需要重新获取锁才能执行;状态切换到阻塞和可运行都会产生上下文切换,但是切换到可运行时候还有一锁池状态,必须要获取到锁后才会切换到可运行状态;多用于线程间的通信,如果是多线程编程建议使用该方法,减少cpu上下文的切换;这是Object的方法,必需要获取到锁才可以执行。notify():唤醒等待队列最前面的一个线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁才可以执行。notifyAll():唤醒等待队列里面所有的线程,该方法不会立即唤醒线程,而是需要等方法体执行完了才会执行唤醒动作;这是Object的方法,必需要获取到锁就才可以执行。join():线程插队,当t1线程在执行的过程中调用了t2线程的join()方法,那么t1线程会挂起(内部调用的是wait/notify机制),t2线程会立即执行。
Thread.start()与Thread.run()的核心区别:
- 核心差异:
start()会创建新线程(异步执行),run()仅为普通方法调用(同步执行,无新线程);- 调用规则:
start()不可重复调用(重复调用会抛异常),run()可重复调用;- 底层逻辑:
start()依赖JVM底层创建线程,run()仅执行业务逻辑,与线程机制无关。
源码
https://github.com/wyh-spring-ecosystem-student/spring-boot-student/tree/releases
spring-boot-student-concurrent 工程
layering-cache
为监控而生的多级缓存框架 layering-cache这是我开源的一个多级缓存框架的实现,如果有兴趣可以看一下
更多推荐

所有评论(0)