目录
一、线程池是啥,有啥用
二、线程池怎么用
1.构造方法
2.如何使用Java的线程池
三、简单实现一个线程池
假设我是一个(好看+有才华) 的妹子,那么我就会有很多追求者,这些也叫备胎们,我们若把他们放到一个地方,那就叫“备胎池”。同理,线程就叫“线程池”。
一、线程池是啥,有啥用
频繁的创建销毁线程开销太大,所以我们会先创建一些线程放着,要用的时候直接拿就行了,这些线程都放一块地方,用完之后再放回这个地方,那么我们管这个地方叫“线程池”。
所以:线程池最⼤的好处就是减少每次启动、销毁线程的损耗。
那么为什么线程池的效率更高,开销更小呢?
那么就得谈谈:用户态和内核态
比如说:
1)在银行你要办业务,可能会需要身份证或者户口本复印件,这时候,你如果没带,那么银行可能会有复印机。
2)那么你有2种选择:第一种是去柜台让柜员帮你,第二种是自己去大厅的复印机复印。
3)内核态:如果让柜员帮你去后台复印,可能没这么快,柜员也许要帮别人也复印,也可能去摸鱼,也可能去上个厕所。我们知道操作系统是由内核和软件组成,有很多软件都需要内核管理,可能会没那么及时。
4)用户态:如果你是自己去大厅复印,自给自足,非常的快。
5)而我们的线程池就是用户态,自己创建线程是内核态,所以效率会比线程池低。
二、线程池怎么用
我们有专门的类表示线程池:ThreadPoolExecutor。
1.构造方法
下面是它的构造方法,最主要的是第4个最为全面,其他三个都是从第4个演化出来的。
我们分析一下这个线程池的参数:
- corePoolSize:正式员⼯的数量. (正式员⼯, ⼀旦录⽤, 永不辞退)
- maximumPoolSize: 正式员⼯ + 临时⼯的数⽬. (临时⼯: ⼀段时间不⼲活, 就被辞退)
- keepAliveTime: 临时⼯允许的空闲时间
- unit: keepaliveTime 的时间单位, 是秒, 分钟, 还是其他值
- workQueue: 传递任务的阻塞队列
- threadFactory: 创建线程的⼯⼚, 参与具体的创建线程⼯作,通过不同线程⼯⼚创建出的线程相当于 对⼀些属性进⾏了不同的初始化设置
- RejectedExecutionHandler: 拒绝策略, 如果任务量超出公司的负荷了接下来怎么处理
- AbortPolicy(): 超过负荷, 直接抛出异常
- CallerRunsPolicy(): 调⽤者负责处理多出来的任务
- DiscardOldestPolicy(): 丢弃队列中最⽼的任务
- DiscardPolicy(): 丢弃新来的任务
1)corePoolSize和maximumPoolSize中,如果核心线程数是M个,在使用的过程中发现不够用,会自动扩容多M个,直至最大线程数max个。
2)keepAliveTime和unit是姐妹,一起用的,就是实习生(非核心线程)太久没活干了,就会裁掉它,怎么样才算太久呢?就需要你自己设置一个时间
3)workQueue是我们需要传进去的堵塞队列,线程池就是一个生产者消费者模型,程序员把任务submit放进线程池中。这个队列可以自己设计容量和类型。
4)线程工厂,是为了弥补构造方法的不足,比如假设我在二维中需要表示一个点,我有2种方法,第一种是横纵坐标,第二种是用极坐标,
但是这样的话重载就失效了,因为名字参数什么的啥都一样啊,构成不了重载。
而是需要工厂设计模式解决,如上图。
5)拒绝策略(重点):当线程池的要执行的任务满了,它就会进行堵塞。但是有时候这未必是件好事。比如说你女神直接拒绝你,比起干耗着你好,这样你就能开启新的生活了。
假设线程池是女神,你是任务,但是备胎太多了忙不过来。
第一个就是女神拒绝了你,你崩溃了,其他备胎也崩溃了(没想到女神有这么多备胎);
第二个女神不想你追她,让你去追其他小姐姐,但是女神依旧和备胎们约会;
第三个是把追的最久的备胎扔掉,没新鲜感了,和最新追我的约会;
第四个是把最新的备胎踢掉,是个念旧的女神,就当一切没有发生,继续和之前的备胎们约会。
2.如何使用Java的线程池
虽然ThreadPoolExecutor固然强大,但是参数太多,用起来确实麻烦,所以Java标准库中有更方便的:Executor提供了一些工厂方法,比如:
接受线程池的类型:ExecutorService
创建便捷线程池的方法:newFixedThreadPool(),把核心线程数和最大线程数设置成一样,不会自动扩容
添加任务到线程池执行:submit()
public static void main(String[] args) throws InterruptedException {
ExecutorService service=Executors.newFixedThreadPool(4);
for(int i=0;i<100;i++){
int id=i;
service.submit(()->{
Thread current=Thread.currentThread();
System.out.println("hello world "+id+ " "+current.getName());
});
}
Thread.sleep(2000);
service.shutdown();
System.out.println("程序结束");
}
三、简单实现一个线程池
需要如下条件:
一个堵塞队列queue,来装要执行的任务,通过take取任务。
线程池类,来用for循环放线程(相当于线程池),这个类有submit()方法用queue队列中用put就能放进去执行这些线程中。
(由于拒绝策略太麻烦了,这里没有写出来,感兴趣的可以自行写一下)
class MyThreadPool {
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
//创造线程
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while (true) {
try {
Runnable runnable=queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
}
}
//创建任务
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}
public class demo15 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool=new MyThreadPool(4);
for(int i=0;i<1000;i++){
int id=i;
myThreadPool.submit(()->{
System.out.println("执行任务 "+id+" "+Thread.currentThread().getName());
});
}
}
}
- 核⼼操作为 submit,将任务加⼊线程池中
- 使⽤ Worker 类描述⼀个⼯作线程。使⽤ Runnable 描述⼀个任务.
- 使⽤⼀个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情:不停的从 BlockingQueue 中取任务并执⾏
- 指定⼀下线程池中的最⼤线程数 maxWorkerCount;当当前线程数超过这个最⼤值时,就不再新增线程了