线程池使用不合理搞崩系统你见过吗?看我这篇就够了!

开发 前端
线程池允许创建的最大线程数,当任务到达时,如果当前线程数小于最大线程数,即使核心线程都是空闲的,也会创建新的线程来处理任务。

核心线程数过多:

如果核心线程数量设置得过高,可能会导致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);

结论:

为了避免这些问题,合理配置线程池参数,监控线程池的状态,以及对任务进行适当的异常处理和资源管理是非常重要的。

责任编辑:武晓燕 来源: 今日头条
相关推荐

2023-01-07 17:41:36

线程池并发

2015-07-29 09:22:25

IOS多线程

2024-07-26 10:42:30

2013-01-14 09:29:04

2011-05-10 10:38:54

布线光纤

2020-09-10 09:31:34

Nginx HTTP代理服务器

2020-09-09 12:55:28

Nginx高并发性能

2024-09-27 11:51:33

Redis多线程单线程

2011-04-06 12:29:42

2019-07-10 15:15:23

JVM虚拟机Java

2023-11-22 07:54:33

Xargs命令Linux

2019-08-16 09:41:56

UDP协议TCP

2021-09-30 07:59:06

zookeeper一致性算法CAP

2022-03-13 09:31:43

MQ消息队列ActiveMQ

2017-04-10 15:47:50

Android Stujni开发入门

2010-04-28 09:50:14

Oracle数据库

2011-08-17 12:25:11

2011-04-06 16:40:27

C++构造函数

2023-10-31 09:29:03

Java配置

2021-10-13 16:54:22

IPv6网络5G
点赞
收藏

51CTO技术栈公众号