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
    评论
java面试笔试资料包括JAVA基础核心知识点深度学习Spring面试题等资料合集: JAVA核心知识点整理-282页 Java与哈希算法.docx Java中Lambda表达式的使用.docx JAVA多线程之线程间的通信方式.docx Java注解详解.docx Java线程池.docx JDK1.8Stream操作.docx JDK8有新特性.docx JVM堆三代.docx JVM的垃圾回收机制详解和调优.docx Spring源码分析之IoC.docx 关于线程和线程池学习使用.docx 深入理解JVM垃圾回收机制.docx 深入理解多线程实现的另一种方式Callable.docx 红黑树简介.docx 线程死锁及解决办法.docx 线程锁之重入锁.docx 线程间的通信.docx 虚拟机内存结构和垃圾回收docx.docx 锁分类的了解.docx 集合的扩容机制.png SpringMVC部分.docx Spring部分.docx 第一题.pdf 第七题 谈谈MySQL支持的事务隔离级别 (1).pdf 第三题 对比HashTable HashMap TreeMap有什么不同.pdf 第二题 Exception Error区别.pdf 第五题 如何保证集合是线程安全的.pdf 第八题 Java并发类库提供的线程池有哪几种 分别有什么特点.pdf 第六题 synchronized和ReentLock有什么区别.pdf 第四题 ArrayList LinkedList Vector的区别.pdf docker讲得最清楚.doc Dubbo是什么?能做什么?.doc java 基于TCP协议的Socket编程和通信.doc Java面试高级篇—说说TCP,UDP和socket,Http之间联系和区别.doc MySQL千万级的大表要怎么优化(读写分离、水平拆分、垂直拆分).doc redis缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级.doc RPC (Remote Procedure Call)即远程过程调用.doc Spring 面试问题 TOP 50(干货推荐收藏必备).doc springboot常见面试题.doc svn和git的区别及适用场景.doc ZooKeeper.doc 为什么分布式一定要有Redis.doc 分布式、高并发、多线程,到底有什么区别.doc 分布式事务.doc 四款消息队列大比拼.docx 多台web服务器之间共享session.docx 消息中间件Kafka与RabbitMQ.doc 电商项目描述注意点.doc 秒杀业务的流量削峰场景如何解决.doc 面试题:Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点.doc

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘了了

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

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

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

打赏作者

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

抵扣说明:

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

余额充值