Skip to main content

进程和线程

进程与线程的用法详解(含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。