核心线程数过多:
如果核心线程数量设置得过高,可能会导致CPU过度调度,增加上下文切换的开销,甚至耗尽系统可用的线程资源。
最大线程数过大:
当任务队列满载且所有工作线程都在运行时,如果最大线程数设置得过高,可能会导致大量线程等待任务,浪费资源,甚至引发内存溢出。
线程池允许创建的最大线程数,当任务到达时,如果当前线程数小于最大线程数,即使核心线程都是空闲的,也会创建新的线程来处理任务。
当线程池的最大线程数设置得过大时,可能会对系统产生以下几方面的影响:
资源耗尽:
内存消耗增加:每个线程都会占用一定的堆栈空间,
线程数过多可能导致内存消耗过大,
甚至引发OutOfMemoryError。
CPU资源过度分配:
过多的线程会导致CPU过度调度,
增加上下文切换的频率,
降低CPU的利用率和整体系统的吞吐量。
性能下降:
上下文切换开销:
频繁的上下文切换会消耗大量的CPU时间,
降低线程的实际执行效率。
I/O等待:
如果线程执行的任务涉及I/O操作,
过多的线程在等待I/O完成时会占用更多的系统资源,
如文件句柄、网络连接等。
系统稳定性受影响:
死锁风险增加:
大量线程并发执行时,
资源竞争更加激烈,
增加了死锁的风险。
系统响应时间变长:
过多的线程导致系统调度负担加重,
响应用户请求的时间可能会显著增加。
管理复杂度提升:
监控和调试难度加大:
线程数过多使得跟踪和分析线程状态变得更加困难,
增加了问题定位和故障排查的复杂度。
任务队列设置不当:
队列容量过大或无界:如果任务队列的容量设置得过大或者没有限制,当系统负载高时,可能会导致内存消耗过大,甚至发生OutOfMemoryError。
频繁的创建和销毁线程,毕竟线程是较重的资源,频繁的创建和销毁对系统性能是没好处的。
队列容量过小:
如果队列容量太小,可能会导致任务被拒绝执行,从而影响系统的正常运行。
任务执行时间过长:
如果提交到线程池的任务执行时间过长,而线程池的核心线程数又相对较少,可能会导致线程池中的所有线程都被长时间占用,无法处理新的任务请求,造成系统响应延迟或拒绝服务。
资源竞争 导致 常见的并发问题:
当多个线程同时访问共享资源时,如果没有正确的同步机制,可能会引发死锁或数据不一致的问题,导致系统不稳定。
资源争用(Resource Contention):
资源争用是指多个线程同时竞争有限的系统资源,导致性能下降。当多个线程同时请求同一个资源时,可能会出现资源争用问题。
//多个读取线程 和 一个写入线程 同时访问 共享的列表list。
//由于读取和写入都需要获取列表的锁,
//可能会导致读取线程和写入线程之间的资源争用,
//从而降低性能。
class YYExample {
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) {
Runnable reader = () -> {
while (true) {
synchronized (list) {
for (Integer value : list) {
// 读取共享列表
System.out.println(value);
}
}
}
};
Runnable writer = () -> {
while (true) {
synchronized (list) {
// 写入共享列表
list.add(1);
}
}
};
// 创建多个读取线程和写入线程
for (int i = 0; i < 50; i++) {
new Thread(reader).start();
}
new Thread(writer).start();
}
}
死锁
`thread1` 先获取 `resource1`,然后尝试获取 `resource2`,
而 `thread2` 先获取 `resource2`,然后尝试获取 `resource1`。
如果这两个线程同时运行,它们可能会相互等待对方释放资源,导致死锁。
class DemoExample {
private static Object resource1 = new Object();
private static Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1 acquired resource 1");
synchronized (resource2) {
System.out.println("Thread 1 acquired resource 2");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2 acquired resource 2");
synchronized (resource1) {
System.out.println("Thread 2 acquired resource 1");
}
}
});
thread1.start();
thread2.start();
}
}
数据不一致
class Counter {
private int count;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
在没有同步机制的情况下,
多个线程并发执行 `increment()` 方法可能导致竞态条件,
从而导致最终的计数结果不正确。
异常处理不当:
如果线程在执行任务时抛出未捕获的异常,
且没有适当的异常处理机制,
可能会导致线程池中的线程停止工作,
减少可用线程的数量,
影响系统性能。
当线程在执行任务时抛出异常,正确的捕获和处理异常是保证程序稳定性和健壮性的关键。以下是在Java中如何捕获和处理线程中抛出的异常的几种方法:
1. 在Runnable或Callable接口实现中捕获异常
如果你的任务是通过实现Runnable或Callable接口来定义的,你可以在实现的方法中直接捕获异常。例如:
public class Task implements Runnable {
@Override
public void run() {
try {
// 执行可能抛出异常的任务
doSomething();
} catch (Exception e) {
// 异常处理逻辑,如记录日志、发送警报等
System.err.println("Task failed due to: " + e.getMessage());
e.printStackTrace();
}
}
private void doSomething() throws Exception {
// 可能抛出异常的代码
}
}
2. 使用Future获取异常
当使用Callable接口并配合ExecutorService时,可以通过Future.get()方法获取任务的结果或抛出的异常:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(new Task());
try {
future.get(); // 这里会抛出任务中抛出的异常
} catch (ExecutionException e) {
// 获取并处理原始异常
Throwable cause = e.getCause();
System.err.println("Task failed due to: " + cause.getMessage());
} catch (InterruptedException e) {
// 处理中断异常
}
3. 设置UncaughtExceptionHandler
你可以为线程设置一个UncaughtExceptionHandler,当线程抛出未捕获的异常时,这个处理器会被调用来处理异常:
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
// 异常处理逻辑
System.err.println(t.getName() + " throws an exception: " + e.getMessage());
}
});
thread.start();
4. 监听线程池异常
对于ExecutorService,可以监听其内部线程的异常。这通常需要自定义线程工厂并设置UncaughtExceptionHandler:
ThreadFactory factory = new ThreadFactoryBuilder()
.setUncaughtExceptionHandler((t, e) -> {
System.err.println(t.getName() + " throws an exception: " + e.getMessage());
})
.build();
ExecutorService executor = Executors.newFixedThreadPool(10, factory);
结论:
为了避免这些问题,合理配置线程池参数,监控线程池的状态,以及对任务进行适当的异常处理和资源管理是非常重要的。