Spring知识点学习(3):线程池的使用

1.实现线程的三种方式

GitHub地址:https://github.com/SirLiuGang/Spring/blob/master/spring-threadpool/src/main/java/com/cn/lg/springthreadpool/a/thread/SingleThread.java

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口通过FutureTask包装器来创建Thread线程
		// 1.继承Thread
        threadExtend threadExtend = new threadExtend();
        threadExtend.start();
        
		class threadExtend extends Thread {
		    @Override
		    public void run() {
		        System.out.println(Thread.currentThread().getName() + "线程启动: 继承Thread类");
		    }
		}
		// 2.实现Runnable接口
        new Thread(() -> System.out.println(Thread.currentThread().getName() + "线程启动: 实现Runnalbe接口")).start();
		// 3.实现Callable接口通过FutureTask包装器来创建Thread线程
        Callable<Integer> oneCallable = new SomeCallable<>(10);
        //由Callable<Integer>创建一个FutureTask<Integer>对象:
        FutureTask<Integer> oneTask = new FutureTask<>(oneCallable);
        // FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
        // 由FutureTask<Integer>创建一个Thread对象:
        Thread oneThread = new Thread(oneTask);
        oneThread.start();

		class SomeCallable<Integer> implements Callable<Integer> {
		    private Integer result;
		
		    public SomeCallable() {
		    }
		
		    public SomeCallable(Integer result) {
		        this.result = result;
		    }
		
		    @Override
		    public Integer call() {
		        System.out.println(Thread.currentThread().getName() + "线程启动: 实现Callable接口");
		        return result;
		    }
		
		}

		// 或者直接使用Lambda表达式:
        int result = 10;
        new Thread(new FutureTask<>(()->{
            System.out.println(Thread.currentThread().getName() + "线程启动: 实现Callable接口 Lambda表达式");
            return result;
        })).start();

2.线程池的使用

GitHub地址:https://github.com/SirLiuGang/Spring/blob/master/spring-threadpool/src/main/java/com/cn/lg/springthreadpool/b/threadpool/ThreadPool.java

java通过Executors提供了四个静态方法创建线程池,分别是:

/**
 * 实现Runnable的方法
 */
class ThreadRunnalbe implements Runnable {

    private static final Logger LOG = LoggerFactory.getLogger(ThreadRunnalbe.class);

    private String threadMethod;    // 线程池的实现
    private Integer index;          // 索引

    ThreadRunnalbe(String threadMethod, Integer index) {
        this.threadMethod = threadMethod;
        this.index = index;
    }

    @Override
    public void run() {
        LOG.info("{}:线程 = {},index = {}", threadMethod, Thread.currentThread().getName(), index);
    }
}
  1. newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
 		ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            es.execute(new ThreadRunnalbe("cacheThreadPool", i));
        }
        es.shutdown();
  1. newFixedThreadPool :创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    (我的项目中常用的是newFixedThreadPool,可以自定义线程池大小,后期可通过CompletionService来进行管理线程的执行情况)
		ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 20; i++) {
            es.execute(new ThreadRunnalbe("fixedThreadPool", i));
        }
        es.shutdown();
  1. newScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。
		// 定时任务线程池
        ScheduledExecutorService es = Executors.newScheduledThreadPool(2);
        for (int i = 0; i < 2; i++) {
            // 参数1:目标对象
            // 参数2:隔多长时间开始执行线程
            // 参数3:执行周期
            // 参数4:时间单位
            es.scheduleAtFixedRate(new ThreadRunnalbe("scheduledThreadPool", i), 5, 10, TimeUnit.SECONDS);
        }
  1. newSingleThreadExecutor: 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
		ExecutorService es = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
            es.execute(new ThreadRunnalbe("singleThreadExecutor", i));
        }
        es.shutdown();

3.线程池的管理

GitHub地址:https://github.com/SirLiuGang/Spring/blob/master/spring-threadpool/src/main/java/com/cn/lg/springthreadpool/c/threadpoolmanage/ThreadPoolManage.java

  1. 原生:线程实现Callable接口,Callable接口是有返回值的,ExecutorService.submit方法将返回值封装为Future ,通过循环Future中线程的状态,获取线程的执行情况。(顺序获取,获取方式是阻塞式的,获取不到就阻塞)
    在这里插入图片描述
		ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<Integer>> resultList = new ArrayList<>();

        // 创建10个任务并执行
        for (int i = 0; i < 10; i++) {
            // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<Integer> future = executorService.submit(new SomeCallable<>(i));
            // ↑ submit和execute方法的区别见:com.cn.lg.springthreadpool.b.threadpool.ThreadPool.shutdownThreadPool()方法
            // 将任务执行结果存储到List中
            resultList.add(future);
        }

        // 遍历任务的结果
        for (Future<Integer> fs : resultList) {
            try {
                LOG.info("执行结果:{}", fs.get());      // 打印各个线程(任务)执行的结果
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                // 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。
                executorService.shutdown();
            }
        }
/**
 * 实现Callable接口可以获取到返回值
 * @param <Integer>
 */
class SomeCallable<Integer> implements Callable<Integer> {
    private Integer result;

    // 通过构造函数将参数注入
    SomeCallable(Integer result) {
        this.result = result;
    }

    /**
     * 实现call方法
     */
    @Override
    public Integer call() {
        System.out.println(Thread.currentThread().getName() + "线程启动: 实现Callable接口");
        return result;
    }

}
  1. 升级版:通过CompletionService管理
    CompletionService将Executor(线程池)和BlockingQueue(堵塞队列)结合在一起,统一使用Callable作为任务的基本单元,整个过程就是生产者不断把Callable任务放入堵塞队列,Executor作为消费者不断把任务取出来运行,并返回结果;
    优势:
  • 堵塞队列防止了内存中排队等待的任务过多,造成内存溢出(毕竟一般生产者速度比較快,比方爬虫准备好网址和规则,就去运行了,运行起来(消费者)还是比较慢的)
  • CompletionService能够实现,哪个任务先运行完毕就返回,而不是按顺序返回,这样能够极大的提升效率;
		ExecutorService executor = Executors.newFixedThreadPool(5);
        CompletionService<Integer> ecs = new ExecutorCompletionService<>(executor);

        // 假设一共有20个任务,每个任务执行1s,使用线程池
        int taskNum = 20;
        for(int i = 0; i < taskNum; i++) {
            final int index = i + 1;
            // Callable的call方法能够返回运行的结果
            ecs.submit(() -> {
                LOG.info("第{}个任务:执行1s,当前线程:{}", index, Thread.currentThread().getName());
                Thread.sleep(2000L);
                return index;   // 因为在CompletionService定义了Integer的返回类型,所以这里需要进行返回
            });
        }

        List<Integer> resultList = new ArrayList<>();
        for(int i = 0; i < taskNum; i++) {
            try {
                Integer result = ecs.take().get();  // 使用阻塞的方式,打印各个线程(任务)执行的结果
                resultList.add(result);
            } catch (Exception e) {
                // 如果执行抛出异常,会在这里捕获
                e.printStackTrace();
            }
        }
        LOG.info("获取到的返回结果:{}", resultList.toString());

        // 当所有线程执行完毕后,关闭线程池
        executor.shutdown();

4.项目中的使用

  1. 视频切片
    需求:用户上传多个视频,需要将所有视频按帧切割成图片。
    前提:用户的视频被上传到了OSS存储,对视频切片需要将视频从OSS下载下来,切割完成后将图片上传到OSS并删除本地视频。
    实现:使用线程池的方式,每个视频使用单独的线程进行处理,而不是单线程排队处理。
    每个线程的实现为:从OSS下载视频到uuid目录,保证多线程下载互不影响,切割出来的图片直接通过流的方式上传到OSS,不占用本地存储,切割完成后删除本地视频及新建的uuid文件夹。(需控制好视频下载和删除的位置,防止不同任务的视频下载到同一目录下,其他线程完成后误删除别的任务的视频,导致别的任务失败)
  2. 从队列从读取任务
    需求:队列为点对点通信。队列中存在大量的任务ID,获取每个任务ID,执行对应的业务逻辑,将数据入库。
    前提:每个任务耗时较长,队列增长速度较快。
    实现:监听器使用线程池,起10个线程实时监听队列,每个监听器中再创建线程池(大小为10),监听器获取到数据后,扔进业务的线程池,异步执行任务,然后继续监听,可以防止监听器阻塞,加快队列的消耗速度。如果业务代码抛出Runnable异常,将异常的任务ID返回给队列重新执行。(通过此方法消耗队列的速度 > 队列的增长速度,毕竟之前是单线程,现在是多线程,还加了分布式,之后的瓶颈就到了数据库那边)
    升级:使用微服务,起2个监听器的微服务,两个服务去消耗队列。(需对任务ID使用分布式锁,防止重复执行任务)
  3. ——待补充
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘了了

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

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

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

打赏作者

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

抵扣说明:

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

余额充值