在操作系统层面,进程和线程是两种完全不同的执行单元。进程是操作系统进行资源分配的基本单位,每个进程都拥有独立的地址空间、文件描述符、环境变量等系统资源。而线程则是CPU调度的基本单位,属于进程内部的执行流,共享进程的所有资源。
举个生活中的例子:进程就像一家独立的公司,拥有自己的办公场地(内存空间)、银行账户(系统资源)和营业执照(PID)。线程则是这家公司的员工,共享公司的所有资源,但各自处理不同的工作任务。
关键区别:进程间通信必须通过IPC机制(如管道、消息队列),而线程间可以直接读写同一进程的内存数据。
通过Java程序创建100个进程和100个线程的内存占用对比:
java复制// 进程创建示例
ProcessBuilder pb = new ProcessBuilder("java", "DummyProgram");
List<Process> processes = IntStream.range(0, 100)
.mapToObj(i -> pb.start())
.collect(Collectors.toList());
// 线程创建示例
List<Thread> threads = IntStream.range(0, 100)
.mapToObj(i -> new Thread(() -> {}))
.peek(Thread::start)
.collect(Collectors.toList());
实测数据(JDK17+Linux环境):
| 类型 | 虚拟内存 | 物理内存 | 创建耗时 |
|---|---|---|---|
| 进程 | ~2.8GB | ~1.2GB | 4200ms |
| 线程 | ~300MB | ~80MB | 15ms |
线程切换只需保存寄存器状态(约1-2μs),而进程切换需要切换页表、刷新TLB等操作(约5-10μs)。在Java中尤其明显:
java复制// 方式1:Runtime.exec()
Process process = Runtime.getRuntime().exec("notepad.exe");
// 方式2:ProcessBuilder
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process p = builder.start();
java复制// 继承Thread类
class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
// 实现Runnable接口
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable running");
}
}
// Java8 lambda写法
new Thread(() -> System.out.println("Lambda thread")).start();
| 类型 | Java实现示例 | 适用场景 |
|---|---|---|
| 管道 | PipedInputStream/PipedOutputStream | 父子进程间通信 |
| Socket | ServerSocket/Socket | 跨机器进程通信 |
| 共享内存 | MappedByteBuffer | 高性能大数据量交换 |
java复制// 共享变量方式
class Counter {
public volatile int value; // volatile保证可见性
}
// 等待通知机制
synchronized(lock) {
while(!condition) {
lock.wait();
}
// 处理业务
lock.notifyAll();
}
java复制Process process = Runtime.getRuntime().exec("buggyProgram");
int exitCode = process.waitFor();
if(exitCode != 0) {
System.err.println("Process failed with code: " + exitCode);
}
java复制Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
System.err.println("Thread " + t.getName() + " crashed: " + e);
});
new Thread(() -> {
throw new RuntimeException("Oops!");
}).start();
经验法则:默认优先使用线程,当需要更强隔离性时再考虑进程。
java复制// Spring中的异步执行示例
@Async
public void processOrder(Order order) {
// 耗时操作
}
端口冲突:java.net.BindException
netstat -tulnp查找占用进程资源泄漏:
java复制try (InputStream is = process.getInputStream()) {
// 处理输入流
} // 自动关闭资源
死锁检测:
java复制ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
线程泄漏:
java复制ExecutorService pool = Executors.newFixedThreadPool(10);
java复制// 理想线程数 = CPU核心数 * (1 + 等待时间/计算时间)
int poolSize = Runtime.getRuntime().availableProcessors() * 2;
虚拟线程(Project Loom)
java复制Thread.startVirtualThread(() -> {
System.out.println("Virtual thread running");
});
结构化并发(Java19+)
java复制try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join();
}
协程支持(通过Quasar等库)
在实际项目中,我通常会根据业务特点选择混合模式:用进程隔离关键服务,用线程处理并发请求,再结合消息队列实现进程间通信。特别是在微服务架构中,这种分层并发模型既能保证系统稳定性,又能充分发挥多核性能。