并发编程总结四

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38095094/article/details/82558191

Executor框架

结构

  • 任务:包括被执行任务需要实现的接口:Runnable 接口或者 Callable 接口
  • 任务的执行:继承自Executor的ExecutorService接口。Executor 框架有两个关键类实现了ExecutorService 接口,分别是ThreadPoolExecutor 和 ScheduledThreadPoolExecutor
  • 异步计算的结果:包括接口Furure 和 实现Future 接口的FutureTask

实现流程

  • 主线程首先要创建实现Runnable 或者 Callable 接口的任务对象
  • 然后可以把Runnable 对象直接交给ExecutorService 执行:ExecutorService.execute(Runnable run) ;或者将Runnable 对象或者 Callable 对象提交给ExecutorService 执行:ExecutorService.submit(Callable a)
  • 如果执行submit() 方法,则会返回一个FutureTask 对象
  • 最后,主线程可以执行FutureTask.get() 方法等待任务执行完成。主线程也可以执行 FutureTask.cancel( boolean mayInterruptIfRunning) 来取消此任务的执行

Executor 成员

ThreadPoolExecutor

Executors 可以创建三种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool

  • FixedThreadPool 被称为可重用固定线程数的线程池,源代码如下
public static ExecutorService newFixedThreadPool(int nThreads){
  return new ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.MILLISECONDS,
                               new LinkedBlockingQueue<Runnable>);
}

其中参数依次为:corePoolSize 和 maximumPoolSize ,keepAliveTime设置为 0L 表示有空闲线程等待接收新任务的时间为0,也就是说一旦有空闲的线程就会被立即终止,LinkedBlockingQueue 是一个无界链式队列,当执行的任务数超过corePoolSize 时,后面的任务将会加入到队列中,等其它任务执行完成之后再从队列中拿取任务执行。

对FixedThreadPool 的说明如下:

1、如果当前运行的线程数少于corePoolSize ,则创建新线程来执行任务
2、当前线程数量等于corePoolSize 时,将任务加入LinkedBlockingQueue
3、线程执行完任务之后,会在循环中反复从LinkBlickingQueue 获取任务来执行
  • SingleThreadExecutor 它和FixedThreadPool 不一样的地方在于它是使用单个worker线程的Executor,也就是nThread为 1
public static ExecutorService newSingleThreadExecutor(){
  return new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,
                               new LinkedBlockingQueue<Runnable>);
}

对SingleThreadExecutor 的说明如下:

1、如果当前运行的线程数为0,则创建一个新线程来执行任务
2、当前线程池有一个线程在运行,将任务加入到LinkedBlockingQueue中
3、线程中任务执行完之后会在一个无限循环中反复从LinkedBlockingQueue 获取任务来执行
  • CachedThreadPool 是一个会根据需要创建新线程的线程池
public static ExecutorService newCacheThreadPool(){
  return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.MILLISECONDS,
                               new SynchronousQueue<Runnable>);
}

CachedThreadPool 使用没有容量的SynchronousQueue 作为线程池的工作队列,但CachedThreadPool 的mximumPool 大小是Integer.MAX_VALUE是无界的。这就意味着:如果主线程提交任务的速度高于maxmumiPool中线程处理任务的速度时,CacheThreadPool会不断创建新线程。

对CachedThreadPool 的说明如下:

  • 首先执行SychronousQueue.poll(keepAliveTime , TimeUnit.NANOSECONDS),判断当前有没有空闲的线程在执行poll()方法,如果有的话主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行结束
  • 如果没有空闲线程,或者初始maximumPool为空时,则创建线程执行execute()方法
  • 新创建的线程执行完之后,会执行poll()方法,空闲线程等待60s,如果在这60s没有新任务交给这个线程执行,则这个线程将会被终止

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 主要用来在给定的延迟之后运行任务,或者定期执行任务。使用的是DelayQueue ,它是一个支持延时获取元素的无界阻塞队列,队列使用PriorityQueue 来实现,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。

执行主要分为两部分:

  • 当调用scheduleAtFixedRate() 或者 scheduleWithFixedDelay() 方法的时候,会向DelayQueue 添加一个视线了RunnableScheduledFuture 接口的ScheduledFutureTask
  • 线程池中的线程从DelayQueue 中获取ScheduledFutureTask,然后执行任务

ScheduledThreadPoolExecutor 的实现:

ScheduledFutureTask 主要包含3个成员变量:

  • long 型成员变量time,表示这个任务将要被执行的具体时间
  • long 型成员变量sequenceNumber ,表示这个任务被添加到ScheduledThreadPoolExecutor 中的序号
  • long 型成员变量period,表示任务执行的间隔周期

DelayQueue 中封装的PriorityQueue 会对队列中的ScheduledFutureTask 进行排序,time小的排在前面执行,假如time 相同,就比较sequenceNumber ,小的排在前面,表示先提交的任务先被执行。

扫描二维码关注公众号,回复: 3121213 查看本文章

执行周期任务的过程:

1、线程1从DelayQueue 中获取已到期的ScheduledFutureTask(DelayQueue.take()),到期任务是指task中的time大于当前时间
2、线程1执行这个task
3、线程1修改ScheduledFutureTask 的time变量为下次将要被执行的时间
4、线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中 

FutureTask

FutureTask 实现了Future 和 Runnable 接口,因此FutureTask 可以交给Executor执行,也可以直接FutureTask.run(),FutureTask有三种状态,分别是:未启动、已启动、已完成。

  • 当FutureTask 处于未启动或者已启动状态时,执行get()方法会导致调用线程阻塞;处于已完成状态时调用会立刻返回结果或者抛出异常
  • 处于未启动状态时,调用cancel() 方法将导致任务永远不会执行;处于已启动状态时,当执行FutureTask.cancel(true) 即使正在执行的任务也会遭到停止,当执行FutureTask.cancel(false) 时正在执行的任务会继续执行,还没执行的任务将不再执行

FutureTask 的使用

当一个线程需要等待另一个线程把某个任务执行完之后它才能继续执行,此时可以使用FutureTask

package chapter10;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class ConcurrentTask {

    private final ConcurrentMap<Object, Future<String>> taskCache = new ConcurrentHashMap<Object, Future<String>>();

    private String executionTask(final String taskName) throws ExecutionException, InterruptedException {
        while (true) {
            Future<String> future = taskCache.get(taskName); //1.1,2.1
            if (future == null) {
                Callable<String> task = new Callable<String>() {
                    public String call() throws InterruptedException {
                        //......
                        return taskName;
                    }
                };
                //1.2创建任务
                FutureTask<String> futureTask = new FutureTask<String>(task);
                future = taskCache.putIfAbsent(taskName, futureTask); //1.3
                if (future == null) {
                    future = futureTask;
                    futureTask.run(); //1.4执行任务
                }
            }

            try {
                return future.get(); //1.5,2.2线程在此等待任务执行完成
            } catch (CancellationException e) {
                taskCache.remove(taskName, future);
            }
        }
    }

}

当两个线程试图同时执行同一个任务时,如果Thread 1执行1.3 后 Thread 2执行 2.1 ,那么接下来Thread 2 将在2.2 等待,直到Thread 1 执行完1.4 后Thread 才能从2.2 返回。

参考《并发编程的艺术》第10章

猜你喜欢

转载自blog.csdn.net/qq_38095094/article/details/82558191