Java并发基础复习笔记:线程池

目录

一、线程池介绍

二、任务

1. 常用的任务相关接口与类

2. FutureTask的实现

三、线程池的使用

1. 概述

2. 创建线程池


一、线程池介绍

如果不使用线程池,编程人员则需要为每一个不在本线程执行的任务新建一个线程,线程的创建开销很大,且如果线程数量远大于cpu核数则频繁的上下文切换会导致程序执行效率大幅降低。

线程池维护了一个任务队列和若干线程,它自动地将任务分配给池中的线程执行,编程人员只需要将新的任务提交给线程池即可。线程池会按照预先设定好的参数决定维护几个线程,什么时候新建线程,什么时候销毁线程。

一般来说,为线程池提供一个Rannable,就会有一个线程调用run方法。当run方法退出时,这个线程不会死亡,而是留在池中为下一个请求提供服务。


二、任务

1. 常用的任务相关接口与类

线程池会维护一个任务队列,调用线程池的submit方法可以将Runnable或者Callable提交给任务队列,submit会立即返回一个Future对象,通过这个Future对象可以获取结果或取消任务。

  1. Runnable接口:Runnable封装一个异步运行的任务,像一个没有参数和返回值的异步方法,只有一个run方法
  2. Callable接口:与Runnable接口类似,但是有返回值,只有一个call方法
  3. Future接口:Future保存异步计算的结果
  4. RunnableRuture接口:继承了Runnable接口后Future接口
  5. FutureTask类:实现了RunnableFuture接口,可以用它来运行Callable任务
/* Runnable接口 */

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
/* Callabel接口 */

@FunctionalInterface
public interface Callable {
    V call() throws Exception;
}
public interface Future {
    /* 如果任务还没执行则取消,如果正在执行且mayInterruptIfRunning为真则中断线程 */
    boolean cancel(boolean mayInterruptIfRunning);
    /* 任务是否被取消 */
    boolean isCancelled();
    /* 任务是否执行完毕 */
    boolean isDone();
    /* 阻塞直到任务执行结束,获取结果 */
    V get() throws InterruptedException, ExecutionException;
    /* 同上,但超时抛出TimeoutException异常 */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

可以通过FutureTask类执行Callable,示例代码如下:

Callable task = ...;
var futureTask = new FutureTask(task);
var t = new Thread(futureTask);  // 因为FutureTask实现了Runnable接口
t.start();
. . .
Integer result = futureTask.get();

2. FutureTask的实现

主要的数据成员:

  • private volatile int state;  保存自己的状态,构造函数中初始为NEW;状态分别为:NEW、COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTING、INTERRUPTED,值从0到6;get方法、isCancelled方法就是通过这个状态来判断是否运行完毕(如果没运行完就阻塞等待,如果运行完就返回outcome)或是否取消(如果state的值大于CANCELLED则判定为取消)
  • private Callable callable;  在构造函数中初始化(通过传入的Callable或Runnable和V),在run方法中调用其call方法
  • private Object outcome;  在run方法中调用callable.call(),如果有异常抛出则赋值为异常,如果正常运行则赋值为其返回值
  • private volatile Thread runner;  运行这个FutureTask的线程,在run方法中通过Thread.currentThread()初始化

run() 方法实现如下:

public void run() {
    if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                    null, Thread.currentThread()))  // 初始化runner,失败则返回
        return;
    try {
        Callable c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;                    // 是否顺利执行(无异常)
            try {
                result = c.call();          // 执行任务
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);           // 产生异常,将outcome设置为异常,并更新状态
            }
            if (ran)
                set(result);                // 没有异常,将outcome设置为结果,并更新状态
        }
    } finally {
        // runner must be non-null until state is settled to
        // prevent concurrent calls to run()
        runner = null;
        // state must be re-read after nulling runner to prevent
        // leaked interrupts
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

三、线程池的使用

1. 概述

JDK提供了一套Executor框架来方便开发人员使用线程池,它的核心成员如下:

Java并发基础复习笔记:线程池_第1张图片

以上成员均在java.util.concurrent包中,是JDK并发包的核心类。其中ThreadPoolExector表示一个线程池,Executors类是一个工厂类,通过newXXX静态方法可以返回不同设定的ThreadPoolExecutor或ScheduledThreadPoolExecutor线程池对象。

Executors类的newXXX方法返回ExecutorService或ScheduledExecutorService对象(分别由ThreadPoolExecutor和ScheduledThreadPoolExecutor实现),开发人员调用execute方法提交Runnable任务,调用submit方法提交Callable任务并通过返回的Future对象获取返回值,调用shutdown方法关闭服务(线程池处理完已提交任务后,不再接受新任务),调用schedule方法延迟执行或定期执行任务。

2. 创建线程池

我们一般通过调用Executors类的静态方法创建线程池,而不是直接去new ThreadPoolExecutor或new ScheduledThreadPoolExecutor,ThreadPoolExecutor的构造方法如下(比较复杂,简单介绍):

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);

其中corePoolSize指定线程池中线程的数量,maximumPoolSize指定线程池中最大线程数量,keepAliveTime指定当线程池线程数超过corePoolSize时多余的线程的存活时间,unit为keepAliveTime的单位,workQueue为任务队列(保存被提交但尚未执行的任务,是一个BlockingQueue接口的对象,内置有多种不同的类型,比如:有界的ArrayBlockingQueue、无界的LinkedBlockingQueue,能处理优先级的PriorityBlockingQueue,没有容量直接提交的SynchronousQueue),threadFactory是线程工厂(用来创建线程,是一个接口,有一个newThread方法),handler指定拒绝策略(任务过多时如何处理)。

Executors中常用的静态工厂方法如下:

  • ExecutorService  newCachedThreadPool ():返回一个缓存线程池,必要时创建新线程,空闲线程保留60s(不常用,因为不确定会产生多少线程,如果频繁提交任务则会导致线程太多,效率降低)
  • ExecutorService  newFixedThreadPool (int nThreads):返回一个固定数目的线程池,线程一直保留(比较常用,结合可用内核数和程序需求设定线程数)
  • ExecutorService  newSingleThreadExecutor ():只有一个线程,顺序执行队列中的任务
  • ScheduledExecutorService  newScheduledThreadPool (int corePoolSize):用于调度执行的固定线程池
  • ScheduledExecutorService  newSingleThreadScheduledExecutor ():只有一个线程,用于调度执行(一般用来做定时任务或周期性任务,替代Timer类)

你可能感兴趣的:(Java并发编程,java,并发编程,多线程)