目录
目录
介绍线程池之前,我们先了解一下一中设计模式---工厂模式。
一.工厂模式
工厂模式是用普通方法来代替构造方法,以此来更加灵活的构造不同的实例。那么为什么构造方法无法实现呢?因为构造方法存在一定的局限性。如果使用同一个参数列表的构造方法,那么就只能构造出一种实例,无法满足实际需求了。工厂模式解决了这种问题,它使用普通方法来构造实例,所以我们要创建何种实例就使用不同的方法来构造就行了。
👇举一个简单的栗子,来更好的理解工厂模式的作用。
class student{
String name;
int age;
}
class teacher{
String name;
int age;
}
class personFactory{
public static student newStudent(String name,int age){//构造学生
//........
}
public static teacher newTeacher(String name,int age){//构造老师
//........
}
}
在上面的代码中,我们通过两个静态方法构造了学生实例和老师实例,这两个方法的参数完全一致,如果我们使用构造方法,是无法完成这样的构造的。
其实这样一看,工厂模式也并不是什么高深莫测的东西,它更像是规范了一种代码写法,使之规避语法上的缺陷。
介绍完了工厂模式,我们来了解线程池。
二.线程池
线程池的存在原因是随着技术的发展,普通创建线程的效率已经无法满足需求了,此时需要提高创建和销毁线程的效率。由于Java标准库还没有协程(一种轻量级线程),所以目前只能使用线程池来提高效率。
此处的线程池本质与字符串常量池,数据库连接池一样,它把将使用的线程,提前创建好,并放在线程池中,当需要使用线程时,直接从线程池中获取,使用完成后,还给线程池。向池获取线程以及把线程还给池,这两个动作(用户代码实现,无需交由内核完成)比创建线程、销毁线程高效许多,这样就提高了程序的效率。
1.创建线程池
Java标准库中提供了Executors工厂类创建线程池。
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);//使用工厂类创建线程
for (int i = 0 ;i <1000;i++) {
int n = i;
pool.submit(new Runnable() {//将任务加入到线程池中
@Override
public void run() {
System.out.println(n);
}
});
}
}
线程池的线程数设定规则:需要共同考虑程序对CPU的占用和IO的占用,来设定合适地线程数。
具体做法:通过测试确定。
变量捕获
在上面代码的for循环中,为什么输出的不可以是i而是n?
i是主线程上的变量,存在于主线程栈上,但是在多线程的环境下,submit把任务加入到线程池执行,任务实际的执行时间是不确定的,可能轮到任务执行时,i已经改变了许多次甚至已经销了,所以java有变量捕获这个技能,其作用是在run被定义的时候,就把主线程栈上的i在当前run的栈上拷贝一份,后面在执行run时,就使用拷贝的i。在JDK1.8前,要求变量捕获必须是final修饰的变量,在JDK1.8开始,放松了标准,要求不一定要被final修饰,只要代码没有修改过这个变量,也可以捕获。
2.ThreadPoolExecutor类
上面的工厂Executors类总共用以下方法构造线程池。
而在这些方法中除了newWorkStealingPool外,都是通过ThreadPoolExecutor来实现的。
如newCachedThreadPool的源码所示:
a.构造方法
那就来看看ThreadPoolExecutor的构造方法:
第四个构造方法包含了前三种,我们就介绍第4种的参数列表。(面试高频)
1️⃣这些构造方法中的前两个参数,corePoolSize代表了核心线程数,maximumPoolSize代表了最大线程数。举个例子理解的话就是:核心线程数就是一个公司的正式员工,最大线程数是一个公司的实习生,那么根据公司的情况,采用一种调整策略:正式员工保持不动,根据任务的多少,动态调节临时工的数量。
2️⃣keepAliveTime是规定非核心线程在这一个时间内都没有任务做,就会被销毁,TimeUnit则是时间单位参数。
3️⃣BlockingQueue<Runnable>为线程池的任务队列,如果有任务,线程池非配线程工作,如果没有任务就阻塞。
4️⃣ThreadFactory用于创建线程。
5️⃣RejectExecutionHandler描述了一种拒绝策略,如果当队列满时,继续添加任务会有什么样的行为。
b.拒绝策略
第一种:如果队列满了,直接抛出异常。
第二种:如果队列满了,多出来的任务,那个线程加的,谁执行。
第三种:如果队列满了,丢弃最早的任务。
第四中:如果队列满了,丢弃最新的任务。
3、实现一个简单的线程池
class MyThreadPool {
private BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>();
public MyThreadPool(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(() -> {
while(true){
try {
Runnable runnable = blockingQueue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
}
}
public void submit(Runnable runnable) {
try {
blockingQueue.put(runnable);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
以上就是本文的全部内容了。