Java 进程与线程详解:从 Thread 类继承到线程安全实践
·
1. 进程与线程的基本概念
1.1 什么是进程?
进程(Process)是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间、系统资源和执行环境。
进程的特点:
- 独立性:进程之间相互隔离,一个进程崩溃不会影响其他进程
- 资源开销大:创建、销毁和切换进程需要较大的系统开销
- 通信复杂:进程间通信(IPC)需要通过管道、消息队列、共享内存等机制
1.2 什么是线程?
线程(Thread)是进程内的一个执行单元,是 CPU 调度的基本单位。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源。
线程的特点:
- 轻量级:创建、销毁和切换线程的开销远小于进程
- 资源共享:同一进程内的线程共享内存、文件句柄等资源
- 通信简单:线程间可以直接通过共享内存进行通信
1.3 进程 vs 线程对比
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源开销 | 大 | 小 |
| 创建速度 | 慢 | 快 |
| 通信方式 | 复杂(IPC) | 简单(共享内存) |
| 独立性 | 强(相互隔离) | 弱(共享资源) |
| 崩溃影响 | 不影响其他进程 | 可能影响同进程其他线程 |
2. Java 中的线程创建:继承 Thread 类
2.1 继承 Thread 类的基本方式
在 Java 中,创建线程最简单的方式是继承 Thread 类并重写 run() 方法。
/**
* 通过继承 Thread 类创建线程
*/
public class MyThread extends Thread {
private String threadName;
public MyThread(String name) {
this.threadName = name;
}
@Override
public void run() {
System.out.println("线程 " + threadName + " 开始执行");
try {
// 模拟线程执行任务
for (int i = 1; i <= 5; i++) {
System.out.println(threadName + ": 执行第 " + i + " 次");
Thread.sleep(500); // 休眠500毫秒
}
} catch (InterruptedException e) {
System.out.println(threadName + " 被中断");
}
System.out.println("线程 " + threadName + " 执行结束");
}
public static void main(String[] args) {
// 创建线程实例
MyThread thread1 = new MyThread("线程A");
MyThread thread2 = new MyThread("线程B");
// 启动线程(注意:不是调用 run() 方法)
thread1.start();
thread2.start();
// 等待线程执行完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有线程执行完毕");
}
}
2.2 start() 与 run() 的区别
这是一个常见的面试点,需要特别注意:
public class StartVsRunDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("当前线程: " + Thread.currentThread().getName());
});
// 错误方式:直接调用 run() 方法
System.out.println("=== 调用 run() 方法 ===");
thread.run(); // 在主线程中执行
// 正确方式:调用 start() 方法
System.out.println("\n=== 调用 start() 方法 ===");
thread.start(); // 创建新线程执行
// 输出结果:
// === 调用 run() 方法 ===
// 当前线程: main
// === 调用 start() 方法 ===
// 当前线程: Thread-0
}
}
关键区别:
run():只是普通方法调用,不会创建新线程start():会创建新线程,并在新线程中执行run()方法
2.3 线程的生命周期
Java 线程有以下几种状态:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000);
synchronized (ThreadStateDemo.class) {
ThreadStateDemo.class.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("创建后状态: " + thread.getState()); // NEW
thread.start();
System.out.println("启动后状态: " + thread.getState()); // RUNNABLE
Thread.sleep(100);
System.out.println("休眠中状态: " + thread.getState()); // TIMED_WAITING
Thread.sleep(2000);
synchronized (ThreadStateDemo.class) {
ThreadStateDemo.class.notify();
}
thread.join();
System.out.println("结束后状态: " + thread.getState()); // TERMINATED
}
}
3. 线程安全的核心问题
3.1 什么是线程安全问题?
当多个线程同时访问共享资源,且至少有一个线程在修改资源时,如果不采取同步措施,就可能导致数据不一致的问题。
/**
* 线程不安全示例:银行账户取款
*/
public class UnsafeBankAccount {
private int balance = 1000; // 初始余额1000元
// 取款方法(线程不安全)
public void withdraw(int amount) {
if (balance >= amount) {
// 模拟耗时操作
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款 " + amount + " 元,余额: " + balance);
} else {
System.out.println("余额不足");
}
}
public static void main(String[] args) {
UnsafeBankAccount account = new UnsafeBankAccount();
// 创建10个线程同时取款
for (int i = 0; i < 10; i++) {
new Thread(() -> {
account.withdraw(100);
}, "线程" + i).start();
}
}
}
可能输出(线程不安全):
线程0 取款 100 元,余额: 900
线程1 取款 100 元,余额: 900 // 余额重复,数据不一致!
线程2 取款 100 元,余额: 800
...
3.2 线程安全问题的根源
- 原子性问题:一个操作被中途打断
- 可见性问题:一个线程的修改对其他线程不可见
- 有序性问题:指令重排序导致执行顺序与预期不符
4. 实现线程安全的常用方法
4.1 使用 synchronized 关键字
4.1.1 同步方法
public class SafeBankAccount {
private int balance = 1000;
// 同步方法
public synchronized void withdraw(int amount) {
if (balance >= amount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款 " + amount + " 元,余额: " + balance);
}
}
}
4.1.2 同步代码块
public class SafeBankAccount2 {
private int balance = 1000;
private final Object lock = new Object(); // 专用锁对象
public void withdraw(int amount) {
// 同步代码块
synchronized (lock) {
if (balance >= amount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款 " + amount + " 元,余额: " + balance);
}
}
}
}
4.2 使用 Lock 接口
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockBankAccount {
private int balance = 1000;
private final Lock lock = new ReentrantLock();
public void withdraw(int amount) {
lock.lock(); // 获取锁
try {
if (balance >= amount) {
Thread.sleep(10);
balance -= amount;
System.out.println(Thread.currentThread().getName() +
" 取款 " + amount + " 元,余额: " + balance);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
}
4.3 使用原子类(Atomic Classes)
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger count = new AtomicInteger(0);
// 线程安全的自增
public void increment() {
count.incrementAndGet();
}
// 线程安全的获取值
public int getCount() {
return count.get();
}
// CAS(Compare And Swap)操作示例
public boolean compareAndSet(int expect, int update) {
return count.compareAndSet(expect, update);
}
}
4.4 使用 volatile 关键字
public class VolatileExample {
private volatile boolean flag = false;
public void writer() {
flag = true; // 写操作
}
public void reader() {
if (flag) { // 读操作
System.out.println("flag is true");
}
}
}
volatile 的特点:
- 保证可见性:一个线程的修改对其他线程立即可见
- 禁止指令重排序
- 不保证原子性(不能替代 synchronized)
5. 线程安全的集合类
5.1 ConcurrentHashMap
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 线程安全的putIfAbsent
map.putIfAbsent("key1", 1);
// 线程安全的compute
map.compute("key2", (k, v) -> v == null ? 1 : v + 1);
// 遍历(弱一致性)
map.forEach((k, v) -> System.out.println(k + ": " + v));
}
}
5.2 CopyOnWriteArrayList
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
// 写时复制:修改时创建新数组
list.add("item1");
list.add("item2");
// 遍历时使用快照,不会抛出ConcurrentModificationException
for (String item : list) {
System.out.println(item);
list.add("newItem"); // 不会影响当前遍历
}
}
}
6. 线程池与线程安全
6.1 使用线程池管理线程
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
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 {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 优雅关闭
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
6.2 ThreadLocal 线程局部变量
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
// 每个线程有自己独立的副本
for (int i = 0; i < 5; i++) {
new Thread(() -> {
int value = threadLocal.get();
threadLocal.set(value + 1);
System.out.println(Thread.currentThread().getName() +
": " + threadLocal.get());
// 使用完后必须清理,防止内存泄漏
threadLocal.remove();
}).start();
}
}
}
7. 最佳实践与常见陷阱
7.1 线程安全最佳实践
-
尽量使用不可变对象
public final class ImmutablePerson { private final String name; private final int age; public ImmutablePerson(String name, int age) { this.name = name; this.age = age; } // 只有getter,没有setter public String getName() { return name; } public int getAge() { return age; } } -
缩小同步范围
// 不好:同步整个方法 public synchronized void process() { // 前置非同步操作 // ... // 核心同步操作 // ... // 后置非同步操作 // ... } // 好:只同步必要部分 public void process() { // 前置非同步操作 // ... synchronized (this) { // 核心同步操作 // ... } // 后置非同步操作 // ... } -
使用线程安全的集合类
-
避免死锁
// 死锁示例 public class DeadlockDemo { private final Object lock1 = new Object(); private final Object lock2 = new Object(); public void method1() { synchronized (lock1) { synchronized (lock2) { // 操作 } } } public void method2() { synchronized (lock2) { synchronized (lock1) { // 可能死锁! // 操作 } } } }
7.2 常见陷阱
-
误用 String 作为锁
// 错误:字符串常量池可能导致意外锁共享 synchronized ("lock") { // ... } // 正确:使用专用对象 private final Object lock = new Object(); synchronized (lock) { // ... } -
忘记在 finally 中释放锁
-
过度同步影响性能
-
误以为 volatile 能保证原子性
8. 总结
通过本文的学习,你应该掌握:
- 进程与线程的区别:进程是资源分配单位,线程是执行单位
- Thread 类的使用:继承 Thread 类创建线程,理解 start() 与 run() 的区别
- 线程安全的核心:理解原子性、可见性、有序性问题
- 同步机制:synchronized、Lock、volatile、原子类的使用场景
- 线程安全集合:ConcurrentHashMap、CopyOnWriteArrayList 等
- 最佳实践:缩小同步范围、使用不可变对象、避免死锁
在实际开发中,应根据具体场景选择合适的线程安全方案。对于简单的计数器,可以使用原子类;对于复杂的业务逻辑,可能需要结合 synchronized 或 Lock 使用。记住:没有银弹,只有最适合当前场景的解决方案。
希望这篇详细的讲解能帮助你深入理解 Java 中的进程、线程和线程安全。在实际编码中,多思考、多测试,才能写出既正确又高效的多线程程序。
更多推荐


所有评论(0)