• java 线程池


    线程池

    什么是线程池技术? 其实,线程池就是一个可以复用线程的技术。要理解什么是线程复用技术,我们先得看一下不使用线程池会有什么问题,理解了这些问题之后,我们在解释线程复用同学们就好理解了。

    假设:用户每次发起一个请求给后台,后台就创建一个新的线程来处理,下次新的任务过来肯定也会创建新的线程,如果用户量非常大,创建的线程也讲越来越多。然而,创建线程是开销很大的,并且请求过多时,会严重影响系统性能。
    而使用线程池,就可以解决上面的问题。线程池内部会有一个容器,存储几个核心线程,假设有3个核心线程,这3个核心线程可以处理3个任务。
    但是任务总有被执行完的时候,假设第1个线程的任务执行完了,那么第1个线程就空闲下来了,有新的任务时,空闲下来的第1个线程可以去执行其他任务。依此内推,这3个线程可以不断的复用,也可以执行很多个任务。
    所以,线程池就是一个线程复用技术,它可以提高线程的利用率。



    创建线程池 ExecutorService ThreadPoolExecutor

    在JDK5版本中提供了代表线程池的接口ExecutorService,而这个接口下有一个实现类叫ThreadPoolExecutor类,使用ThreadPoolExecutor类就可以用来创建线程池对象。下面是它的构造器,参数比较多

    请添加图片描述

    用这7个参数的构造器来创建线程池的对象。代码如下

    ExecutorService pool = new ThreadPoolExecutor(
        3,	//核心线程数有3个
        5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
        8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
        TimeUnit.SECONDS,//时间单位(秒)
        new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在任务队列中等待
        Executors.defaultThreadFactory(), //用于创建线程的工厂对象
        new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    关于线程池,我们需要注意下面的两个问题
    临时线程什么时候创建?

    注意!新任务提交时,发现核心线程都在忙、并且任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
    注意是任务队列满了之后才会创建临时线程 而不是临时线程满了才加入任务队列
    
    • 1
    • 2

    什么时候开始拒绝新的任务?

    核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。
    
    • 1


    线程池执行Runnable任务

    创建好线程池之后,接下来我们就可以使用线程池执行任务了。线程池执行的任务可以有两种,一种是Runnable任务;一种是callable任务。下面的execute方法可以用来执行Runnable任务。

    请添加图片描述

    请添加图片描述

    先准备一个线程任务类

    public class MyRunnable implements Runnable{
        @Override
        public void run() {
            // 任务是干啥的?
            System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");
            //为了模拟线程一直在执行,这里睡久一点
            try {
                Thread.sleep(Integer.MAX_VALUE);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    下面是执行Runnable任务的代码,注意阅读注释,对照着前面的7个参数理解。

    ExecutorService pool = new ThreadPoolExecutor(
        3,	//核心线程数有3个
        5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
        8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
        TimeUnit.SECONDS,//时间单位(秒)这是一个枚举类型 可以选择想要的单位
        new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待 基于数组实现 可以控制等待队列大小
        //这里阻塞队列还可以是new LinkedBlockingQueue<>() 基于链表实现不限制大小 即不限制等待任务数量
        Executors.defaultThreadFactory(), //用于创建线程的工厂对象
        new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
    );
    
    Runnable target = new MyRunnable();
    pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
    pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
    pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
    //下面4个任务在任务队列里排队
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    
    //下面2个任务,到了临时线程的创建时机了
    pool.execute(target);
    pool.execute(target);
    
    // 到了新任务的拒绝时机了!
    pool.execute(target);
    
    
    //pool.shutdown();//等待任务执行完关闭线程池
    //List runnables = pool.shutdownNow();/立即关闭线程池 停止正在执行的任务 返回队列中未执行的任务
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    执行结果:

    pool-1-thread-5 ==> 输出666~~
    main ==> 输出666~~
    pool-1-thread-1 ==> 输出666~~
    pool-1-thread-3 ==> 输出666~~
    pool-1-thread-4 ==> 输出666~~
    pool-1-thread-2 ==> 输出666~~
    //其中123是核心线程执行的 45是临时线程执行的
    //注意程序还是一直运行的 线程池不会自动关闭 设计出来就是一直服务的
    //main输出是因为使用了CallerRunsPolicy()拒绝策略 新来的要拒绝的任务由主线程main执行了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9


    线程池执行Callable任务

    Callable任务相对于Runnable任务来说,就是多了一个返回值。
    执行Callable任务需要用到上面ExecutorService的submit方法

    先准备一个Callable线程任务

    public class MyCallable implements Callable<String> {
        private int n;
        public MyCallable(int n) {
            this.n = n;
        }
    
        // 2、重写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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再准备一个测试类,在测试类中创建线程池,并执行callable任务。

    public class ThreadPoolTest2 {
        public static void main(String[] args) throws Exception {
            // 1、通过ThreadPoolExecutor创建一个线程池对象。
            ExecutorService pool = new ThreadPoolExecutor(
                3,
                5,
                8,
                TimeUnit.SECONDS, 
                new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
    
            // 2、使用线程处理Callable任务。
            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));
    
            // 3、执行完Callable任务后,需要获取返回结果。
            System.out.println(f1.get());
            System.out.println(f2.get());
            System.out.println(f3.get());
            System.out.println(f4.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    某次执行后,结果如下图所示

    pool-1-thread-1求出了1-100的和是:5050
    pool-1-thread-2求出了1-200的和是:20100
    pool-1-thread-3求出了1-300的和是:45150
    pool-1-thread-3求出了1-400的和是:80200
    
    • 1
    • 2
    • 3
    • 4


    核心线程数量到底应该配置多少呢?

    根据经验法则,大致参考以下原则:
    如果是计算密集型的任务:核心线程数量 = CPU的核数 + 1
    如果是IO密集型的任务:核心线程数量 = CPU核数 * 2
    
    • 1
    • 2
    • 3

    CPU核数查看,这个cpu是16核。

    请添加图片描述



    线程池工具类(Executors)

    有同学可能会觉得前面创建线程池的代码参数太多、记不住,有没有快捷的创建线程池的方法呢?有的。Java为开发者提供了一个创建线程池的工具类,叫做Executors,它提供了方法可以创建各种不能特点的线程池。如下图所示

    请添加图片描述

    接下来,我们演示一下创建固定线程数量的线程池。这几个方法用得不多,所以这里不做过多演示,同学们了解一下就行了。

    public class ThreadPoolTest3 {
        public static void main(String[] args) throws Exception {
            // 1、通过Executors创建一个线程池对象。
            ExecutorService pool = Executors.newFixedThreadPool(17);
    
            // 2、使用线程处理Callable任务。
            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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Executors创建线程池这么好用,为什么不推荐同学们使用呢?原因在这里:看下图,这是《阿里巴巴Java开发手册》提供的强制规范要求,在大型并发系统环境中容易出bug。

    请添加图片描述




    补充知识

    补充几个概念性的知识点,知道这些概念什么意思就可以了。

    并发和并行

    先来了解一下什么是进程、线程?

    正常运行的程序(软件)就是一个独立的进程
    线程是属于进程,一个进程中包含多个线程
    进程中的线程其实并发和并行同时存在
    
    • 1
    • 2
    • 3

    可以打开系统的任务管理器看看(快捷键:Ctrl+Shfit+Esc),自己的电脑上目前有哪些进程。

    请添加图片描述

    什么是并发?

    进程中的线程由CPU负责调度执行,但是CPU同时处理线程的数量是有限的,为了保证全部线程都能执行到,CPU采用轮询机制为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
    简单记:并发就是多条线程交替执行

    什么是并行?

    并行指的是,多个线程同时被CPU调度执行。如下图所示,多个CPU核心在执行多条线程

    请添加图片描述

    多线程到底是并发还是并行呢?

    其实多个线程在我们的电脑上执行,并发和并行是同时存在的。



    线程的生命周期

    在Thread类中有一个嵌套的枚举类叫Thread.Status,这里面定义了线程的6中状态。如下图所示

    请添加图片描述

    NEW: 新建状态,线程还没有启动
    RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
    BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
    WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
    TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
    TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这几种状态之间切换关系如下图所示

    请添加图片描述

  • 相关阅读:
    8. 过滤器的作用, 如何实现一个过滤器?
    基于springboot实现财务管理系统项目【项目源码+论文说明】
    [SWPUCTF 2021 新生赛]easy_sql - 联合注入||报错注入||sqlmap
    架构解析:Dubbo3 应用级服务发现如何应对双11百万集群实例
    C数据结构-翻转指针法、头插法实现单链表反转
    PFSK164 3BSE021180R1 有源滤波器和无源滤波器的主要区别
    java File类判断及获取功能
    QTableView对自定义的Model排序
    数据库索引
    浅谈链游的未来:可定制性、身份和社交层
  • 原文地址:https://blog.csdn.net/weixin_43739821/article/details/132782866