8/21 Java学习博客

线程池

线程诞生的意义,就是因为进程的创建/销毁,太重量了比较慢。(线程比进程少了个资源调度的消耗,线程是共享资源)

和进程比线程是快了,但是如果进一步的提高创建销毁的频率,线程开销也不可被忽视了。

所以现在有两种方法来进一步提高效率
1.协程(也叫轻量级线程)相比于线程,他把系统调度的过程给省略了,由程序员手工调度,是一种比较流行的并发编程手段(Java不常用)

2.线程池 (这个方案主要是兜底,使线程不至于很慢)
在使用第一个线程时候,提前把2 3 4 5。。。线程创建好,后续想使用新的线程时候,不需要重新创建,直接复用(此时创建线程的开销就被降低了)

那为什么从池子里面来取得效率比新创建线程效率更高。

主要是因为 从池子取 这个动作 是纯粹用户态的操作。

创建新线程 是 用户态 加上 内核态 相互配合完成的操作。

举个例子 我们去办理证件 需要身份证复印:

此时有两个选择
1.让柜员去帮我复印
2.自己去找个复印机复印

1这个操作就涉及到内核操作,你也不知道柜员什么时候帮你复印好,他可能也要帮别人干活。 是不可控的

操作系统的内核是为所有的进程服务的。创建线程操作,就需要调用系统api,进入到内核中,按照内核态的方式来完成一系列动作。

2.这个操作纯用户态,我们可以直接去复印直接回来。效率很高
是非常可控的

所以就可以分析出这个从线程池中取是比重新创来的快。

线程池对象不是我们new出来的,而是通过一个专门的方法返回,返回一个线程对象。
就是通过设计模式中的工厂模式来完成的。

工厂模式

说到工厂模式,通常我们创建对象是new出来,但是new关键字会触发类的构造方法。但是构造方法存在一定的局限性。

很多时候构造一个对象,我们希望有多种的构造方式,多种方式,就需要使用多个版本的构造方法分别来实现,但是构造方法要求名字必须是类名,不同的构造方法,就只能通过重载的方式来区分(重载要求参数类型/个数不同)

这时候就会有个问题 如果我们创建的对象刚好参数个数都一样 这时候就构成不了重载就会编译失败。

举个例子
在一个坐标系中 我们可以通过笛卡尔坐标系来构造对象
也可以用极坐标来构造对象

class Point{
    public Point(double x, double y){}
    public Point(double r, double angle){}
}

很明显此时就构成不了重载。

我们就通过工厂模式来解决这个问题。使用普通方法来代替构造方法完成初始化工作。普通方法就可以使用不同的方法名了。

实践中一般是单独搞一个类,给这个类搞静态方法,由静态方法负责构造出对象。

class Point{
    double x;
    double y;

    double radius;
    double angle;
}

class PointFactory{
    public static Point makePointByxy(double x, double y){
        Point p = new Point();
        p.x = x;
        p.y = y;
        return p;
    }

    public static Point makePointByra(double radius, double angle){
        Point p = new Point();
        p.radius = radius;
        p.angle = angle;
        return p;
    }
}

就是类似于这种 简单的静态工厂方法

然后我们在调用时候就可以

然后我们在调用时候就可以

public class test {
    public static void main(String[] args) {
        Point a = PointFactory.makePointByxy(1.0, 1.0);
        Point b = PointFactory.makePointByra(1.0, 1.0);
        System.out.println(a.x);
        System.out.println(b.radius);

    }
}

对于线程池呢,java中则是有专门的方法来构造
先说四种


//1.动态适应
        ExecutorService service = Executors.newCachedThreadPool();

//Executors工厂类  newCachedThreadPool();工厂方法

//cache缓存用过之后不急着释放,先留着下次使用,此时构造出的线程池对象有一个基本的特点,线程数目是动态适应的,随着往线程池里添加任务,这个线程池中的线程就会根据需要自动创建。

 //2.固定 指定创建几个线程
        ExecutorService service2 = Executors.newFixedThreadPool(4);

//3.只有一个线程的线程池
        ExecutorService service3 = Executors.newSingleThreadExecutor();

//4.类似于定时器
        ExecutorService service4 = Executors.newScheduledThreadPool(4);

往线程池里面注册任务则是用submit

ExecutorService service2 = Executors.newFixedThreadPool(4);

service2.submit(new Runnable(){
            @Override
            public void run(){
                System.out.println("hello");
            }

上述这几个工厂方法生成的线程池,本质上都是对一个类进行的封装,

ThreadPoolExecutor
(核心方法,就两个
1.构造
2.注册任务(添加任务))

这个类,功能非常丰富,提供了很多参数,标准库上述的几个工厂方法,其实就是给这个类填写了不同的参数用来构造线程池了,

线程池的7大核心参数

ThreadPoolExecutor

(int corePoolSize, //最大核心线程数

int maximumPoolSize, //最大线程数

long keepAliveTime, //存活时间

TimeUnit unit,//时间单位

BlockingQueue workQueue,//阻塞队列。用来存放线程池的任务,可以根据需要灵活设置,需要优先级就可以设置 PriorityBlockingQueue,不需要且任务数目相对恒定用ArrayBlockingQueue 不需要且任务数目变动较大就用LinkedBlockingQueue
其实这里插一句,ArrayList查询快增删慢,LinkedList查询慢增删快

ThreadFactory threadFactory, 线程工厂

工厂模式的体现.
此处使用ThreadFactory 作为工厂类由这个类负责创建线程使用工厂类创建线程,主要是为了在创建过程中,对线程的属性做出一些设置.

如果手动创建线程,就得手动设置这些属性.就比较麻烦.
使用工厂方法封装一下.

RejectedExecutionHandler handler 拒绝策略
)

拒绝策略

分为了四种

1.AbortPolicy 直接抛出异常

2.CallerRunsPolicy 新添加的任务,由添加任务的线程负责执行

3.DiscardOldestPolicy 丢弃任务队列中最老的任务

4.DiscardPolicy 丢弃当前新添加的任务

举个例子
有人要求我跟他一起去送外卖,但是我现在很多事要做

1.我听到心态崩了直接宕机了
.AbortPolicy

2.我直接说你自己去吧,最后他去送外卖了。
.CallerRunsPolicy

3.把我手上排队放最久的一件旧事扔掉,然后我来送外卖。
.DiscardoldestPolicy

4.我拒绝了,然后继续忙我的任务去,然后他也不送了。就直接丢了这个送外卖的事。
.DiscardPolicy

posted @ 2026-08-30 18:28  曾章吉的浩吧  阅读(2)  评论(0)    收藏  举报