线程池,为什么需要线程池在没
线程池,为什么需要线程池在没
线程池
线程池概述
线程池见名知意,就是指一个装多个线程的池子。
为什么需要线程池
在没有线程池的情况下,我们执行一个任务会创建一个线程,执行完毕后线程就会销毁,如果有新的任务就需要重复这些步骤,所以线程池存在的意义就是在执行完一个任务之后,线程不会销毁,并保存在线程池里面,如果有新的任务直接调用线程池里面的线程即可。
线程池的优点:
- 降低资源的消耗:通过重复利用现有的线程对象执行任务,避免了多次创建和销毁的步骤
- 提高了相应速度:因为省去了创建线程的步骤
- 提供附加功能:线程池的扩展性可以使得我们以后加入一些自己的功能,比如说定时,延时
创建步骤
- 创建线程池对象
- 有任务需要线程对象执行的时候,创建线程对象去执行,执行完毕后不销毁,存到线程池中
- 当所有任务都执行完毕,线程池就没有存在的必要了,所以需要关闭线程池
线程池实现代码
方法一:static Executors.newCachedThreadPool() 创建一个默认的线程池
public class ExecutorDemo {
public static void main(String[] args) throws InterruptedException {
/**
* Executors用于创建线程池
* ExecutorService用于操作线程池
*/
ExecutorService es=Executors.newCachedThreadPool();
es.submit(new Runnable() {
@Override
public void run() {
//任务1
System.out.println(Thread.currentThread().getName()+"线程1");
}
});
//任务2
es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});
//关闭线程池
es.shutdown();
}
}
结果是
但是这造成一个问题,以上程序并没有体现出一个线程重复利用的思想,而是创建了两个线程执行两个任务,原因是:线程池遇到任务1时,创建了线程1执行,但是,任务1还没执行完,线程没有归还的时候,任务2抢占到了执行权,这个时候线程池没有多余的线程可以用,因此创建了线程2来执行任务2。
为了避免这个问题,需要让程序沉睡一下,让线程1有充足的时间执行完任务并归还
代码如下:
public class ExecutorDemo {
public static void main(String[] args) throws InterruptedException {
/**
* Executors用于创建线程池
* ExecutorService用于操作线程池
*/
ExecutorService es=Executors.newCachedThreadPool();
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程1");
}
});
//增加睡眠
Thread.sleep(100);
es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});
//关闭线程池
es.shutdown();
}
}
结果:
以上就实现了一个线程执行多个任务的案例。
方法二:static Executors.newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
代码如下:
public class ExecutorDemo2 {
public static void main(String[] args) throws InterruptedException {
/**
* newFixedThreadPool中的参数表示线程池中的最大线程数
*/
ExecutorService es = Executors.newFixedThreadPool(10);
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程1");
}
});
Thread.sleep(100);
es.submit(()->{System.out.println(Thread.currentThread().getName()+"线程2");});
//关闭线程池
es.shutdown();
}
}
可以发现即使让程序进入睡眠仍然创建了两个线程,这是因为newFixedThreadPool()方法要求,在创建完最大数量的线程数之前,只会新建线程,而不会重复利用旧线程。
方式三:自定义线程池
进入newCachedThreadPool() 可以发现方法内部也是通过调用ThreadPoolExecutor类来创建线程池的
因此可以直接根据该类自定义线程池
以上形参一共有7个,可以举例来说明这些参数的作用
案例:假如现在有一家高级餐厅(线程池),实行员工顾客一对一服务,那么代表每有一名顾客,餐厅就需要招聘一名员工为其服务,但是老板基于成本考虑,餐厅不能无限制的招人,因为当顾客离去后,招聘的人太多了就浪费了成本,因此老板把员工分成了正式员工和临时员工,正式员工会一直呆在餐厅工作,而临时员工只会在正式员工繁忙的时候被招进来工作,当顾客较少时,临时员工会被辞退,另外,即便有临时员工的存在,一家餐厅的同时服务人数也是有上限的,等待被服务的顾客,只能在餐厅外排队等候,同时餐厅还对排队人数做了限制,如果排队人数过多的话,超出范围的人数会被拒绝服务。
现在根据本案例中提及的内容,和ThreadPoolExecutor构造方法的形参对号入座
形参名 | 事件解释 | 术语解释 |
---|---|---|
int corePoolSize | 正式员工的数量 | 核心线程数 |
int maximumPoolSize | 餐厅最大的员工数量 | 最多线程数 |
long keepAliveTime | 临时员工空闲多长时间会被辞退(值) | 临时线程数空闲时长 |
TimeUnit unit | 临时员工空闲多长时间会被辞退(单位) | 临时线程数空闲时长(单位) |
BlockingQueue |
排队的客户 | 阻塞队列 |
ThreadFactory threadFactory | 员工从哪里招 | 线程创建处 |
RejectedExecutionHandler handler | 排队人数过多时拒绝服务 | 定义拒绝策略 |
代码实现:
public class ExecutorDemo3 {
public static void main(String[] args) throws InterruptedException {
//创建自定义线程池
ThreadPoolExecutor te = new ThreadPoolExecutor(
2,
4,
2,
TimeUnit.DAYS,
new ArrayBlockingQueue<>(6),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
for (int i = 0; i < 10; i++) {
te.submit(() -> {
System.out.println(Thread.currentThread().getName() + "正在执行");
});
}
//关闭线程池
te.shutdown();
}
}
结果:
可以看到正式员工和临时员工都被调用了,但是服务人数最多为10人,因为服务人数上限=最大员工数+排队人数,如果超过这个数量会报错,这是因为超出的人员会被拒绝服务。
拒绝策略
RejectedExecutionHandler是jdk提供的一个任务拒绝策略结构
子类 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常。是默认的策略。 |
ThreadPoolExecutor.DiscardPolicy() | 丢弃任务,但是不抛出异常 这是不推荐的做法。 |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃队列中等待最久的任务 然后把当前任务加入队列中。 |
ThreadPoolExecutor.CallerRunsPolicy() | 调用任务的run()方法绕过线程池直接执行。 |
用户点评