理解线程池和Executor框架

一、什么是线程池:

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

 

二、为什么要使用线程池:

因为创建和销毁线程都是需要时间的,特别是需要创建大量线程的时候,时间和资源的消耗是不可忽略的,而合理的使用线程池中已经创建的线程,可以减少创建和销毁线程而花费的时间和资源。

 

三、线程池的优点:

(1)降低资源消耗:通过线程的重用可以降低创建和销毁线程花费的时间和资源;

(2)提高响应速度:任务到达时,因为利用线程池中已经创建好的线程,可以不用等待线程创建而直接执行任务;

(3)提高线程的可管理性:线程池允许我们开启多个任务而不用为每个线程设置属性(便于管理);线程池根据当前在系统中运行的进程来优化线程时间片(调优);线程池可以限制创建线程的数量,如果无限制的创建线程,不仅会消耗资源,还会降低系统的稳定性;

 

四、线程池的工作原理

 

 

 

 

五、在项目中线程池使用

1、线程池的创建和线程池的基本设置:(6个参数)

ThreadPoolExecutor tpe = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnalbleTaskQueue, handler);

corePoolSize线程池的大小。如果调用了prestartAllCoreThread()方法,那么线程池会提前创建并启动所有基本线程。

maximumPoolSize线程池的大小。

keepAliveTime闲置时间线空闲后,线程存活时间。如果任务多,任务周期短,可以调大keepAliveTime,提高线程利用率。

timeUnit闲置时间的时间单位。有天(DAYS)、小时(HOURS)、分(MINUTES)、秒(SECONDS)、毫秒(MILLISECONDS)、微秒(MICROSECONDS)、纳秒(NANOSECONDS)

runnalbleTaskQueue阻塞队列方4种)

ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue

静态工厂方法Executors.newFixedThreadPool(  )使用了LinkedBlockingQueue;

静态工厂方法Executors.newCachedThreadPool(  )使用了SynchronousQueue;

handler饱和策论。(4种

线程池满了的时候,任务无法得到处理,这时候需要饱和策略来处理无法完成的任务,饱和策略中有4种处理策略:

AbortPolicy:这是默认的策略,直接抛出异常;

CallerRunsPolicy:只是用调用者所在线程来运行任务;

DiscardOldestPolicy:丢弃队列中最老的任务,并执行当前任务;

DiscardPolicy:不处理,直接把当前任务丢弃;

当然也可以自定义饱和处理策略,需要实现RejectedExecutionHandler接口,比如记录日志或者持久化不能存储的任务等。

2.向线程池提交任务

提交任务的两种方法:execute()和submit()

(1)execute用于提交没有返回值的任务,所以无法判断任务是否被线程执行过。

package com.company;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class Main {

    public static void main(String[] args) {
        //线程池的创建
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,10 ,
                100 ,MILLISECONDS,new ArrayBlockingQueue<Runnable>(5));
        //提交线程池任务
        threadPoolExecutor.execute(new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread 1 execute work" );
            }
        }));
        //停止线程池
        threadPoolExecutor.shutdown();
    }
}

 

(2)submit():用于提交需要返回值的对象。

用于提交需要返回值的对象。返回一个future对象,可以通过future对象判断任务是否被线程执行;可以通过future对象的get()方法获取返回的值,get()方法会阻塞当前线程直到future对象被返回,也可以调用get(long timeout, TimeUnit unit)来实现由等待时间的获取返回值,如果超时仍没有返回值,那么立刻返回,这时候任务可能没有执行完成。

package com.company;

import java.util.concurrent.*;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class Main2 {
    public static void main(String[] args) throws ExecutionException {
        //线程池的创建
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10,
                100, MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
        //添加任务
        Future future= threadPoolExecutor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                String a ="return call";
                return a;
            }
        });
        try {
            System.out.println(future.get());//future.get()调用call()方法的返回结果
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            threadPoolExecutor.shutdown();
        }


    }
}

3、关闭线程池

可以通过shutdown或者shutDownNow来关闭线程池。

原理:遍历线程池中的线程,逐个调用线程的interrupt()方法来中断线程,所以不响应中断的线程可能永远无法终止

(1)shutDown:把线程池的状态设置为SHUTDOWN,然后中断所有没有正在执行任务的线程,而已经在执行任务的线程继续执行直到任务执行完毕;

(2)shutDownNow:把当前线程池状态设为STOP,尝试停止所有的正在执行或者暂停的线程,并返回等待执行的任务的列表;

在调用了shutDown或者shutDownNow后,调用isShutDown()返回true;当所有任务都关闭后,调用isTerminaed()方法返回true。(注意关闭线程池和所有线程关闭是不同的

一、什么是Executor框架?

我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行

机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。

 

二、Executor框架结构图解

1、Executor框架包括3大部分:

(1)任务:也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;

(2)任务的执行:也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。

(3)异步计算的结果:包括Future接口及实现了Future接口的FutureTask类。

三、Executor框架成员:

ThreadPoolExecutor实现类、ScheduledThreadPoolExecutor实现类、Future接口、Runnable和Callable接口、Executors工厂类

 

 

1、ThreadPoolExecutor:

(1)创建:

ThreadPoolExecutor tpe = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue);

(2)ThreadPoolExecutor的子类(3个):

ThreadPoolExecutor通过Executors工具类来创建ThreadPoolExecutor的子类FixedThreadPoolSingleThreadExecutorCachedThreadPool,这些子类继承ThreadPoolExecutor,并且其中的一些参数已经被配置好

//FixedThreadPoll
ExecutorService ftp = Executors.newFixedThreadPool(int threadNums);
ExecutorService ftp = Executors.newFixedThreadPool(int threadNums, ThreadFactory threadFactory);
//SingleThreadExecutor
ExecutorService ste = Executors.newSingleThreadExecutor();
ExecutorService ste = Executors.newSingleThradPool(ThreadFactory threadFactory);
//CachedThreadPool
ExecutorService ctp = Executors.newCachedThreadPool();
ExecutorService ctp = Executors.newCachedThreadPool(ThreadFactory threadFactory);

(3)子类详解:

FixedThreadPool:

应用场景:FixedThreadPool是线程数量固定的线程池,适用于为了满足资源管理的需求,而需要适当限制当前线程数量的情景,适用于负载比较重的服务器。

可以看出它的实现就是把线程池最大线程数量maxmumPoolSize和核心线程池的数量corePoolSize设置为相等,并且使用LinkedBlockingQueue作为阻塞队列,那么首先可以知道线程池的线程数量最多就是nThread,只会在核心线程池阶段创建,此外,因为LinkedBlockingQueue是无限的双向队列,因此当任务不能立刻执行时,都会添加到阻塞队列中,因此可以得到FixedThreadPool的工作流程大致如下:

当前核心线程池总线程数量小于corePoolSize,那么创建线程并执行任务;
如果当前线程数量等于corePoolSize,那么把 任务添加到阻塞队列中;
如果线程池中的线程执行完任务,那么获取阻塞队列中的任务并执行;


*注意:因为阻塞队列是无限的双向队列,因此如果没有调用shutDownNow()或者shutDown()方法,线程池是不会拒绝任务的,如果线程池中的线程一直被占有,FixedThreadPool是不会拒绝任务的。

因为使用的是LinkedBlockingQueue,因此maximumPoolSize,keepAliveTime都是无效的,因为阻塞队列是无限的,因此线程数量肯定小于等于corePoolSize,因此keepAliveTime是无效的

 

SingleThreadExecutor

应用场景:SingleThreadExecutor是只有一个线程的线程池,常用于需要让线程顺序执行,并且在任意时间,只能有一个任务被执行,而不能有多个线程同时执行的场景。

因为阻塞队列使用的是LinkedBlockingQueue,因此和FixedThreadPool一样,maximumPoolSize,keepAliveTime都是无效的。corePoolSize为1,因此最多只能创建一个线程,SingleThreadPool的工作流程大概如下:

当前核心线程池总线程数量小于corePoolSize(1),那么创建线程并执行任务;
如果当前线程数量等于corePoolSize,那么把 任务添加到阻塞队列中;
如果线程池中的线程执行完任务,那么获取阻塞队列中的任务并执行;


CachedThreadPool

应用场景:CachedThreadPool适用于执行很多短期异步任务的小程序,或者是负载较轻的服务器。

CachedThreadPool使用SynchronizedQueue作为阻塞队列,SynchronizedQueue是不存储元素的阻塞队列,实现“一对一的交付”,也就是说,每次向队列中put一个任务必须等有线程来take这个任务,否则就会一直阻塞该任务,如果一个线程要take一个任务就要一直阻塞知道有任务被put进阻塞队列。

因为CachedThreadPool的maximumPoolSize为Integer.MUX_VALUE,因此CachedThreadPool是无界的线程池,也就是说可以一直不断的创建线程。corePoolSize为0 ,因此在CachedThreadPool中直接通过阻塞队列来进行任务的提交。

CachedThreadPool的工作流程大概如下:

首先执行SynchronizedQueue.offer(  )把任务提交给阻塞队列,如果这时候正好在线程池中有空闲的线程执行SynchronizedQueue.poll( ),那么offer操作和poll操作配对,线程执行任务;
如果执行SynchronizedQueue.offer(  )把任务提交给阻塞队列时maximumPoolSize=0.或者没有空闲线程来执行SynchronizedQueue.poll( ),那么步骤1失败,那么创建一个新线程来执行任务;
如果当前线程执行完任务则循环从阻塞队列中获取任务,如果当前队列中没有提交(offer)任务,那么线程等待keepAliveTime时间,在CacheThreadPool中为60秒,在keepAliveTime时间内如果有任务提交则获取并执行任务,如果没有则销毁线程,因此最后如果一直没有任务提交了,线程池中的线程数量最终为0。
*注意:因为maximumPoolSize=Integer.MAX_VALUE,因此可以不断的创建新线程,这样可能会CPU和内存资源耗尽。

 

2、ScheduledThreadPoolExecutor

可以利用Executors工厂类来创建两种ScheduledThreadPoolExecutor:ScheduledThreadPoolExecutor和SingleThreadScheduledExecutor.

 

3、Future接口/FutureTask实现类:

FutureTask的Future就源自于它的异步工作机制,如果我们在主线程中直接写一个函数来执行任务,这是同步的任务,也就是说必须要等这个函数返回以后我们才能继续做接下的事情,但是如果这个函数返回的结果对接下来的任务并没有意义,那么我们等在这里是很浪费时间的,而FutureTask就提供了这么一个异步的返回结果的机制,当执行一个FutureTask的时候,我们可以接着做别的任务,在将来的某个时间,FutureTask任务完成后会返回FutureTask对象来包装返回的结果,只要调用这个对象的get()方法即可获取返回值。

(1) FutureTask的启动:

  ① 由调用线程直接执行:在调用线程中执行futureTask.run()方法;

  ② 由调用线程直接执行:在调用线程中执行futureTask.run()方法;

(2) FutureTask执行完后结果的获取  :futureTask.get( )

(3) futureTask被取消:futureTask.cancel( )

 


————————————————
版权声明:本文为CSDN博主「tongdanping」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tongdanping/article/details/79625109

posted @ 2019-12-29 20:03  旺旺a  阅读(735)  评论(0编辑  收藏  举报