线程和线程池

多线程编程是业务开发中要用到的一项技术,尽管面临着一些挑战,但多线程开发也有着很多优点,例如资源利用率更好,程序设计在某些情况下更简单,程序响应更快等等。接下来主要说一下线程的几种创建方式以及线程池的应用。

1通过继承Thread的方式来创建线程


2通过实现Runnable接口来创建线程


这两种方式本质上其实是一样的,都是通过run()方法,深挖源码的话,第一种方式中Thread其实也是实现了Runnable接口,然后重写run()方法。然而这两种方式有一个不足,就是run()方法里并不能返回一个结果和抛出异常,所以就有了第三种创建线程的方式,通过Callable接口,并用FutureTask来接收返回的对象。

3通过Callable接口,并用FutureTask来接收返回的对象


和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。call()方法可以有返回值,call()方法可以声明抛出异常

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

接下来说线程池,线程池是管理线程的地方,在一个项目中,我们知道需要多次的创建线程和销毁线程,然而创建和销毁是及其耗费内存资源的,所以就有了线程池的需要,来帮我们管理调度线程,省下了很多的内存资源。

我们应该如何创建一个线程池那?Java中已经提供了创建线程池的一个类:Executor

而我们创建时,一般使用它的子类:ThreadPoolExecutor。但是从ThreadPoolExecutor到Executor并不是简单的实现或继承关系,两者中间有好多中间类。下面是各类之间的层级关系调用实现类图


线程池类图

我把这几个类用黑色线条画了起来,发现我们需要的ThreadPoolExecutor首先继承了抽象类AbstractExecutorService,然后AbstractExecutorService实现了ExecutorService接口,最ExecutorService接口又实现了Executor接口。

我们再来看一下ThreadPoolExecutor构造函数的参数


我们可以看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中可以容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是创建线程的线程工厂,最后一个handler,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务,具体的拒绝策略如下:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

再来看线程池的执行流程


线程池的执行流程

我们可以看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。

下面一段测试代码来说明这个线程池执行的流程


最后运行结果与执行流程是一样的,都是先判断核心线程,当核心线程最大后,在判断队列,当队列也满后,在进入非核心线程。

你可能感兴趣的:(线程和线程池)