面试题答案
一键面试1. 多线程优化BIO连接响应时间原理
在Java BIO(Blocking I/O)中,每个连接都会阻塞当前线程直到I/O操作完成。通过多线程技术,可以为每个连接分配独立的线程处理I/O操作,这样主线程就不会被阻塞,从而提高整体的响应时间。
2. 线程池的设计
- 线程池大小:需要根据系统资源和预估的并发连接数来确定。公式
N = CPU核心数 * 目标CPU利用率 * (1 + 等待时间/计算时间)
可以作为参考。例如,如果应用主要是I/O密集型,等待时间较长,线程池大小应设置得较大;如果是CPU密集型,线程池大小应接近CPU核心数。 - 线程创建策略:使用
ThreadPoolExecutor
类,可以通过构造函数来设置核心线程数、最大线程数、存活时间等参数。例如:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
- 线程生命周期管理:合理设置线程的优先级,对于关键任务的线程可以设置较高优先级。同时,要考虑线程的异常处理,在
Thread.UncaughtExceptionHandler
中捕获并处理线程执行过程中的异常,避免线程因未处理异常而终止。
3. 任务分配策略
- 队列分配:使用
BlockingQueue
作为任务队列,如LinkedBlockingQueue
或ArrayBlockingQueue
。LinkedBlockingQueue
是无界队列,可能会导致内存耗尽问题;ArrayBlockingQueue
是有界队列,当队列满时新任务会被拒绝。 - 拒绝策略:当任务队列已满且线程池达到最大线程数时,需要选择合适的拒绝策略。常见的拒绝策略有
ThreadPoolExecutor.AbortPolicy
(默认,直接抛出异常)、ThreadPoolExecutor.CallerRunsPolicy
(调用者线程执行任务)、ThreadPoolExecutor.DiscardPolicy
(丢弃任务)和ThreadPoolExecutor.DiscardOldestPolicy
(丢弃队列中最老的任务)。例如:
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
4. 线程安全问题及解决方案
- 共享资源访问:如果多个线程访问共享资源,如数据库连接池、缓存等,会出现线程安全问题。解决方案是使用同步机制,如
synchronized
关键字、ReentrantLock
锁。例如:
private static final Object lock = new Object();
public void sharedResourceAccess() {
synchronized (lock) {
// 访问共享资源的代码
}
}
- 对象状态一致性:当多个线程修改同一个对象的状态时,可能导致对象状态不一致。可以使用
volatile
关键字确保变量的可见性,或者使用Atomic
类,如AtomicInteger
、AtomicLong
等,它们提供了原子操作方法。例如:
private volatile boolean flag;
private AtomicInteger counter = new AtomicInteger();
- 死锁问题:多个线程相互等待对方释放锁时会发生死锁。为避免死锁,要确保线程获取锁的顺序一致,并且尽量减少锁的持有时间。同时,可以使用
ThreadMXBean
来检测死锁情况。例如:
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
// 处理死锁情况
}