【线程池】

什么是线程池?

线程池是一个可以复用线程的技术。简单来说,线程池是一种基于池化技术的思想来管理线程的技术,旨在减少线程的创建和销毁次数,提高系统的响应速度和吞吐量。它预先创建了一定数量的线程,并将这些线程放入到一个池中(即线程池),当需要执行新的任务时,不是直接创建新的线程,而是从线程池中取出一个空闲的线程来执行该任务,执行完毕后线程并不被销毁,而是放回线程池中供后续任务再次使用。

不使用线程池的问题

  1. 开销大:如果用户每发起一个请求,后台都需要创建一个新线程来处理,那么每次创建新线程都会带来较大的开销,包括系统资源的分配、初始化等过程。这些开销在请求量较大时会非常显著,严重影响系统性能。

  2. 资源浪费:当任务执行完毕后,如果直接销毁线程,那么在下一个任务到来时又需要重新创建线程,这会造成资源的频繁分配和回收,不仅开销大,而且浪费了宝贵的系统资源。

  3. 系统不稳定:如果请求过多,系统可能会创建大量的线程来处理这些请求。然而,系统的线程资源是有限的,过多的线程会导致线程之间竞争系统资源,引发线程切换、上下文切换等开销,进而可能导致系统响应变慢甚至崩溃。

线程池的工作原理

  • 初始状态:线程池中有几个初始化的线程(数量等于核心线程数),这些线程都处于空闲状态,等待任务的到来。
  • 任务提交:当有任务提交给线程池时,如果有空闲的线程,则直接由该线程执行任务;如果没有空闲的线程且任务队列未满,则任务被放入任务队列中等待。
  • 线程执行:线程从任务队列中取出任务并执行,执行完毕后线程再次回到空闲状态等待新的任务。
  • 动态调整:随着任务的增加,如果任务队列满了且线程数还没有达到最大线程数,则线程池会创建新的线程来执行任务。反之,如果一段时间内没有新的任务提交,线程池中的线程数量会逐渐减少到核心线程数(这取决于配置的线程池类型和策略)。
  • 关闭状态:当线程池被关闭时,它不再接受新的任务,并且会等待所有已经提交的任务执行完成后才销毁线程和释放资源。

线程池的创建

方式一 

通过创建ThreadPoolExecutor实例来管理线程池,ThreadPoolExecutor类提供了多个构造函数,允许开发者详细地配置线程池的参数,以满足不同的并发执行需求。

主要参数

  • corePoolSize(核心线程数):线程池中核心线程的数量。即使这些线程处于空闲状态,它们也不会被销毁,除非设置了allowCoreThreadTimeOuttrue
  • maximumPoolSize(最大线程数):线程池中允许的最大线程数。当工作队列已满时,如果线程数小于最大线程数,则会创建新线程来处理任务。
  • keepAliveTime(线程空闲时间):当线程数大于核心线程数时,这是多余空闲线程在终止前等待新任务的最长时间。
  • unit(时间单位):keepAliveTime参数的时间单位,如TimeUnit.SECONDS
  • workQueue(工作队列):用于保存等待执行的任务的阻塞队列。
  • threadFactory(线程工厂):用于创建新线程的工厂。如果不指定,则使用Executors.defaultThreadFactory()来创建线程。这个固定的代码记住就行了。
  • handler(拒绝处理策略):当任务队列和线程池都满了时,用于处理新任务的拒绝策略。
重点参数详解 

任务队列的实现

ThreadPoolExecutor的构造函数允许你指定一个BlockingQueue<Runnable>类型的任务队列。BlockingQueue是一个支持两个附加操作的队列,这两个操作是:在元素可用之前阻塞的检索操作,以及在没有额外空间时阻塞的插入操作。BlockingQueue接口有多种实现,每种实现都有其特定的用途和性能特性。

常见的BlockingQueue实现包括:

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序。

  2. LinkedBlockingQueue:一个由链表结构组成的可选有界阻塞队列。如果创建时没有指定容量,则默认为Integer.MAX_VALUE,即无界队列。

  3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,反之亦然。

  4. PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

如何选择任务队列

  • 如果你需要限制线程池中任务的数量,可以使用有界的ArrayBlockingQueueLinkedBlockingQueue
  • 如果你想要线程池在提交新任务时立即创建新线程(只要不超过maximumPoolSize),可以使用SynchronousQueue。但是,请注意,这可能会导致线程频繁地创建和销毁,从而增加开销。
  • 如果你需要按照任务的优先级来执行,可以使用PriorityBlockingQueue
线程池的注意事项 

常用方法 

 

线程池处理Runnable任务
package demo12;
//任务类
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        //描述你的任务
        System.out.println(Thread.currentThread().getName()+"-->666");
        try {//为了方便观察,我们写一个延迟。
            Thread.sleep(Integer.MAX_VALUE);//Alt键加回车键捕捉一下
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
import java.util.concurrent.*;

public class ThreadPool {
    public static void main(String[] args) {
        //担心忘了,你可以按着ctrl键,然后点击就能看到它的那个提示
//            public ThreadPoolExecutor(int corePoolSize,
//        int maximumPoolSize,
//        long keepAliveTime,
//        TimeUnit unit,
//        BlockingQueue<Runnable> workQueue,
//        ThreadFactory threadFactory,
//        RejectedExecutionHandler handler) {
        ExecutorService pool=//多态写法
                //下面就创建好了一个线程池对象
                new ThreadPoolExecutor(3,5,8,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        Runnable target=new MyRunnable();
        //怎么把这个任务对象交给现场时来处理呢
        pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的。
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);//三个核心线程已经有了,此时复用前面的的核心线程。
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        //到了临时线程的创建时机了
        pool.execute(target);
        pool.execute(target);
        //三个任务在核心线程执行,四个任务在队列里面排着。两个任务在临时线程执行。
        //到了新任务拒绝的时候了
        pool.execute(target);
    }
}

 从图片​​​​​​​我们可以​​​​​​​看​​​​​​​出来​​​​​​​三个​​​​​​​主线​​​​​​​程​​​​​​​和​​​​​​​两​​​​​​​个​​​​​​临时线程都执行了。

如果我们再加一个任务会怎么样呢?

可以看下面这个图片的执行结果:5个都在执行,多出来了一个报错。

线程池处理Callable任务

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n){
        this.n=n;
    }
//重写call方法
    @Override
    public String call() throws Exception {
        //描述线程的任务,返回线程执行后的结果
        //任务:求1到n的和返回
        int sum=0;
        for (int i = 1; i <=n; i++) {
            sum+=i;
        }
        return Thread.currentThread().getName()+"线程求出了1到"+n+"的和是: "+sum;
    }
}

package demo12;

import java.util.concurrent.*;

public class Main2 {
    public static void main(String[] args) throws Exception {
        ExecutorService pool=//多态写法
                //下面就创建好了一个线程池对象
                new ThreadPoolExecutor(3,5,8,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(4),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.AbortPolicy());
        Future<String> f1=//多态写法
                pool.submit(new MyCallable(100));
        Future<String> f2=
                pool.submit(new MyCallable(200));
        Future<String> f3=
                pool.submit(new MyCallable(300));
        Future<String> f4=
                pool.submit(new MyCallable(400));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值