Java多线程基本概念

基本概念

线程与任务的概念不一样。

任务:通常是一些抽象的且离散的工作单元,比如在Web请求中,针对用户的请求需要返回相应的页面是一个任务,在Java中实现Runnable接口的类也是一个任务.

线程:执行任务的实体,可以在单个线程中串行地执行各项任务,例如单线程串行执行Web请求,也可以在为每个请求建立一个线程执行。

任务是一组逻辑工作单元,而线程则是使任务异步执行的机制。

一个简单的例子:当我们定义了一个Runnable对象时,即任务,使用new Thread(Runnable).start()即将任务放到实际的线程中执行。

Executor线程池

Web应用中有两种执行任务的策略:

  • 把所有任务放在单个线程中串行执行:糟糕的响应性和吞吐量。

  • 为每个任务创建一个线程并行的执行:资源管理的复杂性,体现在线程开销大,不稳定。

所以在Java中引入了Executor(线程池)框架来简化线程的管理工作,Executor使用Runnable作为其基本的任务表示形式,在Java类库中,任务执行的主要抽象不是Thread,而是Executor

Executor基于生产者-消费者模式,提交任务的操作相当于生产者(生成待完成的工作单元),执行任务的线程则相当于消费者(执行完这些工作单元),这样就将请求处理任务的提交与任务的实际执行解耦开来。在下面例子中建立一个固定长度的线程池,可以容纳100个线程。

class TaskExecutionWebServer {
    private static final int NTHREADS = 100;
    // 调用Executors中的静态工厂方法可以来创建一个线程池
    private static final Executor exec = 
                    Executors.newFixedThreadPool(NTHREADS);

    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(80);
        while (true) {
            final Socket connection = socket.accept();
            Runnable task = new Runnable() {
                public void run() {
                    handleRequest(connection);
                }
            };
            // 通过execute将任务提交到工作队列中,工作线程反复获取执行
            exec.execute(task);
        }
    }
}

执行策略

线程池是指管理一组同构工作线程的资源池。通过重用现有的线程而不是创建新线程,可以在处理多个请求时,分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程已经存在,因为提高了响应性。

  • 工作队列(Work Queue):线程池是与工作队列密切相关的,其中在工作队列中保存了所有等待执行的任务。
  • 工作者线程(Worker Thread):从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。

在执行策略中定义了任务执行的“What、Where、When、How”等方面,包括:

  • 该任务在哪个线程中执行
  • 任务可以按照什么样的顺序来执行(如优先级、FIFO、LIFO)
  • 有多少个任务可以并发执行
  • ......

生命周期

线程池生命周期(ExecutorService)

我们已经知道了如何创建一个Executor,但并没有讨论如何关闭它。为了解决执行服务的生命周期问题,Executor扩展了ExecutorService接口,添加了一些用于生命周期管理方法。

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    //...
}

ExecutorService的生命周期有3种状态:运行、关闭和已终止。shutdown()方法将执行平缓的关闭过程:不再接受新的任务,同时等待已提交任务执行完成(包括未开始执行的任务)。shutdownNow()将尝试取消所有运行的任务。关闭后再提交,由“拒绝执行处理器”处理,或者抛出RejectedExecutionException异常。

任务生命周期(Future)

由于Runnable不能返回一个值或者抛出一个受检查的异常,存在着很大的局限性。所以引入了Callable,它认为主入口点(即call)将返回一个值,并可能抛出一个异常。

又Runnable和Callable描述都是抽象的计算任务,通常经过创建、提交、开始和完成四个阶段,就如ExecutorService为Executor解决生命周期问题,Future也为一个任务解决了生命周期的问题,并提供了相应地方法来判断是否已经完成或取消,以及获取任务的结果和取消任务。

public interface Callable<V> {
        V call() throws Exception;
}

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning); // 取消任务
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionExeception,
                        CancellationExeception;
    V get(long timeout, TimeUnit unit) throws
                    InterruptedException, ExecutionExeception
                    CancellationExeception, TimeoutException;

通过调用ExecutorService中得submit方法将返回一个Future代表该任务的生命周期,可用来获得任务的执行结果或者取消任务。也可以显示地为某个指定的Runnable或Callable实例化一个FutureTask。

在将Runnable或Callable提交到Executor的过程中,包含了一个安全发布的过程,即将Runnable或Callable从提交线程发布到最终执行任务的线程。类似地,在获取Future结果的过程中也包含了一个安全发布,即将这个结果从计算它的线程发布到任何通过get获得它的线程。

参考: 书本Java Concurrency in Practice

原文地址:https://www.cnblogs.com/echobfy/p/4638852.html