Java线程与并发新手入门:线程终止+通信代码
在Java开发中,线程与并发是重点知识点。本文将从进程、线程、协程的基础概念入手,逐步拆解线程状态、线程终止、同步机制、线程通信等内容,搭配完整可运行代码示例,帮助大家快速掌握Java并发要点,内容简洁实用,适合新手入门与复习巩固。
一、基础概念:进程、线程、协程
要理解Java线程,首先要区分进程、线程、协程三者的区别,明确各自的定位与作用:
-
进程:操作系统资源分配的最小单位,拥有独立的内存空间,进程间相互隔离。Java程序启动时,会自动创建一个独立的Java进程。
-
线程:操作系统CPU调度的最小单位,是进程内的执行单元,共享所属进程的内存与资源。一个进程可以包含多个线程(如Java默认的主线程、GC线程)。
-
协程/虚拟线程:用户态的轻量级线程,由程序自身调度,无需操作系统内核介入,切换开销极低。Java原生不支持协程,但Java 19+引入的虚拟线程(Virtual Thread),实现了类协程的轻量级并发能力。
对比:进程管资源,线程管调度,协程(虚拟线程)追求高效并发,适合I/O密集型场景。
二、多线程的价值与适用场景
使用多线程的目的是提升程序效率,其价值主要体现在三个方面:
-
提高CPU利用率:当线程因I/O操作(如读写文件、网络请求、数据库操作)阻塞时,CPU可切换到其他就绪线程执行,避免CPU空闲等待。
-
提升程序响应性:例如GUI程序中,主线程负责界面交互,子线程处理耗时计算,避免界面卡顿。
-
简化复杂任务:将多个独立任务拆分为不同线程,代码逻辑更清晰(如多用户Web服务的请求处理)。
补充:单核CPU下,多线程并非毫无意义——I/O密集型场景下,多线程可在等待I/O时切换线程,让CPU持续工作;但纯计算密集型场景下,频繁的上下文切换会增加开销,反而降低效率。
三、Java线程的6种状态及转换
Java线程的生命周期包含6种状态,所有状态均定义在java.lang.Thread.State枚举中,可通过Thread.getState()方法查看线程当前状态,状态及转换规则如下:
-
NEW(初始态):线程对象已创建,但未调用
start()方法,尚未启动。 -
RUNNABLE(可运行态):调用
start()后进入该状态,包含两个子状态——READY(等待CPU分配时间片)和RUNNING(正在执行)。 -
BLOCKED(阻塞态):线程尝试获取
synchronized锁时,因锁被其他线程占用而阻塞。 -
WAITING(等待态):线程主动放弃CPU,无超时限制,需其他线程显式唤醒(如调用
notify())才能恢复。 -
TIMED_WAITING(超时等待态):带超时限制的等待状态,超时后自动恢复(如调用
Thread.sleep(long))。 -
TERMINATED(终止态):线程执行完
run()方法或因未捕获异常退出,生命周期结束,无法再次重启。
总结:NEW → RUNNABLE(start());RUNNABLE ↔ WAITING/TIMED_WAITING(等待/唤醒);RUNNABLE ↔ BLOCKED(竞争锁);RUNNABLE → TERMINATED(执行完毕/异常)。
四、线程优先级验证(完整代码示例)
Java线程支持设置优先级(1~10,默认5),但优先级的调度效果依赖操作系统,无法保证严格生效。以下是验证线程优先级对执行次数影响的完整代码:
package basic;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Priority {
private static volatile boolean notStart = true;
private static volatile boolean notEnd = true;
public static void main(String[] args) throws InterruptedException {
List<Job> jobs = new ArrayList<>();
// 前5个线程设为最低优先级,后5个设为最高优先级
for (int i = 0; i < 10; i++) {
int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
Job job = new Job(priority);
jobs.add(job);
Thread t = new Thread(job, "thread" + i);
t.setPriority(priority);
t.start();
}
notStart = false; // 启动所有线程
TimeUnit.SECONDS.sleep(10); // 让线程执行10秒
notEnd = false; // 终止所有线程
// 输出各线程执行次数,观察优先级影响
for (Job job : jobs) {
System.out.println("Job priority" + job.priority + " Job Count" + job.jobCount);
}
}
// 线程执行的任务
static class Job implements Runnable {
private int priority;
private long jobCount;
public Job(int priority) {
this.priority = priority;
}
@Override
public void run() {
// 等待notStart变为false,统一启动
while (notStart) {
Thread.yield(); // 主动让出CPU时间片
}
// 持续执行,直到notEnd变为false
while (notEnd) {
Thread.yield();
jobCount++;
}
}
}
}
说明:Thread.yield()方法会让当前线程主动让出CPU,从RUNNING回到READY状态,重新参与调度,但不改变线程的整体状态(仍属于RUNNABLE)。
五、线程终止:优雅终止的两种方式(完整代码示例)
Java中禁止使用stop()、suspend()、resume()等废弃方法(会导致数据不一致、死锁等问题),推荐使用「volatile标志位」和「interrupt()中断」两种优雅终止方式,以下是完整代码:
package basic;
import java.util.concurrent.TimeUnit;
public class Shutdown {
public static void main(String[] args) throws InterruptedException {
// 方式1:使用 interrupt() 终止线程(可中断阻塞状态)
Runner one = new Runner();
Thread thread = new Thread(one, "CountThread");
thread.start();
TimeUnit.SECONDS.sleep(1); // 让线程执行1秒
thread.interrupt(); // 发送中断信号
// 方式2:使用 volatile 标志位终止线程(实现简单)
Runner two = new Runner();
thread = new Thread(two, "CountThread");
thread.start();
TimeUnit.SECONDS.sleep(1); // 让线程执行1秒
two.cancel(); // 修改标志位,终止线程
}
private static class Runner implements Runnable {
private long i = 0;
private volatile boolean on = true; // 线程终止标志位(volatile保证可见性)
@Override
public void run() {
// 双重判断:标志位 + 中断状态,确保线程安全终止
while (on && !Thread.currentThread().isInterrupted()) {
i++; // 模拟业务逻辑
}
System.out.println("count:" + i); // 输出执行次数
}
// 取消线程(修改标志位)
public void cancel() {
on = false;
}
}
}
说明:
-
volatile标志位:通过
on变量控制线程循环,实现简单,但线程处于阻塞状态(如sleep、wait)时,无法立即响应。 -
interrupt()中断:向线程发送中断信号,线程通过
isInterrupted()检测信号,可中断阻塞状态(抛出InterruptedException),适配范围更广。 -
两种方式均为「协商式终止」,让线程自行退出,保证资源安全释放。
六、守护线程(Daemon Thread)要点
守护线程是为用户线程服务的“后台线程”,规则与特点如下:
比喻:用户线程 = 前线作战士兵(业务),守护线程 = 后勤兵(后台支持)。
-
关键规则:当所有用户线程执行完毕,JVM会直接退出,不等待守护线程执行完毕。
-
注意事项:
setDaemon(true)必须在start()之前调用,否则抛出异常;守护线程的finally块不保证执行,不适合处理资源释放(如关闭文件、释放连接)。 -
典型场景示例:JVM的GC线程、后台日志刷盘线程、心跳检测线程。
七、同步机制:volatile与synchronized
多线程操作共享资源时,会出现线程安全问题(如数据错乱),Java提供了两种同步方式,二者对比与使用场景如下:
7.1 volatile(轻量级同步关键字)
作用:保证变量的可见性(强制线程直接读写主内存,不使用本地缓存);禁止指令重排序;不保证原子性。
适用场景:状态标记量(如线程终止的标志位)、双重检查锁等。
7.2 synchronized(重量级同步关键字)
作用:保证原子性(同一时间仅一个线程执行临界区代码)、可见性、有序性,是实现线程安全最基础、最常用的方式。
使用方式:可修饰普通方法(锁当前对象)、静态方法(锁类对象)、代码块(锁指定对象)。
底层原理:基于对象的Monitor(监视器锁)实现,通过monitorenter(加锁)和monitorexit(释放锁)指令保证互斥性。
7.3 对比
|
特性 |
volatile |
synchronized |
|---|---|---|
|
原子性 |
不保证 |
保证 |
|
可见性 |
保证 |
保证 |
|
有序性 |
禁止指令重排 |
间接保证 |
|
阻塞性 |
非阻塞 |
阻塞 |
八、线程通信:wait()/notify()/notifyAll()(白雪公主与王子示例)
多线程协作时,需通过通信实现“等待-唤醒”逻辑,wait()、notify()、notifyAll()是Java原生的线程通信方式,以下用“白雪公主与王子”的示例,演示完整使用场景:
public class Shumeiren {
// 全局标志位,控制白雪公主线程是否继续等待(解药是否送达)
static boolean flag = true;
public static void main(String[] args) {
// 启动白雪公主线程(等待解药)
new Thread(new Baixuegongzhu(), "白雪公主").start();
// 启动王子线程(发送解药)
new Thread(new Wangzi(), "王子").start();
}
// 白雪公主线程(等待方)
static class Baixuegongzhu implements Runnable {
@Override
public void run() {
// 锁对象必须与王子线程一致(此处用Shumeiren.class全局锁)
synchronized (Shumeiren.class) {
// 用while循环判断条件,防止虚假唤醒
while (Shumeiren.flag) {
try {
System.out.println("白雪公主:进入睡眠状态,等待解药...");
// 调用wait(),主动释放锁,进入等待队列
Shumeiren.class.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("白雪公主:被唤醒,拿到解药啦!");
}
System.out.println("白雪公主:有了解药,执行完毕");
}
}
// 王子线程(通知方)
static class Wangzi implements Runnable {
@Override
public void run() {
// 同一把锁,保证线程通信安全
synchronized (Shumeiren.class) {
System.out.println("王子:给白雪公主发送解药!");
// 唤醒等待队列中的一个线程(此处唤醒白雪公主)
Shumeiren.class.notify();
// 修改标志位,让白雪公主线程退出循环
Shumeiren.flag = false;
try {
// 模拟王子后续操作,持有锁5秒(验证wait()释放锁)
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("王子:任务执行完毕");
}
}
}
}
使用规则:
-
必须在
synchronized同步块/方法中调用,否则抛出IllegalMonitorStateException。 -
锁对象必须一致:
wait()和notify()/notifyAll()必须使用同一个锁对象。 -
必须用
while循环判断条件,防止“虚假唤醒”(线程意外被唤醒后,再次校验条件)。 -
wait()调用后会主动释放锁,而sleep()不会释放锁。 -
优先使用
notifyAll():唤醒所有等待线程,避免信号丢失,比notify()更安全。
九、总结
-
进程管资源,线程管调度,协程(虚拟线程)追求轻量高效,适合高并发I/O场景。
-
多线程的核心价值是提升CPU利用率、处理阻塞,单核CPU仅I/O密集型场景有优势。
-
线程终止需用volatile标志位或interrupt(),禁止使用stop()等废弃方法。
-
volatile保证可见性与有序性,synchronized保证原子性、可见性、有序性,底层依赖Monitor机制。
-
wait()/notify()/notifyAll()是线程通信的方式,必须配合synchronized与while循环使用。
所有代码均已测试可运行,可直接复制到IDE中调试学习。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)