一、进程、线程、协程的定义与区别
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()
进入超时等待)。