java线程池相关解析

1、什么是线程池

线程池就是一种多线程处理形式,处理过程种可以将任务添加到队列种,然后在创建线程自动启动这些任务。这里的线程就是我们学过的线程,任务就是实现了Runnable或Callable接口的实例对象

2、线程上下文切换

对于单核CPU来说,CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换。由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。线程切换时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。

3、线程池的应用场景

java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。

4、创建线程池的几个主要参数

corePoolSize:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务
maximumPoolSize:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);
unit:keepAliveTime的时间单位
workQueue:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中
threadFactory:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建
handler:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy

  • AbortPolicy:默认策略,抛出RejectedExecutionException
  • DiscardPolicy:忽略当前提交的任务
  • DiscardOldestPolicy:丢弃任务队列中最老的任务,给新任务腾出地方
  • CallerRunsPolicy:由提交任务者执行这个任务

5、线程池中线程创建流程及参数理解

6、Java内置线程池—ExecutorService接口介绍

创建一个什么样的ExecutorService的实例(即线程池)需要根据具体应用场景而定,不过Java给我们提供了一个Executors工厂类,它可以帮助我们很方便的创建各种类型ExecutorService线程池,Executors一共可以创建下面这四类线程池:

//1. 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
ExecutorService es=Executors.newCachedThreadPool()
ExecutorService es=Executors.newCachedThreadPool(ThreadFactory threadFactory)
每个方法都可以传入一个自定义的线程类
//2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService es=Executors.newFixedThreadPool(int nThreads)//设置最大线程数
//3. 创建一个定长线程池,支持定时及周期性任务执行。
ExecutorService es=Executors.newScheduledThreadPool(int nThreads)
//4.创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
ExecutorService es=Executors.newSingleThreadExecutor() 

ExecutorService有如下几个常用执行方法:

自己编写任务类,实现Runnable接口
shutdown(Runnable)  //启动一次顺序关闭,执行以前提交的任务,但不接受新任务
shutdownNow(Runnable) //停止所有正在执行的任务,暂停处理正在等待的任务,并返回等待执行的任务列表
submit(Runnable) //提交任务
submit(Callable)
invokeAny(...) 
//方法接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中
//一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个
invokeAll(...)
//invokeAll(...)与 invokeAny(...)类似也是接收一个Callable集合,但是前者执行之后会返回一
//个Future的List,其中对应着每个Callable任务执行后的Future对象。情况下面这个实例:

7、Java内置线程池-ScheduleExecutorService

ScheduleExecutorService是ExecutorService的子接口,具备了延迟运行或定期执行任务的能力

//创建一个可重用固定线程数的线程池且允许延迟运行或定期执行任务
ScheduleExecutorService scheduleExecutorService=Executors.newScheduleThreadPool(int corePoolSize)
//创建一个单线程执行程序,它允许延迟运行或定期执行任务
ScheduleExecutorService scheduleExecutorService=Executors.newSingleThreadScheduleThread()
//延迟时间单位是unit,数量是delay的时间后执行callable
schedule(Callable<V> callable,long delay,TimeUnit unit)

8、Java内置线程池-异步计算结果(future)

Future的常用方法:

//试图取消对此任务的执行
boolean cancle(boolean mayInterrupIfRunning)
//如有必要,等待计算完成,然后获取其结果
V get()
//如有必要,等待所给定计算时间,然后获取其结果
V get(long timeout,TimeUnit unit)
//如果在任务完成前将其取消,返回true
boolean isCancelled()
//如果任务完成,返回true
boolean isDone()

自定义线程池-参数设计分析

核心线程数(corePollSize):核心线程数的设计需要依据任务的处理时间和每秒产生的任务数来决定,例如执行一个任务要0.1秒,系统80%的时间每秒都产生100个任务,那么想在一秒内处理完这些任务就要10个线程,剩下的20%要利用最大线程数来解决。

任务队列长度(workQueue):核心线程数/单给执行任务时间*2,如上面的场景就要设置长度为200,2代表任务的最大等待时间为2秒。

最大线程数(maximumPoolSize):如上场景中,如果系统每秒产生的最大任务是1000个,那么最大的线程数=(最大任务数-任务队列长度)*单个任务执行时间=80。

最大空闲时间:合理设计即可。

自定义线程池-实现步骤

编写任务类(MyTask):实现Runnable接口

public class MyTask implements Runnable{
    private  int id;//任务编号
    //利用构造方法初始化id
    public MyTask(int id){
        this.id=id;
    }
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("开始执行线程"+name+"任务"+id);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("完成执行线程"+name+"任务"+id);
    }

    @Override
    public String toString() {
        return "id="+id;
    }
}

编写线程类(MyWorker):用于执行任务,需要持有所有任务

//设置一个属性,用于保存线程的名字,设置一个属性保存所有任务
public class MyWorker extends Thread{
    private String name;
    private List<Runnable> tasks;
    public MyWorker(String name,List<Runnable> tasks){
        super(name);
        this.tasks=tasks;
    }
    @Override
    public void run() {
       //判断集合中是否有任务,有就一直执行
        while (tasks.size()>0){
            Runnable r=tasks.remove(0);
            r.run();
        }
    }
}

编写线程池类(MyThreadPool),包含提交任务和执行任务的能力

public class MyThreadPool {
    private List<Runnable> tasks= Collections.synchronizedList(new LinkedList<Runnable>());//任务队列  需要线程安全
    private int num;//当前线程数
    private int corePoolSize;//核心线程数
    private int maxSize;//最大线程数
    private  int workSize;//任务队列的长度

    public MyThreadPool(int corePoolSize,int maxSize,int workSize){
        this.corePoolSize=corePoolSize;
        this.maxSize=maxSize;
        this.workSize=workSize;
    }

    //提交任务
    public void submit(Runnable r){
        if(tasks.size()>maxSize){
            System.out.println("任务"+r+"被丢弃了");
        }else {
            tasks.add(r);
            //执行任务
            excTask(r);
        }

    }

    private void excTask(Runnable r) {
        if (num<corePoolSize){
            new MyWorker("核心线程"+num,tasks).start();
            num++;
        }else if(num<maxSize){
            new MyWorker("非核心线程"+num,tasks).start();
            num++;
        }else {
            System.out.println("任务"+r+"缓存中,要等待");
        }
    }
}

编写测试类,创建线程池对象,执行多个任务

public class MyTest {
    public static void main(String[] args) {
        //创建线程池类对象
         MyThreadPool myThreadPool = new MyThreadPool(2, 4, 20);
        //提交任务
        for (int i=0;i<10;i++){
             MyTask myTask = new MyTask(i);
             myThreadPool.submit(myTask);
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值