进程(Process)与线程(Thread)
进程(Process)
-
定义:
- 进程是系统进行资源分配和调度的独立单元,包含程序计数器、环境变量集、内存空间等系统资源,是程序的一次动态执行过程。
-
特点:
- 进程间相互独立,每个进程拥有各自的内存空间和系统资源。
- 进程间通信需通过特定机制,如管道、消息队列、共享内存等。
-
Java与进程:
- Java程序启动时自动创建进程,但Java不直接操作进程,而是由操作系统管理。
- Java提供
Runtime.getRuntime().exec(String command)等API来与本地进程交互,执行本地命令或程序。
线程(Thread)
-
定义:
- 线程是CPU调度和分派的基本单位,比进程更小。线程共享进程中的内存空间和系统资源,但拥有独立的执行栈和程序计数器。
-
特点:
- 线程间共享进程的内存空间,但每个线程的执行栈和程序计数器是独立的。
- 线程间通信和同步变得尤为重要,需要确保数据一致性和避免竞争条件。
-
Java中的线程:
- Java直接支持线程,通过
java.lang.Thread类和实现Runnable接口来创建线程。 - Java线程有五种状态:新建(NEW)、就绪(RUNNABLE)、运行(RUNNING,实际与RUNNABLE合并)、阻塞(BLOCKED)、终止(TERMINATED)。
- Java直接支持线程,通过
线程状态详解
-
新建(NEW):
- 线程已创建但尚未启动。
-
就绪(RUNNABLE):
- 线程可被执行,但尚未分配到CPU时间片。
-
运行(RUNNING):
- 实际执行中,但Java中将RUNNABLE和RUNNING视为同一状态。
-
阻塞(BLOCKED):
- 线程因某些原因(如等待I/O、锁等)暂停执行。
-
等待(WAITING):
- 无限期等待其他线程操作,如
Object.wait()、Thread.join()。
- 无限期等待其他线程操作,如
-
限时等待(TIMED_WAITING):
- 等待其他线程操作,但有时间限制,如
Thread.sleep(long millis)。
- 等待其他线程操作,但有时间限制,如
-
终止(TERMINATED):
- 线程执行完毕。
线程的启动与停止
-
启动:
- 通过调用线程的
start()方法,使线程进入就绪状态,等待CPU分配时间片执行。
- 通过调用线程的
-
停止:
- Java不推荐直接停止线程,因为可能导致资源泄露或数据不一致。应使用以下方法:
- 标志位:通过共享布尔变量控制线程执行循环。
interrupt()方法:请求线程中断自己,并在适当时候响应中断(如通过抛出InterruptedException)。Future和ExecutorService:通过取消Future任务来停止线程池中的线程。
- Java不推荐直接停止线程,因为可能导致资源泄露或数据不一致。应使用以下方法:
线程退出策略
-
正常退出:
run()方法执行完毕。
-
interrupt()退出:- 线程检查中断状态,并响应中断(如结束循环)。
-
守护线程(Daemon Thread):
- 随JVM退出而自动退出,但通常不推荐用于重要任务。
示例代码
-
使用标志位:
public class MyThread extends Thread { private volatile boolean running = true; public void run() { while (running) { try { // 执行任务 Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); running = false; } } } public void stopThread() { running = false; } } -
使用
interrupt():- 当线程A调用线程B的
interrupt()方法时,线程B的中断状态会被设置为true。如果线程B正在等待、休眠或阻塞,则会抛出InterruptedException,线程B可以捕获这个异常并处理。
- 当线程A调用线程B的
-
线程已经sleep半小时了,如何退出:
- 在另一个线程中调用该线程的
interrupt()方法,并在sleep调用所在的线程中捕获InterruptedException异常,然后处理(如退出循环)。
- 在另一个线程中调用该线程的
如果线程已经调用Thread.sleep(30 * 60 * 1000)(即半小时)并且你想中断它,你可以:
- 在另一个线程中调用该线程的
interrupt()方法。 - 在
sleep调用所在的线程中,捕获InterruptedException异常,并适当处理(如退出循环)。
try {
Thread.sleep(30 * 60 * 1000); // 尝试休眠半小时
} catch (InterruptedException e) {
// 处理中断,比如退出线程
Thread.currentThread().interrupt(); // 可选,如果中断状态对后续操作重要
return; // 或其他退出逻辑
}
public class MyThread extends Thread {
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
Thread.sleep(100);
}
} catch (InterruptedException e) {
// 处理中断
}
}
}
-
使用
Future和ExecutorService: -
原理:如果你使用的是
ExecutorService来管理线程池,则可以通过Future对象来取消任务的执行。Future.cancel(mayInterruptIfRunning)方法可以用来尝试取消任务的执行。 -
实现:
- 提交任务到
ExecutorService,并获取返回的Future对象。 - 使用
Future.cancel(true)来尝试中断正在执行的任务(如果mayInterruptIfRunning为true)。 - 关闭
ExecutorService以释放资源。
ExecutorService executor = Executors.newSingleThreadExecutor(); Future<?> future = executor.submit(() -> { // 执行长时间运行的任务 }); future.cancel(true); // 尝试中断任务 executor.shutdown(); // 关闭线程池 - 提交任务到
-
使用
Future和ExecutorService:- 通过
ExecutorService管理线程池,使用Future.cancel(true)尝试中断正在执行的任务。
- 通过
sleep和wait方法的异同
-
相同点:
- 两者都会让当前线程暂停执行。
-
不同点:
- 调用方式:
sleep是Thread类的方法,可在任何地方调用;wait是Object类的方法,只能在同步方法或同步块中调用。 - 锁释放:
sleep不释放锁;wait释放锁,并在被唤醒时重新获取锁。 - 等待时间:
sleep的暂停时间是固定的;wait可以等待直到被其他线程通知或超时。 - 异常处理:两者都抛出
InterruptedException,但wait后通常需要再次检查条件。
- 调用方式:
如何唤醒
wait:可通过其他线程调用该对象的notify()或notifyAll()方法来唤醒。sleep:无法被其他线程直接唤醒,它会在指定的时间后自动醒来。
notify和notifyAll的区别
notify():唤醒在此对象监视器上等待的单个线程(选择是任意的)。notifyAll():唤醒在此对象监视器上等待的所有线程,这些线程在获取对象的锁之后竞争继续执行。