一、进程、线程、协程的定义与区别
1. 进程(Process)
进程是操作系统资源分配的基本单位 ,是程序的一次动态执行过程。每个进程拥有独立的内存空间(代码、数据、栈等)、文件描述符等系统资源,进程之间相互隔离,通过进程间通信(IPC,如管道、 socket 等)交换数据。
特点:资源占用大,切换成本高(需切换内存映射、寄存器等内核状态),并发能力低(系统能同时运行的进程数量有限)。
2. 线程(Thread)
线程是操作系统调度的基本单位 ,隶属于进程,是进程内的一个执行单元。一个进程可以包含多个线程,所有线程共享进程的内存空间和资源(如堆内存、全局变量),但拥有独立的栈内存和程序计数器。
特点:资源占用小(共享进程资源),切换成本中等(需切换栈、程序计数器等用户态状态,无需切换内存映射),并发能力中等(支持数百至数千个线程)。
3. 协程(Coroutine)
协程是用户态的轻量级线程 ,由程序(而非操作系统内核)调度,本质是"可暂停、可恢复"的函数。多个协程通常运行在同一个线程中,通过主动让出 CPU(yield)实现切换,不依赖操作系统调度。
特点:资源占用极小(切换仅需保存用户态栈和状态),切换成本极低(用户态操作),并发能力极高(单线程可支持数万至数百万个协程),适用于 I/O 密集型任务。
用王者荣耀来理解进程与线程
❤1、线程在进程下进行 (单独的英雄角色、野怪、小兵肯定不能运行)
❤2、进程之间不会相互影响,主线程结束将会导致整个进程结束 (两把游戏之间不会有联系和影响。你的水晶被推掉,你这把游戏就结束了)
❤3、不同的进程数据很难共享 (两把游戏之间很难有联系,有联系的情况比如上把的敌人这把又匹配到了)
❤4、同进程下的不同线程之间数据很容易共享 (你开的那一把游戏,你可以看到每个玩家的状态------生死,也可以看到每个玩家的出装等等)
❤5、进程使用内存地址可以限定使用量 (开的房间模式,决定了你可以设置有多少人进,当房间满了后,其他人就进不去了,除非有人退出房间,其他人才能进)
三者核心区别
| 维度 | 进程 | 线程 | 协程 |
|---|---|---|---|
| 资源分配 | 独立内存空间 | 共享进程内存 | 共享线程内存 |
| 调度者 | 操作系统内核 | 操作系统内核 | 用户程序(语言 runtime) |
| 切换成本 | 极高(内核态) | 中(内核态) | 极低(用户态) |
| 并发能力 | 低(受系统资源限制) | 中(数百至数千) | 极高(数万至数百万) |
二、Java 中创建线程的几种方式
Java 中创建线程的核心是定义线程要执行的任务(run() 或 call() 方法),主要有 3 种方式:
1. 继承 Thread 类,重写 run() 方法
Thread 类本身实现了 Runnable 接口,继承后重写 run() 方法定义任务,通过 start() 启动线程。
java
class MyThread extends Thread {
@Override
public void run() { // 定义线程任务
System.out.println("线程执行");
}
}
// 使用:创建实例并调用 start()
new MyThread().start();
特点:简单直接,但 Java 单继承限制,灵活性低,无返回值。
2. 实现 Runnable 接口,重写 run() 方法
Runnable 是函数式接口(仅 run() 方法),实现后将实例传给 Thread 类启动线程。
java
class MyRunnable implements Runnable {
@Override
public void run() { // 定义线程任务
System.out.println("线程执行");
}
}
// 使用:包装到 Thread 中并启动
new Thread(new MyRunnable()).start();
特点:避免单继承限制,推荐优先使用,无返回值,可共享任务实例(多线程执行同一任务)。
3. 实现 Callable 接口,结合 Future 获取返回值
Callable 是带返回值的任务接口(call() 方法),通过 FutureTask 包装后传给 Thread,可获取任务结果。
java
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception { // 带返回值的任务
return 1 + 1;
}
}
// 使用:通过 FutureTask 获取结果
FutureTask<Integer> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
int result = task.get(); // 阻塞获取结果(可能抛出异常)
特点:支持返回值和异常抛出,适合需要任务结果的场景,实现稍复杂。
三、线程的常用方法及作用
下面通过具体示例展示线程中常用方法的实际运用,涵盖线程启动、阻塞、同步、中断等核心场景。Java 中 Thread 类提供了多个方法用于控制线程行为,核心方法如下:
1. 线程启动与执行相关
start():启动线程,向操作系统申请创建新线程,线程进入就绪状态,获取 CPU 后自动执行run()方法。不可重复调用(多次调用抛异常)。run():定义线程要执行的任务逻辑,直接调用时会在当前线程同步执行(不创建新线程)。
1. start() 与 run():启动线程与执行任务
java
public class StartAndRunDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("任务执行,线程名:" + Thread.currentThread().getName());
}, "MyThread");
// start():启动新线程,异步执行
System.out.println("调用start()前,当前线程:" + Thread.currentThread().getName());
thread.start(); // 新线程名为"MyThread"
// run():直接调用,在当前线程(main)同步执行
System.out.println("\n调用run()前,当前线程:" + Thread.currentThread().getName());
thread.run(); // 复用main线程
}
}
输出:
scss
调用start()前,当前线程:main
调用run()前,当前线程:main
任务执行,线程名:main
任务执行,线程名:MyThread
作用:
start()是真正启动新线程的唯一方式,会触发操作系统创建线程;run()只是普通方法,直接调用无并发效果。
2. 线程阻塞与等待相关
sleep(long millis):让当前线程暂停执行指定毫秒数,进入超时等待状态,期间不释放已持有的锁。超时后自动回到就绪状态。join()/join(long millis):让当前线程等待目标线程执行完毕(或等待指定时间),期间当前线程进入等待/超时等待状态。常用于线程同步(如主线程等待子线程完成)。wait()/wait(long timeout):(需在synchronized代码块中调用)让当前线程释放锁并进入等待/超时等待状态 ,需通过notify()或notifyAll()唤醒。
2. sleep():让线程暂停执行
java
public class SleepDemo {
public static void main(String[] args) {
new Thread(() -> {
System.out.println("线程开始执行");
try {
// 暂停2秒(模拟耗时操作)
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行完毕");
}).start();
}
}
输出:
线程开始执行
(等待2秒后)
线程执行完毕
作用 :
让当前线程进入超时等待状态,释放CPU资源但不释放锁,常用于模拟延迟或控制执行节奏。
3. 线程状态与中断相关
interrupt():中断线程(设置中断标志),若线程处于wait()、sleep()、join()等阻塞状态,会抛出InterruptedException并清除中断标志。isInterrupted():判断线程是否被中断(返回中断标志,不清除标志)。interrupted():静态方法,判断当前线程是否被中断(返回标志后清除中断标志)。isAlive():判断线程是否处于活动状态(已启动且未终止)。
3. join():等待其他线程完成
java
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
System.out.println("线程t1执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("线程t2执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
// 主线程等待t1和t2执行完毕后再继续
t1.join();
t2.join();
System.out.println("所有线程执行完毕,主线程继续");
}
}
输出:
线程t1执行完毕
线程t2执行完毕
所有线程执行完毕,主线程继续
作用 :
实现线程同步,让当前线程阻塞等待目标线程完成,常用于主线程等待子线程处理结果。
4. 线程调度相关
yield():当前线程主动让出 CPU 时间片,回到就绪状态,让优先级相同或更高的线程有机会执行(仅为建议,操作系统可能忽略)。setPriority(int newPriority):设置线程优先级(1-10,默认 5),优先级高的线程更可能被调度,但不保证执行顺序。
4. interrupt() 与中断检测:优雅终止线程
java
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
// 循环执行,通过中断标志判断是否退出
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程运行中...");
try {
Thread.sleep(500); // 若在sleep中被中断,会抛异常
} catch (InterruptedException e) {
// 捕获异常后,需手动再次设置中断标志(因为异常会清除标志)
Thread.currentThread().interrupt();
System.out.println("线程被中断,准备退出");
}
}
System.out.println("线程已退出");
});
thread.start();
Thread.sleep(2000); // 主线程等待2秒
thread.interrupt(); // 中断子线程
}
}
输出:
erlang
线程运行中...
线程运行中...
线程运行中...
线程运行中...
线程被中断,准备退出
线程已退出
作用:
interrupt()用于通知线程"需要中断"(设置中断标志),而非强制终止;- 线程需通过
isInterrupted()检测标志或捕获InterruptedException实现优雅退出。
5. 线程销毁相关
stop()(已废弃):强制终止线程,可能导致资源未释放(如锁未释放),不安全,不推荐使用。- 正常终止:线程执行完
run()方法或因未捕获异常退出,进入终止状态,无法再次启动。
5. wait() 与 notify():线程间通信(生产者-消费者模型)
java
public class WaitNotifyDemo {
private static final Object lock = new Object();
private static int count = 0;
private static final int MAX = 5;
public static void main(String[] args) {
// 生产者线程:生产数据
new Thread(() -> {
while (true) {
synchronized (lock) {
// 若数据满了,等待消费者消费
while (count == MAX) {
try {
lock.wait(); // 释放锁并等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.println("生产后, count = " + count);
lock.notify(); // 通知消费者
}
}
}, "生产者").start();
// 消费者线程:消费数据
new Thread(() -> {
while (true) {
synchronized (lock) {
// 若数据空了,等待生产者生产
while (count == 0) {
try {
lock.wait(); // 释放锁并等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
System.out.println("消费后, count = " + count);
lock.notify(); // 通知生产者
}
}
}, "消费者").start();
}
}
输出(循环执行):
ini
生产后, count = 1
生产后, count = 2
消费后, count = 1
消费后, count = 0
生产后, count = 1
...
作用:
wait():让当前线程释放锁并进入等待状态,需在同步代码块中调用;notify():唤醒一个等待该锁的线程,常用于线程间协作(如生产消费模型)。
6. yield():主动让出CPU
java
public class YieldDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("线程t1:" + i);
Thread.yield(); // 主动让出CPU,给t2执行机会
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("线程t2:" + i);
Thread.yield(); // 主动让出CPU,给t1执行机会
}
});
t1.start();
t2.start();
}
}
输出(可能的结果):
erlang
线程t1:0
线程t2:0
线程t1:1
线程t2:1
线程t1:2
线程t2:2
...
作用 :
当前线程主动放弃CPU时间片,回到就绪状态,让优先级相同或更高的线程有机会执行(仅为"建议",操作系统可能忽略)。
总结
1.线程方法的核心作用是控制线程的生命周期和协作方式:
- 启动与执行:
start()(异步)、run()(同步); - 阻塞与等待:
sleep()(不释放锁)、join()(等待线程完成)、wait()(释放锁,需配合锁使用); - 中断与退出:
interrupt()(设置标志)、isInterrupted()(检测标志); - 协作通信:
wait()/notify()(线程间传递信号); - 调度优化:
yield()(主动让出CPU)。
实际开发中,需根据场景选择合适的方法,尤其注意线程安全和状态转换的正确性。
- 进程是资源单位,线程是调度单位,协程是轻量级用户态线程,三者在资源占用和并发能力上差异显著。
- Java 创建线程的核心是定义任务,常用 3 种方式(继承
Thread、实现Runnable、Callable),各有适用场景。 - 线程方法主要用于启动、阻塞、同步、中断等控制,需理解其对线程状态的影响(如
start()触发就绪,sleep()进入超时等待)。