线程池的合理使用
- 根据任务类型选择线程池:
- CPU密集型任务:应使用较小规模的线程池,一般线程数设置为
CPU核心数 + 1
,这样能减少线程上下文切换的开销,让CPU尽可能保持忙碌状态。例如,对于一个4核CPU,线程池大小设为5较为合适。
- I/O密集型任务:由于I/O操作通常会有等待时间,线程在等待I/O完成时CPU处于空闲状态,所以可以使用较大规模的线程池,一般为
CPU核心数 * 2
甚至更多,以充分利用CPU在I/O等待期间的空闲时间。
- 设置合适的线程池参数:
- 核心线程数:是线程池中一直存活的线程数,即使它们处于空闲状态也不会被销毁,除非设置了
allowCoreThreadTimeOut
为 true
。对于大多数应用,核心线程数应根据任务类型和预期负载来确定。
- 最大线程数:是线程池能容纳的最大线程数量。当任务队列已满且核心线程都在忙碌时,新任务会创建新线程,直到达到最大线程数。如果继续有新任务,根据
拒绝策略
进行处理。
- 任务队列:用于存放暂时无法处理的任务。常见的队列有
ArrayBlockingQueue
(有界队列)、LinkedBlockingQueue
(无界队列,实际使用中可能需要设置容量以避免OOM)、SynchronousQueue
(直接提交队列,不存储任务)等。选择合适的队列类型能有效控制线程池的行为。例如,ArrayBlockingQueue
可以限制任务堆积,避免资源耗尽,但可能导致任务被拒绝;LinkedBlockingQueue
能容纳大量任务,但可能会导致线程池长时间不处理新任务,因为核心线程可能一直忙于处理队列中的任务。
- 拒绝策略:当任务队列已满且线程数达到最大线程数时,新任务将被拒绝。常见的拒绝策略有
AbortPolicy
(抛出 RejectedExecutionException
异常)、CallerRunsPolicy
(由提交任务的线程来执行该任务)、DiscardPolicy
(直接丢弃任务)、DiscardOldestPolicy
(丢弃队列中最老的任务,然后尝试提交新任务)。应根据应用场景选择合适的拒绝策略。例如,在一个实时性要求不高的日志处理系统中,可以使用 DiscardPolicy
;而在一个关键业务处理系统中,可能需要使用 AbortPolicy
以便及时发现问题。
锁机制的优化
- 减小锁的粒度:
- 避免对整个对象或方法加锁,尽量只对需要同步的关键代码块加锁。例如,在一个包含多个独立操作的类中,如果只有部分操作涉及共享资源,只对这些操作所在的代码块加
synchronized
块,而不是对整个方法加锁。
- 使用
ConcurrentHashMap
替代 Hashtable
。Hashtable
对所有操作都加锁,而 ConcurrentHashMap
采用分段锁机制,将数据分成多个段,不同段可以并行操作,只有访问同一分段的数据时才需要竞争锁,从而提高了并发性能。
- 优化锁的类型:
- 偏向锁:适用于只有一个线程频繁访问同步块的场景。当一个线程首次进入同步块时,锁会进入偏向模式,该线程在后续访问中无需再次获取锁,从而减少了锁获取的开销。Java 6 及以上版本默认开启偏向锁。
- 轻量级锁:当有多个线程交替访问同步块,但不存在同时竞争的情况时,使用轻量级锁。轻量级锁通过
CAS
(Compare - And - Swap)操作来尝试获取锁,避免了重量级锁的内核态切换开销。如果轻量级锁竞争失败,会升级为重量级锁。
- 读写锁:对于读多写少的场景,使用
ReadWriteLock
可以提高性能。读操作可以并发进行,因为读操作不会修改共享资源,只有写操作需要独占锁。例如,在一个缓存系统中,大量线程可能同时读取缓存数据,而只有少数线程会更新缓存,此时使用读写锁能显著提升性能。
避免死锁
- 破坏死锁的四个必要条件:
- 互斥条件:有些资源本身就具有互斥性,难以破坏。但在设计时可以尽量避免创建过多的互斥资源,或者尝试使用非互斥的方式实现相同功能。
- 占有并等待条件:可以要求线程在启动时一次性获取所有需要的资源,而不是先获取部分资源,再等待其他资源。例如,在一个转账操作中,涉及两个账户,如果线程先锁定一个账户,再等待另一个账户的锁,可能导致死锁。可以设计为同时获取两个账户的锁,然后再进行转账操作。
- 不可剥夺条件:可以通过设置超时机制来破坏此条件。如果一个线程获取锁的时间超过一定限度,就自动释放已获取的锁,让其他线程有机会获取锁。在Java中,可以使用
tryLock
方法并设置超时时间来实现。
- 循环等待条件:对资源进行排序,线程按照固定顺序获取资源。例如,有多个线程需要获取多个锁,可以为这些锁编号,线程必须按照从小到大的顺序获取锁,这样就避免了循环等待的情况。
- 使用
ThreadMXBean
检测死锁:
ThreadMXBean
是Java提供的用于管理和监控线程的接口。可以通过调用 findDeadlockedThreads
方法来检测是否存在死锁。该方法返回一个线程ID数组,如果返回 null
,表示没有检测到死锁;否则,数组中的线程ID对应的线程就是死锁线程。可以进一步通过 ThreadInfo
获取死锁线程的详细信息,如堆栈跟踪,以便分析死锁原因。
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("Detected deadlock:");
for (long threadId : deadlockedThreads) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
System.out.println("Deadlocked thread: " + threadInfo.getThreadName());
System.out.println(threadInfo.getStackTrace());
}
} else {
System.out.println("No deadlock detected.");
}
}
}