博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java并发编程1-线程池
阅读量:4087 次
发布时间:2019-05-25

本文共 4149 字,大约阅读时间需要 13 分钟。

Executors框架简介

Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逸出。

Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

Executor类

Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。

ExecutorService类

ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。

ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当所有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

Executors类

Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。

  • public static ExecutorService newFixedThreadPool(int nThreads)

    • 创建固定数目线程的线程池。

    • newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程;

    • 任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子;

    • 和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器;

    • 从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同: 

  • public static ExecutorService newCachedThreadPool()

    • 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60秒钟未被使用的线程。

    • 缓存型池子通常用于执行一些生存期很短的异步型任务;

    • 注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。

    • 缺省timeout是60s。

  • public static ExecutorService newSingleThreadExecutor()

    • 创建一个单线程化的Executor。

    • 用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)

  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 

    • Timer类不管启动多少定时器,但它只会启动一条线程,当有多个定时任务时,就会产生延迟。如:我们要求一个任务每隔3S执行,且执行大约需要10S,第二个任务每隔5S执行,两个任务同时启动。若使用Timer我们会发现,第而个任务是在第一个任务执行结束后的5S才开始执行。这就是多任务的延时问题。

    • 若多个定时任务中有一个任务抛异常,那所有任务都无法执行。

    • Timer执行周期任务时依赖系统时间。若系统时间发生变化,那Timer执行结果可能也会发生变化。而ScheduledExecutorService基于时间的延迟,并非时间,因此不会由于系统时间的改变发生执行变化。 

Executor执行任务

在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable task) 方法来执行,并且返回一个 Future,是表示任务等待完成的 Future。

Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

Executor执行Runnable任务

通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上执行。

// 获取ExecutorService实例ExecutorService executorService = Executors.newCachedThreadPool();// 提交任务executorService.execute( new Runnable(){    public void run(){        //……    }} );

Executor执行Callable任务

 
  • Callable<String>表示call函数返回值为String类型;

  • 如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

ThreadPoolExecutor类

1、创建线程池:

 

(1) corePoolSize:线程池基本线程数

(2)workQueue:任务队列,FIFO先进先出。

(3) maximumPoolSize:线程池最大数量,如果线程数多于corePoolSize并且workQueue已满,则会创建新线程,但总数不会超过maximumPoolSize。

(4)ThreadFactory:用于自定义创建线程,方便给线程起名字。

(5)RejectedExecutionHandler:饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,则必须要采取一种策略来处理新提交的任务。

有如下四种:

①AbortPolicy:直接抛出异常

②CallerRunsPolicy:只用调用者所在线程来运行任务。

③DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务。

④DiscardPolicy:不处理,丢弃掉。

也可以自定义策略,如记录日志或者持久化任务。

(6)keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。TimeUnit:线程活动保持时间的单位。

2、当试图通过excute方法讲一个Runnable任务添加到线程池中时,按照如下顺序来处理:

(1)如果当前运行的线程数少于corePoolSize,即使线程池中有空闲线程,也会创建新线程来执行任务。

(2)如果运行线程数不少于corePoolSize,则将任务加入BlockingQueue。

(3)如果BlockingQueue队列已满,则创建新线程来执行任务。

(4)如果创建新线程会使线程总数超出maximumPoolSize,任务将被拒绝。

(5)当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池,这样,可以动态地调整线程池中线程的数量。

3、工作线程:

线程池创建线程时,会将线程封装成工作线程Worker,Worker执行完任务之后还会循环获取工作队列中的任务来执行。

4、线程池中的线程执行任务分为两种情况,如下。

(1)在execute(Runnable command)方法中创建一个线程时,会让这个线程执行当前任务。

(2)这个线程执行完当前任务后,会反复从BlockingQueue获取任务来执行。

调用线程池的prestartAllCoreThread()方法可以提前创建并启动所有基本线程。

5、向线程池提交任务:

(1)execute()方法提交任务没有返回值

(2)submit()方法会返回一个future类型的对象

6、关闭线程池

(1)shutdown方法会等当前任务执行完毕

(2)shutdownNow方法会中断正在执行任务的线程

转载地址:http://ldbii.baihongyu.com/

你可能感兴趣的文章
似乎写个ROS功能包并不难,你会订阅话题发布话题,加点逻辑处理,就可以写一些基础的ROS功能包了。
查看>>
if __name__ == ‘__main__‘:就是Python里的main函数,脚本从这里开始执行,如果没有main函数则从上到下顺序执行。
查看>>
PX4官方用户和开发手册的首页面是会给你选择英文和中文的
查看>>
网络协议栈我是不是可以这么理解,就是把你要发送的数据自动处理成TCPIP格式的消息发出去,这种底层的转换不需要你弄了。
查看>>
除了LwIP还有uIP
查看>>
《跟工程师学嵌入式开发》这本书最后的终极项目我反而觉得有说头
查看>>
博士的申请考核制
查看>>
那些硬件的初始化函数主要是在做些上什么?
查看>>
MAVLink学习之路05_MAVLink应用编程接口分析(也有讲STM32下的收发函数)
查看>>
找到了中文版的mavlink手册
查看>>
浅谈飞控开发的仿真功能
查看>>
我觉得在室内弄无人机开发装个防撞机架还是很有必要的,TBUS就做得很好。
查看>>
serial也是见到很多次了,似乎它就是一种串行通信协议
查看>>
TBUS的一些信息
查看>>
PX4+激光雷达在gazebo中仿真实现(古月居)
查看>>
专业和业余的区别就在于你在基础在基本功打磨练习花的时间
查看>>
通过mavlink实现自主航线的过程笔记
查看>>
Ardupilot飞控Mavlink代码学习
查看>>
这些网站有一些嵌入式面试题合集
查看>>
我觉得刷题是有必要的,不然小心实际被问的时候懵逼,我觉得你需要刷个50份面试题。跟考研数学疯狂刷卷子一样!
查看>>