# Java 线程
# 1. 线程基础
# 1.1 线程的概念
- 线程:是操作系统中最小的执行单元,一个进程可以包含多个线程,线程之间共享内存。
- 进程 vs 线程:
- 进程有独立的内存空间,线程共享进程的内存。
- 线程切换成本比进程切换低。
# 1.2 Java 中的线程
创建线程:Java 中有三种常见方式创建线程:
- 继承
Thread
类:重写Thread
类的run()
方法。 - 实现
Runnable
接口:将任务逻辑放入run()
方法,通过Thread
启动。 - 实现
Callable
接口:支持返回结果或抛出异常的任务,结合Future
使用。
- 继承
线程生命周期:
- 新建(NEW):线程被创建但尚未启动。
- 运行(RUNNABLE):线程被调度执行。
- 阻塞(BLOCKED):线程被阻塞,等待进入临界区。
- 等待(WAITING):线程进入等待状态,等待被唤醒。
- 定时等待(TIMED_WAITING):等待一定时间后自动恢复。
- 终止(TERMINATED):线程执行完成或因异常终止。
# 2. 多线程编程
# 2.1 多线程的优势与挑战
优势:
- 提升应用的并发处理能力,提高系统吞吐量。
- 合理利用多核CPU资源。
挑战:
- 线程间的资源共享容易引发竞态条件。
- 存在死锁、线程饥饿等常见并发问题。
- 上下文切换带来的性能开销。
# 2.2 线程的同步机制
锁机制:
synchronized
关键字:用于同步方法或同步代码块,确保同一时间只有一个线程访问临界区。- ReentrantLock:可重入锁,提供了更细粒度的控制和条件变量。
等待通知机制:
wait()
和notify()
:用于线程间的协作,常用于生产者-消费者模型。- Condition:与
ReentrantLock
搭配使用的高级等待/通知机制。
# 2.3 并发包 java.util.concurrent
Java 提供了 java.util.concurrent
并发工具包,简化了多线程编程,常用组件包括:
- Executor 框架:线程池管理,通过
ExecutorService
提供任务提交和执行的机制,避免频繁创建和销毁线程。 - CountDownLatch:允许一个或多个线程等待其他线程完成操作。
- CyclicBarrier:使一组线程在彼此都达到某个状态后继续执行。
- Semaphore:用于控制同时访问特定资源的线程数量。
- BlockingQueue:支持线程安全的队列操作,适合生产者-消费者模型。
# 3. 线程池技术
# 3.1 线程池的工作原理
核心参数:
- 核心线程数:始终保持在活动状态的线程数量。
- 最大线程数:线程池允许的最大线程数量。
- 任务队列:存储等待执行的任务,常见的有
LinkedBlockingQueue
、SynchronousQueue
等。 - 线程存活时间:超过核心线程数的空闲线程在终止前的存活时间。
工作流程:
- 提交任务,若线程数未达到核心数,则创建新线程执行。
- 任务提交到任务队列,等待执行。
- 若任务队列满且线程未达到最大线程数,创建新线程执行。
- 当线程数超过核心数,闲置线程会被回收。
# 3.2 线程池的拒绝策略
当线程池任务队列满时,采用以下拒绝策略:
- AbortPolicy:直接抛出异常,拒绝提交任务。
- DiscardPolicy:直接丢弃任务,不抛出异常。
- DiscardOldestPolicy:丢弃最早的任务,执行新任务。
- CallerRunsPolicy:由调用线程执行任务。
# 4. 线程安全问题与解决方案
# 4.1 竞态条件
当多个线程同时访问共享资源时,可能引发竞态条件,导致数据不一致。
# 4.2 死锁问题
- 死锁:多个线程互相等待彼此持有的资源,形成循环等待,导致线程无法继续执行。
- 避免死锁:通过合理的加锁顺序或使用超时锁机制。
# 4.3 内存可见性问题
- volatile 关键字:保证变量的内存可见性,防止线程使用缓存中的值,确保修改对其他线程可见。
Atomic
系列类:提供了一些原子性操作的封装,如AtomicInteger
、AtomicReference
等,避免传统锁的使用。
# 5. 多线程的设计模式与常见场景
# 5.1 生产者-消费者模型
- 通过
BlockingQueue
实现,生产者不断生成数据,消费者不断消费数据。
# 5.2 Future 模式
- 通过
Future
获取异步任务的执行结果,常与Callable
结合使用。
# 5.3 Fork/Join 框架
- 支持将大任务分解为小任务并行执行,适合大规模数据处理。
# 6. 并发编程的最佳实践
# 6.1 尽量使用高层次的并发工具
- 使用
ExecutorService
管理线程池。 - 使用
java.util.concurrent
中的同步容器,如ConcurrentHashMap
。
# 6.2 避免频繁创建/销毁线程
- 使用线程池复用线程,减少性能开销。
# 6.3 避免过度加锁
- 加锁会带来性能损耗,应尽量减少临界区的范围,避免锁竞争。
# 6.4 使用无锁并发
- 使用
Atomic
类或CAS(Compare And Swap)实现无锁并发,避免传统的锁机制。
# 7. 总结
线程与多线程编程是Java后端架构师的重要技术能力之一。通过掌握线程的基础知识、同步机制、线程池、并发问题的解决方法以及性能优化策略,架构师能够设计出高效、安全的并发系统,提升系统的并发处理能力。面对复杂的并发问题,合理利用Java的并发工具和设计模式,是解决问题的关键。