java多线程学习笔记(五)

来源:互联网 发布:中兴b600刷成网络盒子 编辑:程序博客网 时间:2024/06/06 01:35

将笔者学习多线程的一些笔记发出来,方便读者学习,以前总是索取,不知道回报,希望我的一些经历经验,会帮助到正在学习的你,也希望大家能一起讨论,共同学习。(如果有问题,可以留言,笔者会尽快处理,如果有出错,希望您能指出,大家共同学习)

10、 线程池

1.1         为什么需要线程池?

首先,虽然与进程相比,线程是一种轻量级的工具,但是其创建和关闭依然需要花费时间。如果每一个小的任务都需要创建一个线程,很有可能出现创建和销毁线程所占用的时间大于该线程真实的工作时间。

其次,线程本身也是要占用内存工具的,大量的线程会枪战宝贵的内存资源,如果处理不当,可能到导致Out of Memory异常,即使没有,大量的线程回收也会给GC带来很大的压力。

1.2         什么是线程池?

为了避免系统频繁的创建和销毁线程,我们可以让创建的线程进行服用,当系统需要使用线程时,并不是创建一个新的线程,而是从线程池中获得一个可用的线程,反之,当需要关闭连接时,并不是真的把线程关闭,而是将这个连接归还给线程池。通过这种凡是可以节约不少创建和销毁对象的时间。

1.3         JDK对线程池的支持

JDK提供了一套Executor框架,可以帮助开发人员有效的进行线程控制,其本质就是一个线程池。

Executots类:线程池的创建工厂,可以创建一个拥有特定功能的线程池。

ThreadPoolExecutor类:实现了Executor接口,可以通过这个接口,任何Runnable对象都可以被ThreadPoolExecutor线程池调度。

几种重要的线程池介绍:

Ø  固定线程数量线程池:Executors.newFixedThreadPoll()

Ø  一个线程的线程池:Executors.newSingleThreadExecutor()

Ø  根据实际情况调整的线程池:Executors.newCachedThreadPool();

Ø  一个调度任务的线程池:Executors.newSingleThreadScheduledExecutor()

Ø  调度任务的线程池:Executors.newScheduledThreadPool()

比较难理解的就是newScheduledThreadPool(),它返回一个ScheduledExecutorSerivce对象,可以根据实际需要对线程进行调度。ScheduledExecutorService并不一定会立刻的执行任务,它是起到了计划任务的作用,会在指定的时间对任务进行调度。

它有三个主要调度方法:

Ø  schedule():执行调度任务

Ø  scheduleAtFixedRate():每隔一定时间执行一次调度任务

Ø  scheduleWithFixedDelay():每隔任务执行完,然后间隔一定时间,执行一次调度任务

如果遇到异常,那么后续的所有自认为都会停止调度,因此,必须保证异常被即使处理,为周期性任务的稳定调度提供条件。

1.4         探究线程池的内部实现

对于几个常用的比较核心的线程池,无论是newFixedThreadPoolExecutor()方法,newSingleThreadExecutor()方法还是newCachedThreadPoolExecutor()方法,其实其内部实现都是使用了ThreadPoolExecutor类,只是对其进行了封装。

public static ExecutorService newFixedThreadPool(int nThreads) {        return new ThreadPoolExecutor(nThreads, nThreads,                                      0L, TimeUnit.MILLISECONDS,                                      new LinkedBlockingQueue<Runnable>());    }public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {        return new FinalizableDelegatedExecutorService            (new ThreadPoolExecutor(1, 1,                                    0L, TimeUnit.MILLISECONDS,                                    new LinkedBlockingQueue<Runnable>(),                                    threadFactory));    }public static ExecutorService newCachedThreadPool() {        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,                                      60L, TimeUnit.SECONDS,                                      new SynchronousQueue<Runnable>());    }


我们来看一下ThreadPoolExecutor最重要的构造方法:

public ThreadPoolExecutor(int corePoolSize,

                             int maximumPoolSize,

                             long keepAliveTime,

                             TimeUnit unit,

                              BlockingQueue<Runnable>workQueue,

                             ThreadFactory threadFactory,

                             RejectedExecutionHandler handler)

参数含义:

Ø    corePoolSize:指定了线程池中线程的数量。

Ø    maximumPoolSize:指定了线程池中的最大线程数量。

Ø    keepAliveTime当线程池线程数量超过corePoolSize是,多余的空闲线程的存活时间。

Ø    unit:keepAliveTime的单位

Ø    workQueue:任务队列,被提交但是尚未被执行的任务。

Ø    threadFactory:线程工厂,用于创建线程,一般用默认的即可。

Ø    handler:拒绝策略,单任务太多来不及处理,如何拒绝任务。

workQueue指被提交但是尚未被执行的任务队列,它是一个Blockingqueue接口的对象,仅用于存放Runnable对象,根据队列功能分类,在ThreadPoolExecutor的构造方法中可以使用的几种BlockingQueue:

Ø    SynchronousQueue:直接提交队列,它没有容量,每一个插入操作都要等待一个相应的删除操作,反之,每一个删除操作都要等待相应的插入操作。使用此队列,提交的任务不会被真是的保存而总是将新任务提交给线程执行,如果没有空闲的进程,则尝试创建新的线程,如果已经达到最大值,则执行拒绝策略。

Ø    ArrayBlockingQueue:有界的任务队列,使用此队列,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则会优先创建新的线程,若大于corePoolSize,则会将新的任务加入等待队列,若等待队列满了,则在线程总数不大于maximumPoolSize的前提下,创建新的进程执行任务,若大于maximumPoolSize,则执行拒绝策略。

Ø    LinkedBlockingQueue:无界任务队列,此队列除非用尽系统资源,否则无界的队列不存在任务入队失败的情况,当系统的线程数小于corePoolSize时,线程池会生出新的线程,但是达到corePoolSize后,就不会继续增加,若后续仍有新的任务加入,而没有空间的线程,则等待。

Ø    PriorityBlockingQueue:优先任务队列,此队列是带有执行优先级的队列,它是一个特殊的无界队列。此队列可以根据任务自身的优先级顺序先后执行,在确保系统性能的同事,可能有效的保证质量。

1.5        拒绝策略

ThreadPoolExecutor最后的一个参数指定了拒绝策略,也就是当任务数量超过系统时间承载的能力时的处理逻辑。

JDK内置提供了四种拒绝策略:

Ø    AbortPolicy策略:该策略会直接抛出异常,阻止系统正常运行。

Ø    CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务,显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

Ø    DiscardOledestPolicy策略:该策略将丢弃一个最好的请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

Ø    DiscardPolicy策略:该策略默默的丢弃无法处理的任务,不与任何处理。

以上的任务拒绝策略都实现了RejectedExecutionHandler接口,若以上的拒绝策略扔无法满足实际应用的需要,完全可以自己扩展RejectedExecutionHandler接口。

1.6         自定义线程创建:ThreadFactory

既然线程池可以初始化为含有固定数量的线程,那么这些线程是怎么创建的呢?答案就是ThreadFactory。

ThreadFactory是一个接口,它只有一个方法,用来创建线程:

Thread newThread(Runnable r)。

当线程池需要新建线程时,就会调用这个方法。使用自定义线程池可以让我们更加自由的设置池子中所有线程的状态。

package com.easyyang.structtest;import java.util.concurrent.ExecutorService;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.ThreadFactory;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class ThreadFactoryTest implements Runnable{public static void main(String[] args) throws InterruptedException {ThreadFactoryTest task = new ThreadFactoryTest();ExecutorService es = new ThreadPoolExecutor(5, 5, 0l, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {public Thread newThread(Runnable r) {Thread t = new Thread(r);t.setDaemon(true);System.out.println("create " + t);return t;}});for (int i = 0; i < 5; i++) {es.submit(task);}Thread.sleep(2000);}public void run() {System.out.println("task run");}}

1.7        线程池扩展

虽然JDK已经帮我们实现了高性能的线程池,但是如果我们需要对线程池做一些扩展,需要怎么办呢。好消息是ThreadPoolExecutor也是一个可以扩展的线程池,它提供了先三个接口对线程池进行控制。

Ø    beforeExecutor():线程运行前

Ø    afterExecute():线程运行后

Ø    terminated():整个线程池退出后

1.8         线程池的大小

线程池的大小对系统的性能有一定的影响,过大过小的线程数都无法发挥出最优的系统性能,但是线程池大小的确定也不需要做的非常精细。

一般来说,确定线程池的小打需要考虑CPU的数量、内存的大小等因素,可以按照下面的公式进行估算:

         Ncpu = CPU的数量

Ucpu = 目标CPU的使用率

W/C = 等待时间与计算时间的比率

为保持处理器达到期望的使用率,最优的池的大小等于:

Nthreads = Ncpu * Ucpu *(1 + W/C)

1.9         Fork/Join框架

在处理大数据的时候,可以采用分而治之的思想。即如果想要处理1000个数据,但是并不具备处理1000个数据的能力,那么可以只处理其中的10个,然后分100次处理,最后将结果合成。

在JDK中给出了一个ForkJoinPool线程池,对于fork()方法,并不着急开线程,而是提交给ForkJoinPool线程池进行处理,以节省系统资源。

来看一下ForkJoinPool中一个重要的接口:

public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {        if (task == null)            throw new NullPointerException();        externalPush(task);        return task;    }

你可以向ForkJoinPool中提交一个ForkJoinTask任务,ForkJoinTask有两个重要的子类,RecursiveAction和RecursiveTask,分别表示没有返回值的任务和可以携带返回值的任务。代码示例:

package com.easyyang.structtest;import java.util.ArrayList;import java.util.concurrent.ExecutionException;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.ForkJoinTask;import java.util.concurrent.RecursiveTask;public class ForkJoinPoolTest extends RecursiveTask<Long>{private static final long serialVersionUID = 4080304263404525537L;private static final int THRESHOLD = 10000;private long start;private long end;public ForkJoinPoolTest(long start, long end) {this.start = start;this.end = end;}@Overrideprotected Long compute() {long sum = 0;boolean canCompute = (end - start) < THRESHOLD;if(canCompute){for (long i = start; i < end; i++) {sum += i;}}else{// 分成100个小人物long step = (start + end)/100;ArrayList<ForkJoinPoolTest> subtasks = new ArrayList<ForkJoinPoolTest>();long pos = start;for (int i = 0; i < 100; i++) {long lastOne = pos + step;if(lastOne > end){lastOne = end;}ForkJoinPoolTest subtask = new ForkJoinPoolTest(pos, lastOne);pos += step + 1;subtasks.add(subtask);subtask.fork();}for (ForkJoinPoolTest forkJoinPoolTest : subtasks) {sum+= forkJoinPoolTest.join();}}return sum;}public static void main(String[] args) {ForkJoinPool pool = new ForkJoinPool();ForkJoinPoolTest task = new ForkJoinPoolTest(0, 200000l);ForkJoinTask<Long> result = pool.submit(task);try {long res = result.get();System.out.println("sum = " + res);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}




原创粉丝点击