多线程案例——线程池

本文介绍了线程池的概念,其如何减少线程创建/销毁开销,以及Java标准库中的ThreadPoolExecutor类及其构造方法和拒绝策略。还展示了如何简单实现一个线程池并讨论了线程执行顺序的非确定性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

         线程池的引入:

        由于进程的创建和销毁的开销过大,所以我们引入了线程。但是,在线程的使用过程中,不断的创建和销毁线程,这个开销不断变大,所以我们需要一个方法来减少线程的创建和销毁的开销。

        有两种办法来减少这个开销:

        1.使用纤程/携程 ->更轻量化的线程

        2.使用线程池

        线程池是什么?

        线程池是一种能够存储线程的数据结构,在用户使用完线程后不再销毁线程,而是将线程归还到线程池中,当用户需要使用线程,也由线程池进行分配。

        为什么使用线程池比直接创建/销毁线程更快

        线程池是用户态,也就是代码方面的,更加的可控,当代码执行时,就可以完成对线程的操作,开销更小。

        而创建/销毁线程是内核态的,不可控,不知道什么时候内核会去创建/销毁这个线程,开销相比较大。

        线程池最大的好处就是:减少创建/销毁线程的开销。

        而代价就是会占用更多的资源。

        Java标准库中的线程池

        在Java标准库中,线程池的类为ThreadPoolExecutor,构造方法有四种:

        以构造方法最多的举例:

        corePoolSize:核心线程数,核心线程会一直存在,直到线程池被销毁。

        maximumPoolSize:最大线程数,线程池可容纳的最多的线程数目。

        keepAliveTime:非核心线程数可空闲的时间,当非核心线程空闲时间超过这个时间后,就会被线程池销毁。

        unit:可空闲时间的单位,如:s,ms等

        workQueue:用来存储要执行内容的阻塞队列。

        threadFactory:线程工厂,用来创建线程,封装了创建线程的new操作。

        handler:拒绝策略,如果任务量过多,超过了阻塞队列的容纳值,应该如何处理。

        拒绝策略的四种:

        AbortPolicy():超过负荷,直接抛出异常。(撂挑子不干了)

        CallerRunsPolicy():调⽤者负责处理多出来的任务。(谁添加谁执行)

        DiscardOldestPolicy():丢弃队列中最⽼的任务。(把老任务放弃,执行新的)

        DiscardPolicy():丢弃新来的任务。(不执行新来的任务,假装不知道)

       

        当一个任务被添加到线程池后,会有线程取走这个任务然后进行执行。

        在Java标准库中,也对ThreadPoolExecutor进行了封装,给出了更简单化的线程池创建方法,ExecutorService类。

        ExecutorService类在生成线程池时提供了四种方法:

        newFixedThreadPool:创建固定线程数的线程池
        newCachedThreadPool:创建线程数⽬动态增⻓的线程池
        newSingleThreadExecutor:创建只包含单个线程的线程池
        newScheduledThreadPool:设定延迟时间后执⾏命令,或者定期执⾏命令。是进阶版的Timer。

        简单实现线程池

        实现一个线程池,需要准备:

        一个阻塞队列,用来传递和存储要执行的内容。

        一个构造方法,在方法中创建线程

        一个submit方法,能够添加执行内容

public class MyThreadPoolExecutor {
    //阻塞队列,用来存放要执行的内容
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
    //构造方法,指定创建多少线程
    public MyThreadPoolExecutor(int n){
        for (int i = 0; i < n; i++) {
            Thread t = new Thread(() -> {
               //执行的内容
                while(true){
                    try {
                        //拿到要执行的内容,如果没有要执行的内容,那就就阻塞等待
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }
    //添加要执行的任务
    public void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }

    public static void main(String[] args) throws InterruptedException {
        MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
        for (int i = 0; i < 1000; i++) {
            int k = i;
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("执行第:"+ k +"个工作,执行者:"+ Thread.currentThread().getId());
                }
            });
        }
    }
}

        

        执行这个代码可以发现,执行工作的顺序并不是严格按照放入顺序执行的。

        因为,当一个线程取走这个工作后,他不一定会立刻执行这个工作(在上面的代码里表现为打印内容),那么在这个空档中,有其他线程执行工作,就会体现出这种非严格按照放入顺序执行的局面。

        例如,当12拿到0后立刻执行,然后13拿到了1,但是13此时被调度走了,没有执行,紧接着的14就会拿到2,那么此时14执行,然后13才执行,就形成了0->2->1这种执行方式。

        

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值