多线程八 线程池

1. 线程池

创建线程的方式

  • 继承 Thread
  • 实现 Runnable
  • 实现 Callable
  • 线程池(推荐使用线程池来创建线程

线程池概述:

程序启动一个新的线程的成本是比较高的,因为它涉及到要与操作系统的交互. 而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生命周期很短的线程时,更应该考虑使用线程池. 线程池里的每一个线程在调用结束后,并不会死亡,而是再次回到线程池中成为空闲状态,再次等待调度. 在JDK5之前,线程池必须手动来创建;JDK5之后,Java内置线程池来直接使用.

线程池的三个优点如下:

  1. 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁带来的消耗
  2. 提高响应速度: 当任务到达时,任务可以不需要等待线程创建能立即执行(线程池中存在已经创建好的线程)
  3. 提高线程的可管理性: 使用线程池可以统一进行线程分配、调度和监控

在这里插入图片描述
源码解析 Interface Executor
在这里插入图片描述

2. 线程池核心接口

ExecutorService 普通线程池

向线程池提交任务:

execute(Runnable command)

submit(Collable<T> task || Runnable)

ScheduledExecutoService 定时线程池

scheduleAtFixedRate()

ThreadPoolExecutor 线程池核心类

ThreadPoolExecutor : ExecutorService 的子类

ThreadPoolExecutor(int corePoolSize,intmaximumPoolSize,
                   long keepAliveTime,TimeUnit unit,
                   BlockingQueue<Runnable> workQueue,
                   RejectedExecutionHandler handler)

3. 线程池工作原理

[外链图片转存失败(img-4QrjGqJw-1565343912034)(C:\Users\j2726\AppData\Roaming\Typora\typora-user-images\1564904750301.png)]

线程池工作流程:

当一个任务提交给线程池时 ---->

核心线程池 corePoolSize

最大线程池 maximumPoolSize

阻塞队列 BlockingQueue

拒绝策略 RejectPolicy

  1. 首先判断核心池的线程数量是否达到 corePoolSize. 若未达到,线程池创建新的线程执行任务并将其置入核心池中; 否则,判断核心线程池是否有空闲线程. 若有,分配任务执行; 否则,进入步骤2
  2. 判断当前线程池中线程数量有没有达到线程池的最大数 maximumPoolSize. 若没有,创建新的线程执行任务并将其置入线程池中; 否则,进入步骤3
  3. 判断阻塞队列是否已满. 若未满,将任务置入阻塞队列中等待调度. 否则,进入步骤4
  4. 调用相应的拒绝策略打回任务.(有四种拒绝策略,默认抛出异常给用户 AbortPolicy)

4. java中常见的阻塞队列

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,静态工厂方法Executors.newFixedThreadPool()使用了这个队列
  • 内置线程池 FixedThreadPool, SingleThreadPool
  • SynchronousQueue:一个不存储元素的阻塞队列
  • 内置线程池 CachedThreadPool (一个元素的插入必须等待同时有一个元素的删除操作,否则插入操作就一直阻塞)
  • PriorityBlockingQueue: 基于优先级的阻塞队列

5. 创建线程池以及向线程池提交任务

手工创建一个线程池

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 3, 5,
                                        2000,TimeUnit.MILLISECONDS,                                                                      
                                        new LinkedBlockingDeque<Runnable>());

可以使用两个方法向线程池提交任务,分别为execute() 和 submit()方法

  • execute()方法用于提交不需要返回值的任务; 所以无法判断任务是否被线程池执行成功
  • submit()方法用于提交需要返回值的任务; 线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
线程中提交方法 传入参数
execute() Runnable
submit() Runnable 或 Callable
package com.ThreadPool;

import java.util.concurrent.*;

/**
 * @Author: Mr.Q
 * @Date: 2019-08-05 08:05
 * @Description:
 */
class ThreadPoolTest implements Callable<String> {
    private Integer tickets = 20;
    @Override
    public String call() throws Exception {
        for (int i = 0; i < tickets; i++) {
            if(tickets > 0)
                System.out.println(Thread.currentThread().getName() +"还剩 "+tickets--+" 票...");
        }
        return Thread.currentThread().getName() + "票卖完了!";
    }
}

public class ExecutorTest {
    public static void main(String[] args) {
        ExecutorService executor = new ThreadPoolExecutor(2,3,
                60,TimeUnit.SECONDS,
                new LinkedBlockingQueue<>());
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        for(int i = 0; i < 5; i ++) {
            executor.submit(threadPoolTest); //submit可以提交Callable,Runnable接口
        }
        executor.shutdown();
    }
}

在这里插入图片描述
提交了5次任务,但是 ThreadPool 创建了两个线程;

原因是核心线程池未满,有空闲线程;由于实现 Callable接口只做了 System.out,线程拿到任务很快执行完成;执行任务的速度快于提交任务的速度,就不在创建线程了…

线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力

6. 合理配置线程池

查看当前电脑CPU数目:

System.out.println(Runtime.getRuntime().availableProcessors());

输出的为当前电脑可执行的线程数目,目前默认一个CPU可以同时执行俩个线程,CPU数为线程数的一半

配置核心池以及最大线程池线程数量

  • CPU密集型任务:nCPU + 1
  • IO密集型任务: 2 * nCPU

CPU密集型任务 : 频繁操作CPU(大数运算)

IO密集型任务 : 频繁操作文件(读写文件)

7. JDK 内置的四大线程池 Executor

Executor 框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。通过Executor框架的工具类Executors,可以创建3种类型的 ThreadPoolExecutor

1.缓存线程池(无大小限制的线程池)newCachedThreadPool()

使用场景:适用于负载较轻的服务器,或执行很多短期的异步任务

ExecutorService executorService = Executors.newCachedThreadPool();

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
任务提交速度 > 线程执行速度 会不断创建线程(有可能无限创建线程将内存写满)
任务提交速度 < 线程执行速度 只会创建若干线程

2.固定大小线程池newFixedThreadPool(int nThreads)

使用场景: 适用于负载较重的服务器,来满足资源分配的要求

ExecutorService executorService = Executors.newFixedThreadPool();

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

3.单线程池newSingleThreadExecutor()

使用场景: 多线程场景下需要让任务串行执行

ExecutorService executorService = Executors.newSingleThreadExecutor();

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

4.定时调度池: newScheduledThreadPool();

使用场景:ScheduledExecutorService是java.util.concurrent并发包下的一个接口,表示调度服务

ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

定时调度池中的方法

  1. scheduled.schedule();

延迟 delay个时间单元后创建 nThreads个线程执行 command任务

 ScheduledExecutorService scheduled = Executors.newScheduledThreadPool();
 scheduled.schedule();
 
 public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
  1. scheduled.scheduleAtFixedRate();

延迟 delay个时间单元后每隔 period time执行一次 command任务

 ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(3);
 scheduled.scheduleAtFixedRate();

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
  1. scheduled.scheduleWithFixedDelay();

scheduled.scheduleAtFixedRate();scheduled.scheduleWithFixedDelay();都表示每间隔一段时间定时执行任务

  • scheduleAtFixedRate是以上一次任务的开始时间为间隔的,并且当任务执行时间大于设置的间隔时间时,真正间隔的时间由任务执行时间为准
  • scheduleWithFixedDelay是以上一次任务的结束时间为间隔

ScheduledExecutorService中方法详解(参考)

发布了77 篇原创文章 · 获赞 118 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43232955/article/details/98971298