线程池原理及使用

1.线程池核心参数含义

线程池源码,咱们先依次介绍下各个参数的含义:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize:核心线程数

maximumPoolSize:最大线程数

keepAliveTime:当线程数大于核心数时,这是多余的空闲线程在终止前等待新任务的最长时间;即空闲线程没任务执行时存活的最长时间

unit:时间单位,针对第三个参数keepAliveTime

workQueue:线程池中存放任务的阻塞队列,有界阻塞队列/无界阻塞队列

threadFactory: 执行程序创建新线程时使用的工厂

handler:由于达到线程边界和队列容量而阻塞执行时要使用的处理程序;jdk中自带了四种处理策略:

1.AbortPolicy:线程池默认策略,当线程池队列满时,再有任务过来,直接丢弃任务,并抛出RejectedExecutionException异常

//JDK 1.8源码

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

2.CallerRunsPolicy:被拒绝任务的处理程序,它直接在主线程中运行被拒绝的任务,除非主线程被关闭,在这种情况下任务将被丢弃。意思就是任务添加到队列失败,此时主线程直接执行此任务,除非主线程是被杀了才不会执行

//JDK1.8源码

public static class CallerRunsPolicy implements RejectedExecutionHandler { 
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }

3.DiscardOldestPolicy:抛弃队列中最老的任务,然后将新任务尝试入队,队列是先进先出,所以队头的任务肯定是最老的

//JDK1.8源码

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

 4.DiscardPolicy:队列满的话丢掉新任务但是不会抛出异常

//JDK1.8源码

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //空方法
    }
}

这个策略大家可以根据自己业务需求设置,也可以自定义写自己的异常策略。

2.底层原理

AtomicInteger atomicInteger = new AtomicInteger(0);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 
2, 60, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(1),
        (r) -> new Thread(r, "t" + atomicInteger.incrementAndGet()),
        new ThreadPoolExecutor.AbortPolicy());

上面是我自己定义的一个线程池:核心线程数1,最大线程数2,空闲线程超时时间60秒,定义的长度为1的有界队列,拒绝策略采用AbortPolicy策略

假如现在同时过来4个任务:task1由核心线程执行,task2会进入队列,task3和task4发现核心线程和队列都满,则会新建空闲线程(1个空闲线程),task3或者task4其中的一个会被空闲线程执行,剩余的一个则执行策略,抛出报错

任务进入线程池的底层逻辑:判断核心线程数是否还有剩余,若有剩余,任务直接由核心线程执行;若达到核心线程数,则会进入队列,队列分有界阻塞队列和无界阻塞队列,若是无界队列,按理说可以入队无限多的任务(无界队列可能会引起内存相关问题,下文讲解);若是有界队列,则判断是否达到有界队列的最大长度,若队列没满,则任务入队;若队列已满,则会执行相应的策略。

核心线程是不会被销毁的,任务执行完毕后,会从队列中拿出队头的任务接着执行

空闲线程在超过设置的超时时间未执行任务则会被销毁

3.线程池在使用过程中的问题

3.1 如果在线程池中使用无界阻塞队列会有什么问题?

若队列里面的任务在访问远程服务异常的情况下会调用失败导致超时,导致队列里面积压的任务越来越多,执行任务的线程在访问远程服务都会超时,时间很漫长,但是任务入队列的速度还是一样的快,导致无界阻塞队列越来越大,会导致内存异常的飙升,可能还会导致OOM;

3.2 若线程池的队列满了之后,会发生什么?

若你new的线程池的阻塞队列是有界的,可以避免内存溢出,在队列满之后,此时需要看你设置的maximumPoolSize值,若这个值为(1000)则可能存在待执行的任务瞬间超过这个值,用自带的策略的话,超过的任务会被抛弃,导致任务丢失;若maximumPoolSize=Integer.MAX_VALUE,多少任务就会创建多少线程,但是每个线程会有自己的栈内存,占用一定的内存空间,这样也可能会导致内存飙升,系统会崩溃掉,即使内存没有溢出,也会导致机器的CPU负载特别高导致机器挂掉;

若用无界队列,则会出现上面3.1的问题

具体的如何设计线程池需要结合自己业务场景、业务负载制定合适的线程池参数;或者自定义策略,若线程池已经无法执行更多的任务,可以把任务持久化写入磁盘,在后台专门起一个线程,等线程池的工作负载降低了,再执行磁盘里的任务等

3.3 若服务宕机了,阻塞队列里面的请求任务该如何处理?

服务器宕机,队列存储在内存中,则队列里面的任务都会丢失,为了解决这一问题,我们可以在提交任务到线程池之前将任务插入数据库,记录此任务的状态(未提交等),在服务器重启后,后台设计一个功能在此数据库中将漏掉的任务再次提交即可;

学无止尽,与君共勉!!!

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
线程池原理已经在上面进行了解释,下面是一个使用线程池实现文件拷贝的C语言示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #define THREAD_POOL_SIZE 5 #define BUFFER_SIZE 1024 typedef struct { char src_file[256]; char dest_file[256]; } CopyTask; typedef struct { pthread_t thread; int is_working; } Worker; CopyTask task_queue[THREAD_POOL_SIZE]; Worker workers[THREAD_POOL_SIZE]; pthread_mutex_t mutex; pthread_cond_t cond; void *worker_thread(void *arg) { while (1) { pthread_mutex_lock(&mutex); // 等待任务到来 while (strlen(task_queue[*(int *)arg].src_file) == 0) { pthread_cond_wait(&cond, &mutex); } // 执行任务 CopyTask copy_task = task_queue[*(int *)arg]; task_queue[*(int *)arg].src_file[0] = '\0'; // 清空任务 pthread_mutex_unlock(&mutex); // 拷贝文件 FILE *src = fopen(copy_task.src_file, "rb"); FILE *dest = fopen(copy_task.dest_file, "wb"); if (src == NULL || dest == NULL) { printf("Failed to open file.\n"); continue; } char buffer[BUFFER_SIZE]; size_t bytesRead; while ((bytesRead = fread(buffer, sizeof(char), BUFFER_SIZE, src)) > 0) { fwrite(buffer, sizeof(char), bytesRead, dest); } fclose(src); fclose(dest); printf("File copied from %s to %s\n", copy_task.src_file, copy_task.dest_file); } } void thread_pool_init() { int i; pthread_mutex_init(&mutex, NULL); pthread_cond_init(&cond, NULL); for (i = 0; i < THREAD_POOL_SIZE; i++) { workers[i].is_working = 0; task_queue[i].src_file[0] = '\0'; pthread_create(&workers[i].thread, NULL, worker_thread, &i); } } void thread_pool_submit(char *src_file, char *dest_file) { pthread_mutex_lock(&mutex); // 查找空闲线程 int i; for (i = 0; i < THREAD_POOL_SIZE; i++) { if (strlen(task_queue[i].src_file) == 0) { strcpy(task_queue[i].src_file, src_file); strcpy(task_queue[i].dest_file, dest_file); pthread_cond_signal(&cond); break; } } pthread_mutex_unlock(&mutex); } int main() { int i; thread_pool_init(); // 提交任务 for (i = 0; i < 10; i++) { char src_file[256], dest_file[256]; sprintf(src_file, "source%d.txt", i); sprintf(dest_file, "destination%d.txt", i); thread_pool_submit(src_file, dest_file); } // 等待任务完成 sleep(1); return 0; } ``` 在上述示例中,我们定义了一个 `CopyTask` 结构体,用于存储拷贝任务的源文件和目标文件。线程池中的任务队列存储了 `CopyTask` 结构体的实例。 在主函数中,我们初始化了线程池,并提交了10个文件拷贝任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

you are so cute

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值