多线程代码案例之线程池

目录

1.池的引入介绍

2.线程池参数介绍和使用

3. 模拟实现线程池


1.池的引入介绍

池,在计算机中很常见。

1.1.什么是池

(1)池举例

像我们之前已经接触过的:常量池,现在准备学习的线程池等等。像:常量池、线程池、数据库连接池、进程池、内存池等等,它们之间用到的思想都是差不多的。

(2)使用池的目的

目的:提高效率

(3)如何提高效率

相比大家对“养鱼”这个词语都不陌生吧,它不是普通的养鱼,而“养鱼”的人就称为“海王”。

而“养鱼”就需要一个池,暂且称它为鱼池。当一个海王不想要这条鱼时,就可以直接丢弃,并且可以直接从鱼池中再选取一条鱼。如果没有池,那么丢弃之后,就要花费各种时间和精力再去寻找下一条鱼。因此,池是可以提高换鱼的效率的。

1.2.线程池的优势

(1)线程池出现的原因

即使可以多线程编程,但是如果线程反复不断的创建和销毁,这些小的花销积攒起来也是不小的,当所谓,时间久了就会感到厌倦。

为了对上述情况进行优化,有两种做法:线程池和协程(纤程),这里只介绍线程池

(2)线程池可以提高效率的原因

1)直接调用api去创建线程/销毁线程,是需要通过 用户态+内核态 配合去完成的,而内核中的许多操作都重量级的。

2)通过线程池/协程,则只需要通过用户态就可以完成,不需要通过内核态。因此使用线程池之后,效率会得到提高。

3)在使用线程吃的时候,线程都是已经提前创建好并且使用数据结构存放在用户态中;当需要使用的时候,可以直接取出来,使用完再存放回去。

2.线程池参数介绍和使用

2.1.线程池的七个参数

标准库线程池的类:ThreadPoolExecutor,有四个构造方法,下面介绍构造方法中参数最多的一个。(七个构造方法、第七个最重要)

上述的某些参数,不单独介绍,需要组合起来一起介绍 

(1)第一、二个参数

1)corePoolSize:表示核心线程数

2)maximumPoolSize:表示最大线程数(最大线程数=核心线程数+非核心线程数)

3)线程池的动态扩展

当线程池刚创建好的时候,里面就会包含n个核心线程,此时,线程池中的线程数也就是n。

而线程池会提供一个submit方法,就可以往线程池中添加任务(Runnable对象);当任务少时,不需要多添加其他的线程,但是当任务多时,此时的n个线程处理不过来了,这个时候,线程池就会自动创建出新的线程来(创建的线程总数 <= 最大线程数),这个线程就是非核心线程;当任务少时,就会把这些非核心线程给回收了。

4)线程数的设计

在实际开发中,线程数不可无脑设计,需要根据实际的情况来。(电脑的配置和cpu核心数等等)

两个极端情况:

1.cpu密集型程序,完成代码需要消耗很多的cpu。此时的线程数目应该<=cpu逻辑核心数。

2.IO密集型程序,不需要很多cpu。此时,线程数可以任意多

但是实际的情况,都是介于两者之间的,所以正确的做法是:

通过实验的方式,去测试性能最高的一个值。

注意:当线程池中的线程数目过多时(甚至超过cpu逻辑核心数),不会出现问题,但是会增加线程调度的开销。因为,cpu逻辑核心数就那么多,这么多线程,就需要去等待,也就是调度。

(2)第三、四个参数

1)keepAliveTime:非核心线程运行空闲的最大时间(超过就会被回收)

2)unit:上面的时间单位

这些单位有很多种:

(3)第五个参数

1)workQueue:任务队列

2)作用:用于保存Runnable任务,供线程去执行(会在submit方法中进行添加任务)

(4)第六个参数

1)threadFactory:工厂类

2)作用:用于创建线程

3)这个参数可以不提供

(5)第七个参数

1)handler:拒绝策略

2)作用:当任务队列满时,线程池该怎么做?

3)下面四种做法(策略)

1.ThreadPoolExecutor.AbortPolicy:直接抛出异常。

2.ThreadPoolExecutor.CallerRunsPolicy:线程池不执行该任务,谁添加的任务,就由谁来执行

3.ThreadPoolExecutor.DiscarOldestPolicy,:丢弃最老的任务(队头任务),执行新的任务

4.ThreadPoolExecutor.DiscardPolicy,:丢弃最新的任务(正在submit的任务),然后按照原有方式执行

2.2.线程池的工作流程

这里是介绍在有了线程池之后的。

(1)当向线程池中添加任务时,线程池会先检查是否有空闲的线程。如果有空闲线程,就会给该任务分配一个空闲的线程。

(2)如果没有空闲的线程,就会检查当前线程数是否小于核心线程数;如果小于,线程池会创造一个新的核心线程来执行任务。

(3)如果线程数>=核心线程数,线程池会检查工作队列是否已满;如果工作队列未满,新任务会被放入工作队列中等待执行。

(4)如果工作队列已满,检查当前线程数是否小于最大线程数;如果小于,线程池就会创建新的非核心线程来执行任务。

(5)如果当前线程数已达到最大值,线程池就会采取拒绝策略。

以上的五个过程是一个动态的过程。下面是大致的流程图

2.3.使用现有线程池

因为ThreadPoolExecutor类的构造方法,直接使用起来比较的复杂,所以标准库中自己提供了几个工厂类,对该线程池进行了进一步的封装(也就是使用了工厂模式对构造进行封装,初始化就不需要调用构造方法,直接调用工厂类的静态方法即可)

(1)工厂类

Executors

(2)工厂类提供可以创建线程池的四个方法

        //1.创建最普通的线程池、可以根据任务的数目。自动进行线程扩容
        Executors.newCachedThreadPool();
        //2.创建固定数目的线程池
        Executors.newFixedThreadPool(10);
        //3.创建一个只含单个线程的线程池
        Executors.newSingleThreadExecutor();
        //4.创建一个固定线程个数,但是任务延时执行的线程池
        Executors.newScheduledThreadPool(10);

第一、第二个比较常用;而前面两个中第一个是无线创建线程,容易消耗更多的cpu资源,为了更安全等因素,我们推荐使用第二个可以指定线程数的方法。

(3)接收返回值

工厂调用返回的类型是ExecutorService类型的

ExecutorService service = Executors.newFixedThreadPool(10);

(4)使用线程池编程

创造好线程池之后,就需要向线程池中添加Runnable任务,当有任务之后,线程就会马上执行。

public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 5000; i++) {
            int count = i;
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("第"+count+"个任务"+" name: "+Thread.currentThread().getName());
                }
            });
        }
    }

运行结果:

3. 模拟实现线程池

现在,我们要自己模拟实现一个线程池。先明确一个线程池所需要的东西:

(1)n个线程-用于执行任务(2)任务队列-用于存放任务(3)submit方法-用于向线程池中提交任务

而这里模拟的线程池,是被工厂类封装起来的。

3.1.实现最大核心数线程池

(1)线程池需要的框架

(2)完成线程池

class MyThreadPool {
    //1.有任务队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);//任务队列可以存放1000个Runnable任务
    //2.构造方法,用于初始化
    public MyThreadPool(int morePoolSize) {//n就是要初始化多少个线程
        for (int i = 0; i < morePoolSize; i++) {//下面根据核心线程数创造
            Thread t = new Thread(()->{
               while (true) {//线程创建后之后,就不断的去执行任务,否则只执行一次任务就会被销毁了
                   try {
                       Runnable runnable = queue.take();//从任务队列中取出任务
                       runnable.run();//执行run中的任务
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }

            });
            t.start();
            threadList.add(t);
        }
    }
    //3.提供submit方法
    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);
    }
}

1)构造方法中,可以执行最大的线程数;当创建对象时,线程就已经被创建好了,当任务队列为空时,这些线程就会阻塞等待,直到有任务。

2)submit方法,只需要向任务队列中添加任务即可

测试代码:

public static void main(String[] args) throws InterruptedException {
        MyThreadPool threadPool = new MyThreadPool(10);//核心线程、最大线程
        for (int i = 0; i < 101; i++) {//循环向线程池中插入任务
            int id = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("我是第"+id+"个任务"+Thread.currentThread().getName());
                }
            });
        }
    }

运行结果:

上面是指定只有核心线程数的,下面多加一个最大线程数

3.2.使用最大线程数线程池

(1)改进1:构造方法多一个参数,然后存于成员变量maxPoolSize中

(2)改进2:提供一个顺序表,用于存储已有线程

(3)改进3:在submit方法中进行判断,当已有任务到达一定的数量并且已有线程数还可以增加时,就多创建一些线程出来进行处理任务。

(4)代码改进: 

class MyThreadPool {
    //1.有任务队列
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);//任务队列可以存放1000个Runnable任务
    private int maxPoolSize = 0;
    private List<Thread> threadList = new ArrayList<>();//用于存放已经创建好的线程
    //2.构造方法,用于初始化
    public MyThreadPool(int morePoolSize,int maxPoolSize) {//n就是要初始化多少个线程
        this.maxPoolSize = maxPoolSize;//记录最大的线程数
        for (int i = 0; i < morePoolSize; i++) {//下面根据核心线程数创造
            Thread t = new Thread(()->{
               while (true) {//线程创建后之后,就不断的去执行任务,否则只执行一次任务就会被销毁了
                   try {
                       Runnable runnable = queue.take();//从任务队列中取出任务
                       runnable.run();//执行run中的任务
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
               }

            });
            t.start();
            threadList.add(t);
        }
    }
    //3.提供submit方法
    void submit(Runnable runnable) throws InterruptedException {
        queue.put(runnable);

        //当任务数到达一定的数量时,自动增加线程
        if(queue.size() >= 500 && threadList.size() < maxPoolSize) {
            Thread t = new Thread(()->{
                while (true) {//线程创建后之后,就不断的去执行任务,否则只执行一次任务就会被销毁了
                    try {
                        Runnable runnable2 = queue.take();//从任务队列中取出任务
                        runnable.run();//执行run中的任务
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

            });
            t.start();
            threadList.add(t);
        }
    }
}

运行结果和上述是一样的

这就模拟出了线程池。

  • 22
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个基于线程池多线程并发读取不同文件内容的Java代码案例: ```java import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FileReadThreadPool { private static final int THREAD_POOL_SIZE = 5; // 线程池大小 private ExecutorService executorService; // 线程池 private File[] files; // 需要读取的文件数组 public FileReadThreadPool(File[] files) { this.files = files; this.executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); } public void readFiles() { for (File file : files) { executorService.execute(new FileReaderRunnable(file)); } executorService.shutdown(); } private static class FileReaderRunnable implements Runnable { private File file; FileReaderRunnable(File file) { this.file = file; } @Override public void run() { try (BufferedReader reader = new BufferedReader(new FileReader(file))) { String line; while ((line = reader.readLine()) != null) { System.out.println(Thread.currentThread().getName() + " : " + line); } } catch (Exception e) { e.printStackTrace(); } } } } ``` 使用方法如下: ```java public static void main(String[] args) { File[] files = {new File("file1.txt"), new File("file2.txt"), new File("file3.txt")}; FileReadThreadPool fileReadThreadPool = new FileReadThreadPool(files); fileReadThreadPool.readFiles(); } ``` 在这个例子中,我们使用了一个固定大小的线程池(`newFixedThreadPool`),并将每个文件的读取任务放入线程池中执行。每个读取任务都是一个实现了`Runnable`接口的内部类`FileReaderRunnable`,它负责打开文件、读取文件内容并输出到控制台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小娥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值