高并发对象 —— Executors 执行器

High Level Concurrency Objects , 高级并发对象 From Oracle.com-CSDN博客

高并发对象 —— Lock Objects 锁定目标-CSDN博客

高并发对象 —— 本地线程随机 ThreadLocalRandom-CSDN博客

高并发对象 —— Atomic Variables 原子变量-CSDN博客

高并发对象 —— Concurrent Collections 并发集合-CSDN博客

https://docs.oracle.com/javase/tutorial/essential/concurrency/executors.html

In all of the previous examples, there's a close connection between the task being done by a new thread, as defined by its Runnable object, and the thread itself, as defined by a Thread object. This works well for small applications, but in large-scale applications, it makes sense to separate thread management and creation from the rest of the application. Objects that encapsulate these functions are known as executors. The following subsections describe executors in detail.

Executor Interfaces define the three executor object types.
Thread Pools are the most common kind of executor implementation.
Fork/Join is a framework (new in JDK 7) for taking advantage of multiple processors.

Executor Interfaces

https://docs.oracle.com/javase/tutorial/essential/concurrency/exinter.html

The java.util.concurrent package defines three executor interfaces:

java.util.concurrent包定义了三个执行器接口:

  • Executor, a simple interface that supports launching new tasks.

    执行器,一个支持启动新任务的简单接口。

  • ExecutorService, a subinterface of Executor, which adds features that help manage the life cycle, both of the individual tasks and of the executor itself.

    ExecutorService是Executor的一个子接口,它增加了辅助管理生命周期(包括单个任务和执行器本身)的功能特性。

  • ScheduledExecutorService, a subinterface of ExecutorService, supports future and/or periodic execution of tasks.

    ScheduledExecutorService是ExecutorService的子接口,支持任务的延迟和/或周期性执行。

Typically, variables that refer to executor objects are declared as one of these three interface types, not with an executor class type.

通常,引用执行器对象的变量被声明为这三种接口类型之一,而不是执行器类类型。

The Executor Interface
The Executor interface provides a single method, execute, designed to be a drop-in replacement for a common thread-creation idiom. If r is a Runnable object, and e is an Executor object you can replace

Executor接口提供了一个单一方法execute,旨在作为常见线程创建习惯用法的直接替代方案。如果r是Runnable对象,e是Executor对象,您可以用e.execute(r)替代(new Thread(r)).start()。

(new Thread(r)).start();


with

e.execute(r);


However, the definition of execute is less specific. The low-level idiom creates a new thread and launches it immediately. Depending on the Executor implementation, execute may do the same thing, but is more likely to use an existing worker thread to run r, or to place r in a queue to wait for a worker thread to become available. (We'll describe worker threads in the section on Thread Pools.)

然而,"execute"的定义较为宽泛。底层惯用法会创建一个新线程并立即启动它。根据Executor的实现方式,"execute"可能执行相同的操作,但更倾向于使用现有的工作线程来运行r,或将r放入队列等待可用工作线程。(我们将在关于线程池的部分详细描述工作线程。)

The executor implementations in java.util.concurrent are designed to make full use of the more advanced ExecutorService and ScheduledExecutorService interfaces, although they also work with the base Executor interface.

java.util.concurrent中的执行器实现旨在充分利用更高级的ExecutorService和ScheduledExecutorService接口,尽管它们也适用于基础的Executor接口。

The ExecutorService Interface
The ExecutorService interface supplements execute with a similar, but more versatile submit method. Like execute, submit accepts Runnable objects, but also accepts Callable objects, which allow the task to return a value. The submit method returns a Future object, which is used to retrieve the Callable return value and to manage the status of both Callable and Runnable tasks.

ExecutorService接口通过类似但更通用的submit方法对execute进行了补充。与execute一样,submit接受Runnable对象,但同时也接受Callable对象,允许任务返回值。submit方法返回一个Future对象,该对象用于获取Callable的返回值以及管理Callable和Runnable任务的状态。

ExecutorService also provides methods for submitting large collections of Callable objects. Finally, ExecutorService provides a number of methods for managing the shutdown of the executor. To support immediate shutdown, tasks should handle interrupts correctly.

ExecutorService 还提供了提交大量 Callable 对象的方法。最后,ExecutorService 提供了多种方法来管理执行器的关闭。为了支持立即关闭,任务应正确处理中断。

The ScheduledExecutorService Interface
The ScheduledExecutorService interface supplements the methods of its parent ExecutorService with schedule, which executes a Runnable or Callable task after a specified delay. In addition, the interface defines scheduleAtFixedRate and scheduleWithFixedDelay, which executes specified tasks repeatedly, at defined intervals.

ScheduledExecutorService接口通过schedule方法对其父接口ExecutorService的功能进行了扩展,该方法可在指定延迟后执行Runnable或Callable任务。此外,该接口还定义了scheduleAtFixedRatescheduleWithFixedDelay方法,用于以固定时间间隔重复执行特定任务。

THREAD POOLS

https://docs.oracle.com/javase/tutorial/essential/concurrency/pools.html

Most of the executor implementations in java.util.concurrent use thread pools, which consist of worker threads. This kind of thread exists separately from the Runnable and Callable tasks it executes and is often used to execute multiple tasks.

Java并发工具包(java.util.concurrent)中的大多数执行器实现都采用线程池机制,这些线程池由工作线程组成。此类工作线程与其所执行的Runnable和Callable任务相互独立,通常用于执行多个任务。

Using worker threads minimizes the overhead due to thread creation. Thread objects use a significant amount of memory, and in a large-scale application, allocating and deallocating many thread objects creates a significant memory management overhead.

使用工作线程可以最小化线程创建的开销。线程对象会占用大量内存,在大规模应用程序中,分配和释放许多线程对象会产生巨大的内存管理开销。

One common type of thread pool is the fixed thread pool. This type of pool always has a specified number of threads running; if a thread is somehow terminated while it is still in use, it is automatically replaced with a new thread. Tasks are submitted to the pool via an internal queue, which holds extra tasks whenever there are more active tasks than threads.

固定线程池是一种常见的线程池类型。这类线程池始终维持指定数量的线程运行;如果某个线程在使用过程中意外终止,它会自动被新线程替换。任务通过内部队列提交到线程池,当活跃任务数量超过线程数时,该队列会暂存额外的待处理任务。

An important advantage of the fixed thread pool is that applications using it degrade gracefully. To understand this, consider a web server application where each HTTP request is handled by a separate thread. If the application simply creates a new thread for every new HTTP request, and the system receives more requests than it can handle immediately, the application will suddenly stop responding to all requests when the overhead of all those threads exceed the capacity of the system. With a limit on the number of the threads that can be created, the application will not be servicing HTTP requests as quickly as they come in, but it will be servicing them as quickly as the system can sustain.

固定线程池的一个重要优势在于,使用它的应用程序能够优雅地降级。要理解这一点,不妨以一个Web服务器应用为例,其中每个HTTP请求都由单独的线程处理。如果应用程序仅为每个新HTTP请求创建新线程,而系统接收的请求数量超过其即时处理能力,当所有线程的开销超出系统容量时,应用程序会突然停止响应所有请求。通过限制可创建的线程数量,应用程序虽无法以请求到达的速度处理所有HTTP请求,但能以系统可持续的最快速度处理它们。

A simple way to create an executor that uses a fixed thread pool is to invoke the newFixedThreadPool factory method in java.util.concurrent.Executors This class also provides the following factory methods:

创建一个使用固定线程池的执行器的简单方法是调用java.util.concurrent.Executors中的newFixedThreadPool工厂方法。该类还提供以下工厂方法:

  • The newCachedThreadPool method creates an executor with an expandable thread pool. This executor is suitable for applications that launch many short-lived tasks.

    newCachedThreadPool方法创建了一个具有可扩展线程池的执行器。该执行器适用于启动许多短生命周期的任务的应用程序。

  • The newSingleThreadExecutor method creates an executor that executes a single task at a time.

    newSingleThreadExecutor 方法创建一个一次只执行单个任务的执行器。

  • Several factory methods are ScheduledExecutorService versions of the above executors.

    上述执行器的ScheduledExecutorService版本提供了多个工厂方法。

If none of the executors provided by the above factory methods meet your needs, constructing instances of java.util.concurrent.ThreadPoolExecutor or java.util.concurrent.ScheduledThreadPoolExecutor will give you additional options.

如果上述工厂方法提供的执行器都无法满足您的需求,构造java.util.concurrent.ThreadPoolExecutor或java.util.concurrent.ScheduledThreadPoolExecutor的实例将为您提供更多选择。

Fork/Join

https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html

The fork/join framework is an implementation of the ExecutorService interface that helps you take advantage of multiple processors. It is designed for work that can be broken into smaller pieces recursively. The goal is to use all the available processing power to enhance the performance of your application.

Fork/Join框架是ExecutorService接口的一种实现,旨在帮助开发者充分利用多核处理器的优势。该框架专为可被递归分解为更小任务单元的工作场景而设计,其核心目标是通过调用所有可用计算资源来提升应用程序的运行效率。

As with any ExecutorService implementation, the fork/join framework distributes tasks to worker threads in a thread pool. The fork/join framework is distinct because it uses a work-stealing algorithm. Worker threads that run out of things to do can steal tasks from other threads that are still busy.

与任何ExecutorService实现一样,fork/join框架将任务分配给线程池中的工作线程。fork/join框架的独特之处在于它采用了工作窃取算法:空闲的工作线程可以从仍在忙碌的其他线程那里窃取任务。

The center of the fork/join framework is the ForkJoinPool class, an extension of the AbstractExecutorService class. ForkJoinPool implements the core work-stealing algorithm and can execute ForkJoinTask processes.

fork/join框架的核心是ForkJoinPool类,它是AbstractExecutorService类的扩展。ForkJoinPool实现了核心的工作窃取算法,能够执行ForkJoinTask进程。

Basic Use
The first step for using the fork/join framework is to write code that performs a segment of the work. Your code should look similar to the following pseudocode:

使用fork/join框架的第一步是编写执行部分工作的代码。您的代码应类似于以下伪代码:

if (my portion of the work is small enough)
  do the work directly
else
  split my work into two pieces
  invoke the two pieces and wait for the results


Wrap this code in a ForkJoinTask subclass, typically using one of its more specialized types, either RecursiveTask (which can return a result) or RecursiveAction.

After your ForkJoinTask subclass is ready, create the object that represents all the work to be done and pass it to the invoke() method of a ForkJoinPool instance.

将这个代码封装在ForkJoinTask的子类中,通常使用其更专门的类型之一,即RecursiveTask(可以返回结果)或RecursiveAction(不返回结果)。

当你的ForkJoinTask子类准备就绪后,创建表示所有待完成工作的对象,并将其传递给ForkJoinPool实例的invoke()方法。

Blurring for Clarity
To help you understand how the fork/join framework works, consider the following example. Suppose that you want to blur an image. The original source image is represented by an array of integers, where each integer contains the color values for a single pixel. The blurred destination image is also represented by an integer array with the same size as the source.

为了帮助您理解fork/join框架的工作原理,请考虑以下示例。假设您想要模糊一张图像。原始源图像由一个整数数组表示,其中每个整数包含单个像素的颜色值。模糊后的目标图像也由与源图像大小相同的整数数组表示。

Performing the blur is accomplished by working through the source array one pixel at a time. Each pixel is averaged with its surrounding pixels (the red, green, and blue components are averaged), and the result is placed in the destination array. Since an image is a large array, this process can take a long time. You can take advantage of concurrent processing on multiprocessor systems by implementing the algorithm using the fork/join framework. Here is one possible implementation:

实现模糊效果是通过逐像素处理源数组完成的。每个像素与其周围像素进行平均计算(红、绿、蓝分量分别取平均值),结果存入目标数组。由于图像是大型数组,此过程可能耗时较长。您可以通过采用fork/join框架实现该算法,充分利用多处理器系统的并行处理能力。以下是可能的实现方案:


  

public class ForkBlur extends RecursiveAction {
    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;
  
    // Processing window size; should be odd.
    private int mBlurWidth = 15;
  
    public ForkBlur(int[] src, int start, int length, int[] dst) {
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    protected void computeDirectly() {
        int sidePixels = (mBlurWidth - 1) / 2;
        for (int index = mStart; index < mStart + mLength; index++) {
            // Calculate average.
            float rt = 0, gt = 0, bt = 0;
            for (int mi = -sidePixels; mi <= sidePixels; mi++) {
                int mindex = Math.min(Math.max(mi + index, 0),
                                    mSource.length - 1);
                int pixel = mSource[mindex];
                rt += (float)((pixel & 0x00ff0000) >> 16)
                      / mBlurWidth;
                gt += (float)((pixel & 0x0000ff00) >>  8)
                      / mBlurWidth;
                bt += (float)((pixel & 0x000000ff) >>  0)
                      / mBlurWidth;
            }
          
            // Reassemble destination pixel.
            int dpixel = (0xff000000     ) |
                   (((int)rt) << 16) |
                   (((int)gt) <<  8) |
                   (((int)bt) <<  0);
            mDestination[index] = dpixel;
        }
    }


  ...
Now you implement the abstract compute() method, which either performs the blur directly or splits it into two smaller tasks. A simple array length threshold helps determine whether the work is performed or split.

现在你来实现抽象的 compute() 方法,该方法要么直接执行模糊处理,要么将其拆分为两个较小的任务。通过简单的数组长度阈值就能判断是执行工作还是拆分任务。

protected static int sThreshold = 100000;

protected void compute() {
    if (mLength < sThreshold) {
        computeDirectly();
        return;
    }
    
    int split = mLength / 2;
    
    invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
              new ForkBlur(mSource, mStart + split, mLength - split,
                           mDestination));
}


If the previous methods are in a subclass of the RecursiveAction class, then setting up the task to run in a ForkJoinPool is straightforward, and involves the following steps:

Create a task that represents all of the work to be done.

如果上述方法属于RecursiveAction类的子类,那么在ForkJoinPool中设置任务运行就很简单,主要包括以下步骤:

创建一个代表所有待完成工作的任务。

// source image pixels are in src
// destination image pixels are in dst
ForkBlur fb = new ForkBlur(src, 0, src.length, dst);


Create the ForkJoinPool that will run the task.

ForkJoinPool pool = new ForkJoinPool();


Run the task.

pool.invoke(fb);


For the full source code, including some extra code that creates the destination image file, see the ForkBlur example.

如需完整的源代码,包括创建目标图像文件的一些额外代码,请参阅ForkBlur示例。

Standard Implementations
Besides using the fork/join framework to implement custom algorithms for tasks to be performed concurrently on a multiprocessor system (such as the ForkBlur.java example in the previous section), there are some generally useful features in Java SE which are already implemented using the fork/join framework. One such implementation, introduced in Java SE 8, is used by the java.util.Arrays class for its parallelSort() methods. These methods are similar to sort(), but leverage concurrency via the fork/join framework. Parallel sorting of large arrays is faster than sequential sorting when run on multiprocessor systems. However, how exactly the fork/join framework is leveraged by these methods is outside the scope of the Java Tutorials. For this information, see the Java API documentation.

除了使用fork/join框架在多处理器系统上实现自定义并行算法(如前一节ForkBlur.java示例所示),Java SE中还有一些已基于该框架实现的通用功能。例如Java SE 8引入的java.util.Arrays类的parallelSort()方法,其实现就采用了fork/join框架。这些方法与普通sort()类似,但通过fork/join框架实现了并发处理。在多处理器系统中,对大数组进行并行排序比顺序排序更快。不过这些方法具体如何运用fork/join框架已超出Java教程范围,相关细节请参阅Java API文档。

Another implementation of the fork/join framework is used by methods in the java.util.streams package, which is part of Project Lambda scheduled for the Java SE 8 release. For more information, see the Lambda Expressions section.

java.util.streams包中的方法采用了fork/join框架的另一种实现,该包是计划在Java SE 8版本中发布的Lambda项目的一部分。更多信息请参阅Lambda表达式章节。

Interrupts

https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html

An interrupt is an indication to a thread that it should stop what it is doing and do something else. It's up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate. This is the usage emphasized in this lesson.

A thread sends an interrupt by invoking interrupt on the Thread object for the thread to be interrupted. For the interrupt mechanism to work correctly, the interrupted thread must support its own interruption.

中断是向线程发出的一个信号,表示它应该停止当前正在执行的操作并转而处理其他任务。具体如何响应中断由程序员决定,但线程通常会终止运行——这正是本课程重点强调的用法。

要发送中断信号,需在目标线程对应的Thread对象上调用interrupt方法。为使中断机制正常运作,被中断的线程必须自行支持中断处理。

Supporting Interruption
How does a thread support its own interruption? This depends on what it's currently doing. If the thread is frequently invoking methods that throw InterruptedException, it simply returns from the run method after it catches that exception. For example, suppose the central message loop in the SleepMessages example were in the run method of a thread's Runnable object. Then it might be modified as follows to support interrupts:

线程如何支持自身的中断?这取决于它当前正在执行的操作。如果线程频繁调用会抛出InterruptedException的方法,那么它只需在捕获该异常后从run方法返回即可。例如,假设SleepMessages示例中的核心消息循环位于线程Runnable对象的run方法内,那么可以按以下方式修改以支持中断:

for (int i = 0; i < importantInfo.length; i++) {
    // Pause for 4 seconds
    try {
        Thread.sleep(4000);
    } catch (InterruptedException e) {
        // We've been interrupted: no more messages.
        return;
    }
    // Print a message
    System.out.println(importantInfo[i]);
}


Many methods that throw InterruptedException, such as sleep, are designed to cancel their current operation and return immediately when an interrupt is received.

What if a thread goes a long time without invoking a method that throws InterruptedException? Then it must periodically invoke Thread.interrupted, which returns true if an interrupt has been received. For example:

许多会抛出InterruptedException异常的方法,比如sleep,其设计初衷是当接收到中断信号时取消当前操作并立即返回。

如果一个线程长时间不调用会抛出InterruptedException的方法怎么办?那么它必须定期调用Thread.interrupted方法,如果接收到中断信号,该方法会返回true。例如:

for (int i = 0; i < inputs.length; i++) {
    heavyCrunch(inputs[i]);
    if (Thread.interrupted()) {
        // We've been interrupted: no more crunching.
        return;
    }
}


In this simple example, the code simply tests for the interrupt and exits the thread if one has been received. In more complex applications, it might make more sense to throw an InterruptedException:

在这个简单的例子中,代码仅测试中断信号,若收到中断则退出线程。在更复杂的应用程序中,抛出 InterruptedException 可能更合理:

if (Thread.interrupted()) {
    throw new InterruptedException();
}


This allows interrupt handling code to be centralized in a catch clause.

这允许将中断处理代码集中在一个 catch 子句中。

The Interrupt Status Flag
The interrupt mechanism is implemented using an internal flag known as the interrupt status. Invoking Thread.interrupt sets this flag. When a thread checks for an interrupt by invoking the static method Thread.interrupted, interrupt status is cleared. The non-static isInterrupted method, which is used by one thread to query the interrupt status of another, does not change the interrupt status flag.

中断机制通过一个称为中断状态(interrupt status)的内部标志实现。调用Thread.interrupt方法会设置此标志。当线程通过静态方法Thread.interrupted检查中断时,中断状态将被清除。而非静态方法isInterrupted用于一个线程查询另一个线程的中断状态,该方法不会改变中断状态标志。

By convention, any method that exits by throwing an InterruptedException clears interrupt status when it does so. However, it's always possible that interrupt status will immediately be set again, by another thread invoking interrupt.

按照惯例,任何通过抛出InterruptedException退出的方法在这样做时会清除中断状态。然而,中断状态很可能立即被另一个调用interrupt方法的线程再次设置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值