进程和线程
进程与线程的用法详解(含Java实现)
一、进程的用法
1. 进程的创建与管理(Java示例)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ProcessExample {
public static void main(String[] args) {
try {
// 方法一:使用Runtime.getRuntime().exec()
Process process1 = Runtime.getRuntime().exec("notepad.exe"); // Windows记事本
System.out.println("进程1启动成功,PID: " + process1.pid());
// 方法二:使用ProcessBuilder(推荐)
ProcessBuilder pb = new ProcessBuilder("java", "-version");
pb.directory(new File("/tmp")); // 设置工作目录
pb.redirectErrorStream(true); // 合并标准输出和错误输出
Process process2 = pb.start();
// 读取进程输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process2.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 等待进程结束并获取退出码
int exitCode = process2.waitFor();
System.out.println("进程2退出码: " + exitCode);
// 强制终止进程(类似kill -9)
// process1.destroyForcibly();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
2. 进程间通信(IPC)示例
管道通信(Java实现)
import java.io.IOException;
import java.io.OutputStream;
public class PipeExample {
public static void main(String[] args) {
try {
// 创建两个进程:一个写进程,一个读进程
ProcessBuilder writerBuilder = new ProcessBuilder("echo", "Hello from writer process");
ProcessBuilder readerBuilder = new ProcessBuilder("cat");
// 启动写进程
Process writerProcess = writerBuilder.start();
OutputStream writerOutput = writerProcess.getOutputStream();
// 将写进程的输出连接到读进程的输入
Process readerProcess = readerBuilder.start();
writerOutput.transferTo(readerProcess.getInputStream());
// 读取读进程的输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(readerProcess.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("读取到: " + line);
}
}
// 等待进程结束
writerProcess.waitFor();
readerProcess.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
二、线程的用法
1. 创建线程的三种方式
方式一:继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread类的线程执行中");
}
}
// 使用
MyThread thread1 = new MyThread();
thread1.start(); // 启动线程,不是调用run()
方式二:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("实现Runnable接口的线程执行中");
}
}
// 使用
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
方式三:实现Callable接口(带返回值)
import java.util.concurrent.*;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable线程返回结果";
}
}
// 使用
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
String result = future.get(); // 阻塞等待结果
System.out.println("结果: " + result);
executor.shutdown();
2. 线程同步与协作
synchronized关键字
class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void decrement() {
synchronized (this) {
count--;
}
}
}
wait()/notify()机制
class MessageQueue {
private List<String> messages = new ArrayList<>();
private static final int MAX_CAPACITY = 10;
// 生产者方法
public synchronized void produce(String message) throws InterruptedException {
while (messages.size() >= MAX_CAPACITY) {
wait(); // 队列满时等待
}
messages.add(message);
notifyAll(); // 通知消费者有新消息
}
// 消费者方法
public synchronized String consume() throws InterruptedException {
while (messages.isEmpty()) {
wait(); // 队列空时等待
}
String message = messages.remove(0);
notifyAll(); // 通知生产者有空间可用
return message;
}
}
3. 线程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("任务" + taskId + "由线程" + Thread.currentThread().getName() + "执行");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
三、进程与线程的适用场景
| 场景 | 选择进程 | 选择线程 |
|---|---|---|
| 计算密集型任务 | 多进程(充分利用多核CPU) | 多线程(但受限于CPU核心数) |
| IO密集型任务 | 进程切换开销大,不推荐 | 多线程(等待IO时可切换) |
| 需要隔离资源 | 必须用进程(如容器技术) | 线程共享资源,无法隔离 |
| 任务独立性 | 进程间互不影响 | 一个线程崩溃可能影响整个进程 |
| 通信复杂度 | 高(需IPC) | 低(直接共享内存) |
四、进程与线程的对比总结
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源 | 独立的内存空间、文件句柄等 | 共享进程资源,仅拥有自己的栈和寄存器 |
| 创建开销 | 高(需分配完整资源) | 低(仅需分配少量线程特有资源) |
| 通信方式 | IPC(管道、消息队列、共享内存等) | 直接读写共享变量,需同步机制 |
| 调度 | 操作系统调度,上下文切换开销大 | 轻量级调度,上下文切换快 |
| 健壮性 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
| 编程难度 | 复杂(需处理IPC和同步) | 较简单(但需注意线程安全) |
五、常见面试问题解答
问题1:什么时候该用进程,什么时候该用线程?
回答:
- 需要充分利用多核CPU时用多进程或多线程(Java中更常用多线程)。
- 需要资源隔离(如容器化部署)时用进程。
- IO密集型任务用多线程更合适,因为线程切换开销小。
- 任务间独立性强(如多个不同的应用)用进程,任务间需要频繁共享数据用线程。
问题2:Java中如何优雅地停止一个线程?
回答:
- 正确方式:使用
volatile标志位控制线程终止。class MyRunnable implements Runnable {
private volatile boolean running = true;
@Override
public void run() {
while (running) {
// 线程执行逻辑
}
}
public void stop() {
running = false;
}
} - 错误方式:避免使用
Thread.stop()(已弃用,可能导致资源泄漏)。
问题3:线程池的核心参数有哪些?如何合理配置?
回答:
核心参数包括:
corePoolSize:核心线程数。maximumPoolSize:最大线程数。workQueue:任务队列(如LinkedBlockingQueue)。keepAliveTime:空闲线程存活时间。
配置建议:
- 计算密集型任务:
corePoolSize = CPU核心数 + 1。 - IO密集型任务:
corePoolSize = 2 * CPU核心数。 - 任务队列大小需根据业务场景调整,避免无限队列导致OOM。