Java高级技术第五章——高并发之线程池

前言

前言点击此处查看:
http://blog.csdn.net/wang7807564/article/details/79113195

线程池

线程的开启和回收是要消耗系统性能的,对于大量使用线程的场景,使用线程池来进行管理,实现单个线程的复用,提高并发效率。

Callable

对Runnable进行了扩展,相对于Runnable接口,Callable的调用是可以有返回值的。

Future

Future接口是一个泛型的接口,该接口中定义有些一些方法,这些方法分别是:
1. boolean cancel(boolean mayInterruptIfRunning)
尝试去取消正在执行的任务。
2. V get()
这个方法是阻塞执行的,由于Future是带有返回结果的,所以,该方法一直阻塞,知道返回执行结果。
3. V get(long timeout, TimeUnit unit)
同上,可以设置超时时间。
4. boolean isCancelled()
如果在正常完成直接被取消则返回真。
5. boolean isDone()
如果任务完成,返回真。
使用Future接口的一个例子

FutureTask<Integer> task = new FutureTask<>(Runnable接口);
new Thread(task).start();

这里面的参数task,就可以是实现Futrue接口的对象。

Executor

Executor是一个接口,这个接口里面有一个名为execute的抽象函数,该函数的函数原型是是这样的:

public void execute(Runnable command)

重写该方法就可以实现应用Executor接口的功能了。

ExecutorService:

Excutor的继承树是这样的:
继承树
ExcutorService接口继承了Excutor接口,此外,该接口还重新定义了一些其他的方法,比较典型的是submit()方法,该方法能够重载多种参数,其函数原型是:

<T> Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
<T> Future<T> submit(Runnable task, T result)

也就是说,该接口定义的submit()方法是有返回值的,这个返回值的类型是一个Futrue类型的对象。
我们不去具体地实现该接口来实现线程池,而是使用Executors工厂类来给我们返回不同的实现了该接口的线程池实体,而我们只需要去用submit()或者excutor()方法来提交事务就可以了,在我们用工厂类进行实例化的时候,有些参数是可以传递的,但是大多数中间过程,已经由这个工厂类来完成了。
ExcutorService接口的一些常用的方法:
1. execute(Runnable)
接收一个Runnable实例,并且异步地执行
2. submit(Runnable)
submit(Runnable)和execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕等。
3. submit(Callable)
submit(Callable)和submit(Runnable)类似,也会返回一个Future对象,但是除此之外,submit(Callable)接收的是一个Callable的实现,Callable接口中的call()方法有一个返回值,可以返回任务的执行结果,而Runnable接口中的run()方法是void修饰的,没有返回值。
  也就是说,以上的两个方法,就是用来提交给实现ExcutorService接口的具体线程池中的执行事务,通过以上两种方法,可以让线程池来执行我们需要执行的任务。
4. invokeAny(…)
接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务的执行结果。这个方法也无法保证返回的是哪个任务的执行结果,反正是其中的某一个。
5. invokeAll(…)
invokeAll(…)与 invokeAny(…)类似也是接收一个Callable集合,但是前者执行之后会返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。
6. shutdown()
对当前线程池中已经执行的线程下达关闭命令,同时拒绝在线程池中添加新的任务,如果使用submit()添加新的任务,会产生RejectedExecutionException的异常。
7. isShutdown()
如果这个excutor()已经被下达关闭指令,则返回真,但是具体是否真的被关闭了,需要用isTerminated()方法来判断。
8. isTerminated()
如果所有任务已经完成后续的关闭工作则返回真。

Executors:

前面已经说过了,Executors类是一个静态工厂类,可以返回的类型有:
* ExecutorService
* ScheduledExecutorService
* ThreadFactory
* Callable
通过该类的工厂方法可以返回具体功能的线程池对象,这些对象都是实现了ExcutorService接口的,我们想要提交事务,只需要调用该接口的submit()或excutor()方法就可以了,使用十分便利。其主要的工厂方法有:

static ThreadFactory defaultThreadFactory()
static ExecutorService newCachedThreadPool()
static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
static ExecutorService newFixedThreadPool(int nThreads)
static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
static ExecutorService newSingleThreadExecutor()
static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
static ScheduledExecutorService newSingleThreadScheduledExecutor()
static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
static ThreadFactory privilegedThreadFactory()
static ExecutorService unconfigurableExecutorService(ExecutorService executor)
static ScheduledExecutorService unconfigurableScheduledExecutorService
(ScheduledExecutorService executor)

比较常用的线程池的5种创建方式:
1. SingleThread Executor
只有一个线程的线程池,因此所有提交的任务是顺序执行,代码:

Executors.newSingleThreadExecutor()
  1. Cached Thread Pool
    线程池里有很多线程需要同时执行,旧的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,代码:
Executors.newCachedThreadPool()
  1. FixedThreadPool
    拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,代码:
Executors.newFixedThreadPool(4)

在构造函数中的参数4是线程池的大小,一般设置与cpu的数量保持一致,获取cpu的数量:

int cpuNums = Runtime.getRuntime().availableProcessors();
  1. ScheduledThreadPool
    用来调度即将执行的任务的线程池,代码:
Executors.newScheduledThreadPool()
  1. Single Thread Scheduled Pool
    只有一个线程,常用来调度执行将来的任务,代码:
Executors.newSingleThreadScheduledExecutor()

线程池的一些补充

ScheduledThreadPool

该类型的线程池返回的对象是用ScheduledExecutorService 接口的变量来接受的,例如:

ScheduledExecutorService service = Executors.newScheduledThreadPool(4);

该接口继承自ExcutorService接口,新增了scheduleAtFixedRate()方法,该方法的定义与Timer类的同名方法类似的:

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

command - 执行的任务
initialDelay - 延迟多久去首次执行
period - 两次执行的时间间隔
unit - 上面两个参数的计时单位,例如TimeUnit.MILLISECONDS

newWorkStealingPool

该线程池实际上是一个精灵线程(deamon,守护线程、后台线程)池,如果主线程不阻塞的话,精灵线程池中定义的线程是不会执行的,只有当主线程阻塞时候,才会触发精灵线程池中的全部线程执行。
具体使用方法:

ExecutorService service = Executors.newWorkStealingPool();
service.execute(...);//提交任务
System.in.read(); 
//主线程在这里阻塞,会触发上面精灵线程池中的线程,执行在Runnable接口中实现的任务。

ForkJoinPool

Jdk7新增了并发框架fork/join框架,在这种框架下,ForkJoinTask代表一个需要执行的任务,真正执行这些任务的线程是放在一个线程池(ForkJoinPool)里面。ForkJoinPool是一个可以执行ForkJoinTask的ExcuteService,与ExcuteService不同的是它采用了work-stealing模式:

所有在池中的线程尝试去执行其他线程创建的子任务,这样就很少有线程处于空闲状态,非常高效。

和ExecutorService一样,ForkJoinTask可以调用shutdown()和 shutdownNow()来终止线程,会先设置每个线程的任务状态为CANCELLED,然后调用Thread的interrupt方法来终止每个线程。

自定义线程池

Java线程池Executors中常用的工厂方法newCachedThreadPool(),newFixedThreadPool(),newSingleThreadExecutor(),newScheduledThreadPool()在底层都是调用了ThreadPoolExecutor()这个构造方法来实例化一个线程池对象的。
在某些情况下,Executors实例化的线程池可能并不能满足我们的需要,我们需要自定义线程池。那么,我们就要使用到ThreadPoolExecutor()来创建一个线程池。它的构造函数是:

public ThreadPoolExecutor(int corePoolSize,//核心线程数,线程池初始化创建的线程数量
           int maximumPoolSize,//线程池中能创建的最大线程数
           long keepAliveTime,//线程存活时长
           TimeUnit unit,//线程存货时常的时间单位
           BlockingQueue<Runnable> workQueue,//一个待执行任务构成的阻塞队列
           ThreadFactory threadFactory//拒绝策略
           ) {...}

我们看到BlockingQueue,就应该想到,在上面说到过BlockingQueue可以分为两种,有界队列和无界队列。
对于ThreadPoolExecutor()的第五个参数,有界队列和无界队列所带来的效果还不一样。
1. 对于有界队列
若有新的任务加入线程池中,如果线程池当前实际线程数量小于corePoolSize核心线程数的时候,则优先创建线程。若大于corePoolSize时,则会将多余的任务存放在队列中。若队列已满,且请求线程小于maximumPoolSize的情况下,则会创建新的线程。若队列已满,且最请求线程大于maximumPoolSize的情况下,则执行拒绝策略,或其他自定义方式。
2. 对于无界队列
与有界队列相比,除非系统资源耗尽,否则无界队列不存在任务入队失败的情况,若系统的线程数小于corePoolSize时,则新建线程执行corePoolSize。当达到corePoolSize后,则把多余的任务放入队列中等待执行。无界队列会保持快速增长,直到耗尽系统内存为之,其中maximumPoolSize并无真实用处。
我们可以看下Executors中创建CachedThreadPool的源代码:

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

对于最后一个拒绝策略参数,通常解决方式是:
1. AbortPolicy:直接抛出异常,系统继续工作,默认策略。
2. CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中执行,运行当前被丢弃的任务。
3. DiscardOrderstPolicy:丢弃最老的请求,尝试再次提交当前任务。
4. 丢弃无法处理的任务,不给于任何处理。
如果需要自定义策略,需要实现RejectedExecutionHandler接口。
该接口需要实现一个方法:

void rejectedExecution(Runnable r, ThreadPoolExecutor executor);

猜你喜欢

转载自blog.csdn.net/wang7807564/article/details/80048648