面试题答案
一键面试1. 线程复用
在Java BIO网络编程中,线程复用可以通过线程池来实现。线程池维护一组线程,这些线程可以被重复使用来处理不同的任务,避免了频繁创建和销毁线程带来的开销。
- 使用
ThreadPoolExecutor
创建线程池:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maximumPoolSize = 10;
// 线程存活时间
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.SECONDS;
// 任务队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue);
// 提交任务
for (int i = 0; i < 20; i++) {
executor.submit(new Task("Task " + i));
}
// 关闭线程池
executor.shutdown();
}
}
class Task implements Runnable {
private String taskName;
public Task(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is processing " + taskName);
// 模拟任务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 分析:
corePoolSize
:线程池维护的最小线程数,即使这些线程处于空闲状态,也不会被销毁。maximumPoolSize
:线程池允许创建的最大线程数。keepAliveTime
:当线程数大于核心线程数时,多余的空闲线程的存活时间,超过这个时间就会被销毁。workQueue
:任务队列,用于存放等待执行的任务。当线程池中的线程都在忙碌且任务队列未满时,新的任务会被放入任务队列中等待。
2. 线程优先级调整
线程优先级可以通过setPriority
方法来设置。优先级范围从1(最低)到10(最高),默认优先级是5。
public class ThreadPriorityExample {
public static void main(String[] args) {
Thread highPriorityThread = new Thread(() -> {
System.out.println("High priority thread is running");
// 模拟任务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
highPriorityThread.setPriority(Thread.MAX_PRIORITY);
Thread lowPriorityThread = new Thread(() -> {
System.out.println("Low priority thread is running");
// 模拟任务处理
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
highPriorityThread.start();
lowPriorityThread.start();
}
}
- 注意事项:
- 虽然设置线程优先级可以影响线程调度,但不能完全依赖它来保证任务的执行顺序。因为不同操作系统对线程优先级的实现和调度算法可能不同。在一些操作系统中,线程优先级的差异可能不明显。
3. 避免线程饥饿和死锁
- 避免线程饥饿:
- 公平调度:在使用线程池时,可以选择公平调度策略。例如,
ThreadPoolExecutor
的构造函数中可以通过设置RejectedExecutionHandler
来处理任务拒绝情况,确保所有任务都有机会被执行。另外,PriorityQueue
等数据结构在用于任务队列时,也可以根据任务的优先级进行排序,但要注意避免低优先级任务长时间得不到执行。 - 动态调整优先级:在任务执行过程中,根据任务的执行时间、等待时间等因素动态调整线程优先级,使长时间等待的低优先级任务有机会提升优先级得到执行。
- 公平调度:在使用线程池时,可以选择公平调度策略。例如,
- 避免死锁:
- 破坏死锁的四个必要条件:
- 互斥条件:有些资源本身就是互斥的,难以破坏。但在设计时可以尽量减少对互斥资源的使用。
- 占有并等待条件:可以要求线程一次性获取所有需要的资源,而不是逐步获取,这样就不会出现占有部分资源又等待其他资源的情况。
- 不可剥夺条件:对于某些资源,可以设计成可剥夺的,例如当一个线程长时间占用资源时,其他高优先级线程可以强制剥夺其资源。
- 循环等待条件:可以对资源进行编号,要求线程按照一定顺序获取资源,避免形成循环等待的环。
- 死锁检测:使用工具如
jstack
命令来检测死锁。jstack
可以打印出Java进程中线程的堆栈信息,通过分析这些信息可以发现死锁的线程。在代码中也可以通过使用ThreadMXBean
来检测死锁,示例如下:
- 破坏死锁的四个必要条件:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class DeadlockDetectionExample {
public static void main(String[] args) {
// 模拟死锁场景(此处省略具体死锁代码)
Thread deadlockDetector = new Thread(() -> {
while (true) {
try {
Thread.sleep(5000);
long[] deadlockedThreads = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("Deadlock detected:");
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
for (long threadId : deadlockedThreads) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
System.out.println(threadInfo.getThreadName());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
deadlockDetector.setDaemon(true);
deadlockDetector.start();
}
}
在高并发的Java BIO网络编程中,合理运用上述线程管理策略可以有效优化系统资源使用,提高系统的稳定性和性能。