多线程的使用

多线程的使用

参考:

多线程、线程池的创建方式,为什么阿里推荐自定义线程池 completablefuture 自定义线程池

多线程4种实现方式

1、继承Thread类

2、实现Runnable接口

3、实现Callable接口

  • 实现Callable不能简单把Callable对象传给thread,要使用FutureTask做一次封装
  • get()可以获取到call()返回值
  • get()可以获取到call()返回值

4、线程池

通常在业务代码中前三种都不使用,只使用第四种(线程池)
每个系统通常有一两个线程池,每一个异步任务,提交给线程池执行即可

提交任务到线程池的两种方式

①execute() 返回值为void,代表只执行异步任务,没有返回值
②submit() 返回值为Future,既可以执行任务,野口接收返回值。

四种创建线程方式的区别

①:继承thread和实现runnable接口的方式,无法得到返回值,实现callable接口可以得到返回值。
②:前三种方式都无法控制资源,即来一个线程就要创建一个线程,容易使系统资源耗尽
③:线程池的方式可以控制资源,系统性能稳定。

线程池的优点

①:降低资源消耗。可以重复利用已经创建好的线程,降低线程的创建和销毁带来的损耗。

②:提高响应速度。因为线程池中的线程都处于等待分配任务状态,当任务过来时可以直接执行。

③:提高线程的可管理性。如果是单cpu的话,创建多个线程,会导致资源耗尽,但是线程池有拒绝策略;另外还可以核心业务和非核心业务两种线程池,如果某个时间内存压力大,可以释放掉非核心业务线程池。使用线程池就可以使线程的管理方便。

使用ThreadPoolExecutor方式创建线程池

ThreadPoolExecutor属于原生的创建方式,其他的线程池创建方式都是封装了ThreadPoolExecutor类。

ThreadPoolExecutor继承关系图下图

在这里插入图片描述

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {}

创建ThreadPoolExecutor 线程池的时候有七个重要参数

  1. int corePoolSize:核心线程数,线程池创建好就已经准备就绪的线程数 量,等待接收异步任务 ,异步任务进来后,自动执行。核心线程会一直存在,除非设置了allowCoreThreadTimeOut,才允许核心线程超时。
  2. int maximumPoolSize:线程池允许存在的最大线程数
  3. long keepAliveTime:超时时间。如果当前线程数量大于核心数量,且在keepAliveTime时间内保持空闲,就释放掉。 释放的是最大线程数 - 核心线程数
  4. TimeUnit unit: 超时时间单位
  5. BlockingQueue workQueue:阻塞队列,如果线程有很多,就会把线程保存在队列里 只要线程有空闲,就去阻塞队列中取。
  6. ThreadFactory threadFactory:线程的创建工厂
  7. RejectedExecutionHandler handler:拒绝策略 ,如果线程满了采取的策略
    拒绝策略分类:
    ①:DiscardOldestPolicy 抛弃掉最早进入的线程
    ②:DiscardPolicy 抛弃掉最新的线程
    ③:DiscardPolicy 剩余的线程调用run方法,变为同步执行
    ④:DiscardPolicy 抛弃掉最新的线程,并抛出异常!

使用Executors创建线程

Executors创建线程,其底层还是用的是ThreadPoolExecutor创建的

可以看到 Executors 的源码实现

  1. newFixedThreadPool(int nThreads)

    固定大小 core = 自定义的线程数,但阻塞队列是无界队列,会OOM内存溢出

    核心线程数和最大线程数相等;

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
  1. newCachedThreadPool()

    core是0,最大线程数无限大,无限创建线程的话,会使cpu占用100%,因为cpu要不停的调度线程去执行任务

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
  1. newSingleThreadExecutor(); 单线程的线程池,后台从队列里取,挨个执行。阻塞队列是无界队列,会OOM内存溢出
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
  1. newScheduledThreadPool(); 带有定时任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

线程池的工作顺序

  1. 线程池创建,准备好核心线程数的线程,准备接收任务
  2. 任务越来越多,执行任务的线程数达到核心线程数,这时候进来的新任务会存在阻塞队列中
    • 当核心线程执行完后就去队列中执行任务
  3. 阻塞队列满了,就会开启新的线程,直到线程数达到最大线程数
    • 注意:此时队列满了,新创建的线程会优先处理新进来的任务,而不是处理队列里的任务,
    • 队列里的任务只能由核心线程忙完了再来执行,可能导致队列的任务等待时间过长,队列积压,特别是IO密集型的场景
  4. 达到最大线程数后,任务还在进来,就会执行拒绝策略
  5. 当任务减少,当前线程池的线程数大于核心线程数,超过keepAliveTime 后,max-core的线程会被回收内存空间;

阻塞队列

为什么要使用阻塞队列?

队列是先进先出,当放入一个元素,会放在队列的尾部,而取出一个元素是在队列头部取出,那么当队列为空或队列满了怎么办?

使用阻塞队列,当队列为空,取元素会被阻塞,当队列满了,添加新元素也会被阻塞;

等空队列有数据,或满的队列有空余容量了,被阻塞的线程就会被自动唤醒;

好处是:

不需要关注队列何时阻塞,阻塞的线程何时唤醒,这都是有阻塞队列来完成;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值