何为进程?
- 进程就是系统中正在运行的程序,程序一旦运行就是进程。
- 每个进程都拥有自己独立的地址空间
- 一个进程无法访问另外一个进程的变量和数据结构,如需访问需要通过进程之间的通信,比如管道,文件和套接字
何为线程?
- 操作系统中能够进行调度的最小单位,线程包含的进程里
- 线程是进程运行的最小单位
- 线程是进程中一个单一顺序运行的控制流,一个进程可以并发多个线程,每个线程并行执行不同的任务
线程的实现方式
- 继承Thread
通过继承 Thread 类创建线程,主要是重写其 run() 方法,将需要在线程中执行的任务写入该方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...." + Thread.currentThread().getName());
}
}
特点:
- 实现起来简单直接,但是无法继承其他类(Java不支持多继承)
- 不适合资源共享,因为每次创建都需要新的实例
- 实现Runnable接口
实现 Runnable 接口是更常用的方式,尤其在需要多个线程共享资源时。
// 实现方式1
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running..." + Thread.currentThread().getName());
}
}
// 实现方式2
public class ThreadCreateExample {
public static void main(String[] args) {
Thread th = new Thread(()
-> System.out.println("Thread is running..." + Thread.currentThread().getName()));
th.start();
}
}
特点:
- 更加灵活,可以实现多个接口
- 更适合资源共享,因为可以将同一个Runnable实例传递给多个Thread
- 实现Callable接口
Callable 是 Java 5 引入的新方式(在 java.util.concurrent 包中),与 Runnable 的区别在于:
- 他可以返回结果
- 他可以抛出异常
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable task executed:" + Thread.currentThread().getName();
}
}
public class ThreadCreateExample {
public static void main(String[] args) throws Exception {
Callable<String> callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println(result);
}
}
特点
- 能够返回结果和处理异常。
- 更适合需要获取线程任务执行结果的场景。
- 使用线程池
Java 提供了线程池来管理和复用线程,这是一种更高效的方式,特别是在需要频繁创建线程的场景中。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交任务
for (int i = 0; i < 5; i++) {
executorService.submit(() -> {
System.out.println("Thread pool task: " + Thread.currentThread().getName());
});
}
// 关闭线程池
executorService.shutdown();
}
}
特点
- 线程池能够高效管理线程资源,减少线程频繁创建和销毁的开销。
- 适合并发量较大的场景。
Thread类 Run和Start的区别
start()的作用
流程
- start会启动一个新的线程
- 它会调用底层的本地方法,让JVM为线程分配资源,并最终调用run()方法
关键点:
- 调用start()方法,线程会进入就绪状态,等待CPU调度
- 当被调度时,线程会执行run()方法中的代码
特点:
- start()是异步调用,主线程和新线程可以同时运行。
- 新线程由 JVM 调度,run() 方法在新的线程中运行
run()的作用
解释
- run()方法是一个普通的方法,定义线程的任务逻辑。
- 如果直接调用 run() 方法,不会创建新线程,而是由当前线程执行任务,相当于普通方法调用
特点:
- run() 是同步调用,它不会启动新线程,线程任务由当前线程(这里是主线程 main)执行。
- 没有真正的多线程效果。
线程的状态
线程的六种状态
- NEW(新建状态)
- 定义:线程被创建但尚未启动时的状态
- 触发条件:调用 new Thread() 创建线程对象后,线程处于 NEW 状态。
- 特点:线程还未分配任何系统资源。
- RUNNABLE(可运行状态)
- 定义:线程已经启动,并处于可运行状态。
- 触发条件:调用 start() 方法后,线程进入 就绪队列,等待 CPU 调度。
- 特点:此状态并不意味着线程正在运行,只是具备运行条件。
- BLOCKED(阻塞状态)
- 定义:线程试图获取一个被其他线程持有的 同步锁 时的状态。
- 触发条件:线程等待进入同步块或方法。
- 特点:线程处于挂起状态,无法继续执行。
- WAITING(无限等待状态)
- 定义:线程等待另一个线程显式唤醒。
触发条件:
- Object.wait()
- Thread.join()并未设置超时时间
- 特点:线程处于无限期等待,需要外部唤醒。
- TIMED_WAITING(限时等待状态)
- 定义:线程等待另一个线程显式唤醒,但是有时间限制
触发条件:
- Thread.sleep()
- Object.wait(timeout)
- Thread.join(timeout)
- 特点:线程在超时时间后会自动从等待状态返回
- TERMINATED(终止状态)
- 定义:线程完成任务或因异常退出时的状态。
- 触发条件:线程的 run() 或 call() 方法执行结束。
- 特点:线程进入此状态后不能再次启动。
如何优雅的停止线程
- 使用标记位
class StopThreadExample implements Runnable {
private volatile boolean running = true;
@Override
public void run() {
while (running) {
System.out.println("Thread is running...");
try {
Thread.sleep(1000); // 模拟工作
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断状态
System.out.println("Thread interrupted");
}
}
System.out.println("Thread is stopping...");
}
public void stop() {
running = false;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
StopThreadExample example = new StopThreadExample();
Thread thread = new Thread(example);
thread.start();
Thread.sleep(5000); // 运行 5 秒
example.stop(); // 停止线程
}
}
优点:
- 安全且符合 Java 编程规范。
- 线程可以正常释放资源并完成必要的清理操作。
- 使用 Thread.interrupt()
Thread.interrupt() 是一种常用的线程停止方法,它向目标线程发送中断信号,但不会强制终止线程的执行。
如何处理中断:
- 如果线程在调用阻塞方法(如 sleep()、wait()、join())时被中断,会抛出 InterruptedException
- 如果线程未阻塞,Thread.interrupt() 只是将线程的中断状态设置为 true。
class InterruptExample implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread is running...");
Thread.sleep(1000); // 模拟工作
}
} catch (InterruptedException e) {
System.out.println("Thread interrupted");
Thread.currentThread().interrupt(); // 重新设置中断状态
}
System.out.println("Thread is stopping...");
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptExample());
thread.start();
Thread.sleep(5000); // 运行 5 秒
thread.interrupt(); // 发送中断信号
}
}
优点:
- 提供了清理资源的机会。
- 避免了直接强制终止线程导致的不一致性问题。
- 使用 stop() 方法(不推荐)
Thread.stop() 方法会立即终止线程的执行,不管线程的当前状态如何。
为什么不推荐:
- 它会直接释放线程持有的所有锁,可能导致共享资源的不一致性或数据损坏。
- 被废弃(deprecated)并不安全。
- 使用 ExecutorService.shutdown() 或 shutdownNow()
在使用线程池时,推荐通过线程池的管理方法来停止线程。
方法介绍:
- shutdown():平滑关闭线程池,允许已提交的任务执行完成。
- shutdownNow():尝试停止线程池中的所有任务。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.out.println("Thread interrupted");
}
}
});
Thread.sleep(5000); // 运行 5 秒
executor.shutdownNow(); // 尝试立即停止线程
}
}
interrupted 和 isInterrupted
interrupted
作用:检测当前线程 的中断状态,并清除中断标志
- 返回值:返回当前线程的中断状态(true 表示线程已被中断)。
- 清除中断标志:调用此方法后,中断标志会被清除(从 true 变为 false)。
- 静态方法:属于 Thread 类的静态方法,作用于当前线程
特点:
- 只检查当前线程的中断状态,与调用此方法的线程无关
- 清除中断标志,第二次调用时返回 false
isInterrupted
作用:检测某个线程的中断状态,但不会清中断标志
- 返回值:返回调用此方法的线程的中断状态(true 表示线程已被中断)。
- 非静态方法:需要通过线程对象调用,作用于指定线程。
特点
- 检查指定线程的中断状态,不会清除中断标志。
- 通常配合业务逻辑,用于让线程根据中断状态优雅退出。
如何选择?
- 如果需要 检测并清除 当前线程的中断状态,使用 interrupted()
- 如果需要 仅检测 某个线程的中断状态,不改变其状态,使用 isInterrupted()
yield
yield 是 Thread 类中的一个静态方法,用于让当前线程提示调度器暂停执行,以便其他线程有机会运行,但它不保证一定会暂停或让其他线程运行。
yield 的工作原理
- 暂停当前线程:调用 Thread.yield() 后,当前线程会从 运行状态 (RUNNING) 切换到 就绪状态 (READY),等待再次被调度执行。
- 调度器决定下一步:调度器根据线程优先级和调度策略决定是否重新执行当前线程,或者切换到其他线程
- 没有强制效果:调用 yield() 后,当前线程可能立即再次被调度执行
yield 的特点
- 提示调度器释放CPU:
它是一个提示而非强制,具体行为取决于 JVM 和底层操作系统。 - 不改变线程状态:
线程从运行状态切换到就绪状态,不会进入阻塞或等待状态。 - 不释放锁资源:
如果当前线程持有锁资源,调用 yield() 后锁依然被持有。
- 提示调度器释放CPU:
注意事项
- 不可预测性:
调用 yield() 并不一定会使其他线程运行,因为线程调度器可能会选择重新调度当前线程。 - 依赖操作系统和 JVM:
不同平台的线程调度策略不同,因此 yield() 的行为可能不一致。 - 不适合精确控制:
不建议使用 yield() 作为线程调度的核心控制方式,更推荐使用 wait/notify 或线程池。
- 不可预测性:
方法 | 描述 | 是否释放锁 | 状态变化 |
---|---|---|---|
yiled | 提示调度器让出 CPU,但不强制 | 否 | 从运行状态切换到就绪状态 |
sleep | 使线程进入休眠指定时间,不主动释放CPU | 否 | 进去阻塞状态 |
wait | 线程等待,需配合 notify 或 notifyAll | 是 | 进去等待状态 |
join | 等待另一个线程完成执行 | 否 | 当前线程阻塞 |
如何理解线程被挂起
线程挂起的本质:
- 挂起是线程的一种暂停行为,线程主动或被动地停止运行,等待条件满足后恢复。
- 挂起的线程不占用 CPU。
阻塞状态:
- 被动等待资源,不能执行其他任务。
等待状态:
- 主动让出资源,可通过条件触发恢复。
线程调度关系:
- 挂起的线程不会影响其他线程的运行,JVM 调度器会切换到其他就绪线程,最大化 CPU 利用率。
守护线程
守护线程是 Java 中一种特殊的线程,其生命周期依赖于用户线程(非守护线程)。当所有用户线程都结束运行时,JVM 会自动终止所有守护线程并退出。
守护线程的应用场景
垃圾回收:
- JVM 的垃圾回收线程是守护线程的一种典型实现。
后台服务:
- 用于处理日志、监控任务等非关键性工作。
心跳检测:
- 用于定期检查某些资源的状态或提供后台支持。
join
在 Java 中,join() 是 Thread 类的一个方法,用于让当前线程等待另一个线程执行完成之后再继续运行。
线程同步:阻塞当前线程,直到调用 join() 的线程完成。
线程协调:确保多线程执行的顺序。例如,当线程 B 需要依赖线程 A 的结果时,可以使用 A.join(),让线程 B 等待线程 A 执行完成。
join() 的方法签名
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
public final void join(long millis, int nanos) throws InterruptedException
- join():等待直到目标线程执行完成。
- join(long millis):等待目标线程最多 millis 毫秒。
- join(long millis, int nanos):等待目标线程最多 millis 毫秒加 nanos 纳秒。
join() 的工作原理
- 调用 join() 的线程进入 WAITING 状态。
- join() 的目标线程完成后,会唤醒调用 join() 的线程。
- 其实现依赖于 Object 的 wait() 和 notify() 方法。
join() 与线程状态
- 调用 join() 的线程会进入 WAITING 或 TIMED_WAITING 状态(取决于是否设置了超时时间)。
- 目标线程完成后,调用 join() 的线程会重新进入 RUNNABLE 状态。
注意事项
异常处理:
* join() 方法会抛出 InterruptedException,因此必须捕获或声明抛出。 * 如果当前线程在等待期间被中断,会提前退出。
死锁风险:
* 如果两个线程互相调用对方的 join(),可能导致死锁:
性能考虑:
* 如果线程完成时间过长,调用 join() 的线程会长时间阻塞,可能影响程序的响应性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。