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
其实这里插一句,ArrayList查询快增删慢,LinkedList查询慢增删快
ThreadFactory threadFactory, 线程工厂
工厂模式的体现.
此处使用ThreadFactory 作为工厂类由这个类负责创建线程使用工厂类创建线程,主要是为了在创建过程中,对线程的属性做出一些设置.
如果手动创建线程,就得手动设置这些属性.就比较麻烦.
使用工厂方法封装一下.
RejectedExecutionHandler handler 拒绝策略
)
拒绝策略
分为了四种
1.AbortPolicy 直接抛出异常
2.CallerRunsPolicy 新添加的任务,由添加任务的线程负责执行
3.DiscardOldestPolicy 丢弃任务队列中最老的任务
4.DiscardPolicy 丢弃当前新添加的任务
举个例子
有人要求我跟他一起去送外卖,但是我现在很多事要做
1.我听到心态崩了直接宕机了
.AbortPolicy
2.我直接说你自己去吧,最后他去送外卖了。
.CallerRunsPolicy
3.把我手上排队放最久的一件旧事扔掉,然后我来送外卖。
.DiscardoldestPolicy
4.我拒绝了,然后继续忙我的任务去,然后他也不送了。就直接丢了这个送外卖的事。
.DiscardPolicy

浙公网安备 33010602011771号