0
点赞
收藏
分享

微信扫一扫

线程池(一)

在觉 2022-02-17 阅读 47

1、初始化线程池的4种方式

​ 1 继承 Thread类

​ 2 实现 Runnable 接口,并重写run方法

​ 3 实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)

​ 4 线程池

区别:1、2不能得到返回值,3可以获取返回值

​ 1、2、3都不能控制资源

​ 4可以控制资源,性能稳定

@Slf4j
public class ThreadTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        log.info("mian start............");
        /**
         * 1)、继承 Thread
         * 2)、实现 Runnable 接口
         * 3)、实现 Callable 接口 + FutureTask (可以拿到返回结果,可以处理异常)
         * 4)、线程池
         */

        // 1.继承 Thread
        log.info("mian start............");
        Thread01 thread01 = new Thread01();
        thread01.start(); // 启动线程
        log.info("mian end............");

        // 2、实现runnable接口
        /*
        Runnable01 runnable01 = new Runnable01();
        new Thread(runnable01).start();
        */

        // 3、实现 Callable 接口 + FutureTask
        FutureTask<Integer> futureTask = new FutureTask<>(new Callable01());
        new Thread(futureTask).start();
        //  阻塞等待整个线程执行完,获取返回结果
        Integer integer = futureTask.get();
        log.info("mian end............" + integer);
        

        // 线程池   给线程池直接提交任务
        /**
         * 以上三种方式启动线程的方式缺点是比较吃cpu资源
         * 【将所有的多线程异步任务都交给线程池执行】
         */
    }

    public static class Thread01 extends Thread{
        @Override
        public void run() {
            log.info("当前线程:" + Thread.currentThread().getId());
            int i = 10/2;
            log.info("运行结果:{}",i);
        }
    }

    public static class Runnable01 implements Runnable{

        @Override
        public void run() {
            log.info("当前线程:" + Thread.currentThread().getId());
            int i = 10/2;
            log.info("运行结果:{}",i);
        }
    }

    public static class Callable01 implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            log.info("当前线程:" + Thread.currentThread().getId());
            int i = 10/2;
            log.info("运行结果:{}",i);
            return i;
        }
    }
}

2、线程池

1、线程池的创建

​ 1)new ThreadPoolExecutor()

		/**
			七大参数
         *     int corePoolSize:核心线程数【一直存在】,线程池,创建好以后就准备就绪的线程数量,就等待来接收异步任务去执行
         *     int maximumPoolSize:最大线程数,作用:控制资源
         *     long keepAliveTime:存活时间,如果当前的线程数量大于核心数量。释放空闲的线程(超出核心线程的线程部分,核心线程是不释放的),只要线程空闲大于指定的keepAliveTime
         *     TimeUnit unit:时间单位
         *     BlockingQueue<Runnable> workQueue:阻塞队列,如果任务有很多,就会将目前多的任务放在队列里面
         *                                        只要有线程空闲,就会去队列里面取出新的任务继续执行
         *     ThreadFactory threadFactory:线程的创建工厂,一般默认使用:Executors.defaultThreadFactory()
         *     RejectedExecutionHandler handler:如果队列满啦,按照我们指定的拒绝策略执行任务
**/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
                5,
                200,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(100000),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
  1. 使用Executors直接创建的线程池
1Executors.newCachedThreadPool(); core是0,所有都可回收
2Executors.newFixedThreadPool(10); 固定大小,core=max,都不可回收
3Executors.newScheduledThreadPool(10); 定时任务的线程池
4Executors.newSingleThreadExecutor(); 单线程的线程池,后台从队列里面获取任务,一个一个执行

2、线程池的工作流程

​ 流程图:
在这里插入图片描述

流程步骤:

​ 1、线程池创建,准备好核心线程,准备接收任务

​ 2、任务存放顺序,先是判断核心数,如果小于核心数就存入核心线程池中

​ 3、如果大于核心数,就会将进来的任务放入阻塞队列中,空闲的核心线程就会去阻塞队列中获取任务执行

​ 4、当阻塞队列已满,就直接开新的线程执行,最大只能开到max指定的数量

​ 5、max都执行完成,有很多空闲的线程,在指定的存活时间keepAliveTime以后,就会释放(max-core)这一部分的线程,核心线程不会释放

​ new LinkedBlockingDeque<>(100000): 默认是Integer的最大值,内存不足,需要在创建的时候指定大小

3、经典面试题

一个线程池 core 7; max 20 ,queue:50,100 并发进来怎么分配的;
* 先有 7 个能直接得到执行,接下来 50 个进入队列排队,在多开 13 个继续执行。现在 70 个
* 被安排上了。剩下 30 个默认拒绝策略。

4、使用线程池的好处

1) 降低资源的消耗

  • 通过重复利用已经创建好的线程降低线程的创建和销毁带来的损耗

2)提高响应速度

  • 因为线程池中的线程数没有超过线程池的最大上限时,有的线程处于等待分配任务的状态,当任务来时无需创建新的线程就能执行

3)提高线程的可管理性

  • 线程池会根据当前系统特点对池内的线程进行优化处理,减少创建和销毁线程带来的系统开销。无限的创建和销毁线程不仅消耗系统资源,还降低系统的稳定性,使用线程池进行统一分配

5、详解各个参数

1)maximumPoolSize

线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收

2)workQueue

阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。

常用的阻塞队列有:

1)ArrayBlockingQueue       // 基于数组的先进先出队列,此队列创建时必须指定大小;

2)LinkedBlockingQueue      // 基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE

3)synchronousQueue       // 这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

3)threadFactory(线程工厂)

线程工厂。用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。

4)handler(拒绝执行策略)

拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • 丢弃任务并抛出RejectedExecutionException异常:ThreadPoolExecutor.AbortPolicy;

  • 丢弃任务,但是不抛出异常:ThreadPoolExecutor.DiscardPolicy;

  • 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程):ThreadPoolExecutor.DiscardOldestPolicy;

  • 由调用线程处理该任务:ThreadPoolExecutor.CallerRunsPolicy

举报

相关推荐

0 条评论