线程池详解

工厂设计模式

我们在使用线程的时候,如果我们需要频繁的创建和销毁线程,此时创建销毁线程的成本,就非常巨大,所以我们就引入了一个线程池的概念
提前创建好的一些线程,后续需要使用的线程的时候,就直接从线程池里面取就可以了
为什么从池子里面取就比从系统这里创建线程更快更高效呢,
如果是从系统这里创建线程,就需要调用系统API,进一步的由操作系统的内核来完成线程的创建(内核是给所有的进程提供服务的,是不可控的)
如果是从线程池这里获取线程,上述的内核中进行的操作,都已经提前做好了,现在的取线程的过程,就是纯粹的用户代码完成(可控的)
在这里插入图片描述

ExecutorService

是一个线程池对象

Executors

在创建线程池对象的过程中,被称为"工厂类"

newFixedThreadPool

是工厂方法
一般创建对象都是通过new,通过构造方法,但是构造方法,存在重大缺陷
构造方法的名字固定就是类名
有的类,需要有多种不同的构造方式
但是构造方法名字又固定,就只能使用方法重载的方式来实现(参数的个数和类型需要有差别)
线程池详解_第1张图片
假如我们要构造一个位置,构造位置我们可以通过两种方法,一种是利用笛卡尔坐标来构造,另一种就是通过极坐标的方式来表示,但是两种方法参数的个数和类型都是一样的,就无法构成有效重载
使用工厂模式来解决上述问题,不使用构造方法,使用普通的方法来构造对象,这样的方法名字就可以取任意的了
线程池详解_第2张图片
在这里插入图片描述
创建一个固定线程数量的线程池
在这里插入图片描述
创建一个线程数目动态变化的线程池
在这里插入图片描述
包含单个线程
在这里插入图片描述
类似于定时器的效果,添加一些任务,任务都在后续的某个时刻再执行,被执行的时候不是只有一个扫描线程来执行任务,可能是多个线程共同执行所有的任务(假如有100个任务,分给四个线程共同完成)
线程池创建好了之后,使用submit方法,就可以把任务添加到线程池中

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo3 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(4);
//        Executors.newCachedThreadPool();
//        Executors.newSingleThreadExecutor();
//        Executors.newScheduledThreadPool();
        for (int i =0;i<1000;i++){
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
        }
    }
}

除了上述这些线程池之外,标准库还提供了一个接口更丰富的线程池类

ThreadPoolExecutor

这是一个原生的API
上述介绍的线程池都是对这个线程进行的一次封装得到的

经典面试题:

谈谈Java标准库中的线程池的构造方法的参数和含义

int corePoolSize核心线程数,至少得有这些线程

int maximumPoolSize最大线程数,最多不能超过这些线程

ThreadPoolExecutor里面的线程个数并不是固定不变的,会根据当前任务的情况动态发生变化(自适应)

long keepAliveTime,TimeUnit unit
允许线程空闲的最大时间,一个是时间,一个表示单位
超过这个时间限制,该线程就可以被销毁了

BlockingQueue workQueue
线程池里有很多任务,这些任务,可以使用阻塞队列来管理
线程池可以内置阻塞队列,也可以手动指定一个

ThreadFactory threadFactory
工厂模式,通过这个工厂来创建线程

RejectedExecutionHandler handler
线程池考察的重点,拒绝方式/拒绝策略
线程池有一个阻塞队列,当阻塞队列满了之后,继续添加任务,该如何应对

ThreadPoolExecutor.AbortPolicy
直接抛出异常,线程池就停止运行了

ThreadPoolExecutor.CallerRunsPolicy
谁是添加这个新的任务的线程,谁就去执行这个任务

ThreadPoolExecutor.DiscardOldestPolicy
丢弃最早的任务,执行新的任务

ThreadPoolExecutor.DiscardPolicy
把新的任务丢弃掉,执行原来的任务

线程个数的设定

在这里插入图片描述
创建线程的时候,线程个数是咋来的,
关于线程的个数,网上会有各种各样的答案
假设CPU的逻辑核心数为N,线程池中线程的个数可能为N,N+1,N1.2,N1.5,N*2
但是所有这种有确切数字的,都是错误的,为什么?
不同的线程中,线程要做的工作不一样

有的线程的工作,是"CPU密集型",线程的工作全是运算
大部分工作都是要在CPU上完成的
CPU得给他安排核心去完成工作才可以有进展
如果CPU是N个核心,当你线程数量也是N的时候,理想情况,每个核心上有一个线程,如果有很多个线程,线程也就是需要排队等待,不会有新的进展

有的线程的工作,是"IO密集型",读写程序,等待用户输入,网络通信
涉及到大量的等待时间,等待的过程中,没有使用CPU,这样的线程就算更多一些,也不会给CPU造成太大的负担,比如CPU是16核心的,写32个线程,由于是IO密集的,这里的大部分线程都在等,都不消耗CPU,反而CPU的占用情况还很低

实际开发中,一个线程中一部分是CPU密集,一部分是IO密集的
此时,一个线程,几成是在CPU上运行,几成是在等待IO,说不好,这里更好的方法,是通过实验的方式,来找到合适的线程数
性能测试,尝试不同的线程数目,尝试过程中,找到性能和系统资源开销,比较均衡的数值

你可能感兴趣的:(java,linux,服务器)