线程调度之线程池
创建一个线程,销毁一个线程,都会消耗一定量的CPU资源。当线程创建得越来越频繁,而系统的CPU已经分配不了那么多的资源,可能会变成一种等待的状态,使用起来非常不方便。那么有没有什么方法可以直接现取现用不需要等待,提高线程调度的效率呢?
线程池的概念
我们在学习Java时不免要认识到很多池,例如:字符串常量池,数据库连接池等其他池。
字符串常量池
:将经常会用到的字符串存储在一个‘池’一样的地方,当需要使用时,直接拿取,
不需要在内存中开辟一个空间
存放该内容!
数据库连接池
:负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是
再重新建立一个
。
线程池的创建:
//Executors.newCachedThreadPool :线程池
ExecutorService pool = Executors.newFixedThreadPool(10);//设定线程数
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
同样的,线程池也是这样的一个存在,当你需要足够多的资源,而又不想消耗太多CPU,就可以使用线程池提高效率,接下来我由两个方面来介绍线程池对效率的提升:
-
线程的普通创建是在内核态的,然后由
内核态转换为用户态
,这样的转换不得已会消耗资源,虽然看起来非常小,但是与
直接在用户态
中拿取线程对比,效率的确要略低一筹。 - 线程池是Java官方提供的线程池,肯定是从各个方面进行了优化,其中可以调用的线程数也有最大值,不过一般使用到最大值。
工厂模式
线程池的工厂模式,解决构造方法的‘坑’,当我们创建线程时,会调用构造方法,而我们有时候不知道要传递什么参数来调用构造方法,而这个工厂模式,就可以解决这样的问题。
工厂模式的使用,官方给出的使用方法:
参数解读
:
int corePoolSize | 核心线程数(最重要的线程数) |
---|---|
int maximumPoolSize | 最大线程数 |
int keepAliveTime | 保持活跃的线程数(除核心线程数的其他线程) |
Timeunit unit | (除核心线程数的其他线程)存活数 |
BlockingQueue workQueue | 线程池中需要管理许多任务,通过阻塞队列进行管理 |
ThreadFactory threadFactory | 线程工厂,创建线程的辅助类 |
RejectedExecutionHandler handler | 线程池的拒绝策略 |
线程池的拒绝策略
(敲黑板,面试常考):
AbortPolicy | 直接抛出异常 |
---|---|
CallerRunsPoilcy | 由添加的线程自己执行该任务(对自己的行为负责) |
DiscardOldestPolicy | 丢弃最久的任务,接收新任务 |
DiscardPolicy | 丢弃最新任务,也就是不能接收当前任务 |
使用工厂模式创建对象时,不需要直接
new
,使用其静态方法直接创建需要的对象,为何可以使用不同的构造方法,是因为构造方法重载了。在创建线程时可以填写数字来确定创建的线程数!不过不填写会根据代码需求来创建线程数。
实现一个线程池
:
class MyThreadPool {
//用于存放任务
private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();
//给任务池中添加任务
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//指定创建线程数
public MyThreadPool(int n){
for (int i = 0; i < n; i++) {
Thread t = new Thread(()->{
//只要有任务就一直取
while (true) {
try {
Runnable r = queue.take();
r.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
t.start();
}
}
}
使用实现的线程池:
class Main{
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int number = i;//变量捕获
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("The number: "+number);
}
});
}
}
}
这里的number是一个实际final,所以这里要用number来代替i。
在实际开发中,线程的数量是如何确定的
?
解答这个问题,我们需要明白程序的工作,一般程序的任务分为两类:
- CPU密集性任务,要在CPU上运行,对性能要求很高
- IO密集性任务,主要是等待IO进行操作,对性能没有什么要求
具体需要的线程数要进行测试才知道,通过时间和空间来计算
最优
的线程数。