Java笔记:Future设计模式

mac2022-06-30  23

Java笔记:Future设计模式

简述

  Future设计模式提供了一种票据的思想,其具体思路为:   在主线程中存在一个负责任务接收和开票的对象(将持有该对象的线程称作主线程),当有子线程向该对象提交任务的时候,该对象提供一个方法,该方法会负责生成并返回一个票据对象交给创建出的新的子线程,同时创建出新的工作线程执行任务,从而可以使子线程和主线程可以完成其他的工作,主线程不必将提交的任务串行化处理,从而提高效率。   与实际生活中的票据相似,票据对象的类会包含一个对象锁、对任务处理结果的引用以及任务是否完成的标记位。子线程与工作线程间即可通过票据对象实现线程间的通信,该对象提供以下若干接口:   1、任务处理结果的获取,子线程想要获取任务处理结果时调用,以同步等待的方式当任务未完成时使持有票据的子线程等待,任务完成时返回结果引用。   2、任务完成时票据对象对处理结果的接受,主线程创建的工作线程在完成相应工作后调用,工作线程将结果传入票据对象中,之后票据对象唤醒因想要获取任务处理结果而陷入对象锁等待池的子线程。   子线程可以使用接收到的票据对象获取到任务处理的结果,若任务处理时间过长,也不必从票据对象中立即获取任务处理结果,避免陷入票据对象的等待池中。在此段时间中子线程和主线程都可以做其他工作,仅让创建出的工作线程完成工作返回结果即可。若子线程想尽快得到处理结果,提交完任务后立即获取结果,就只好等待。具体编码如下:

票据接口与具体实现类

  票据接口如下:

public interface Future<T> { T get() throws InterruptedException; boolean done(); }

  具体实现如下:

public class FutureTask<T> implements Future<T> { private T result; private boolean isDone; // 局部对象锁 保证同步 同一时刻只有一个线程获取LOCK对象 // 由final实现该引用与堆内存中空间不可更改的绑定 private final Object LOCK = new Object(); @Override public T get() throws InterruptedException { synchronized (LOCK){ while (!isDone){ System.out.println(Thread.currentThread() + " :该任务未完成,暂时等待"); LOCK.wait(); } System.out.println(Thread.currentThread() + " :该任务执行完毕"); return result; } } protected void finish(T result){ synchronized (LOCK){ if (isDone){ return; } // 本对象接收计算结果 this.result = result; // 将完成标记设置为真 this.isDone = true; // 唤醒其他因调用本对象get方法且任务未完成而陷入LOCK对象等待池中的线程 LOCK.notifyAll(); } } @Override public boolean done() { return isDone; } }

  该类使用对象锁完成工作线程与提交任务线程间的通信,属于经典的在不满足条件时提交任务线程等待,工作线程改变条件后唤醒提交任务线程(wait/notify实现线程间通信)。

接收任务接口与具体实现类

  接收任务接口如下:

public interface FutureService<IN, OUT> { //Runnable 提交的任务无返回值 Future<?> submit(Runnable runnable); //使用Task接口 存在输入输出过程 提交的任务有返回值 Future<OUT> submit(Task<IN, OUT> task, IN input); }

  具体实现如下:

public class FutureServiceImpl<IN, OUT> implements FutureService<IN, OUT> { // 提交至此的任务都会交给新线程完成 // 线程前缀名 private final static String FUTURE_THREAD_PREFIX = "FUTURE_THREAD - "; // 记录创建的线程个数 方便对创建的线程进行编号 // 线程安全的sum id记录总数和线程编号方法 private final static AtomicInteger atomicInteger = new AtomicInteger(0); private String getThreadName(){ return FUTURE_THREAD_PREFIX + atomicInteger.getAndIncrement(); } @Override public Future<?> submit(Runnable runnable) { System.out.println(Thread.currentThread() + " : 接收Runnable任务"); FutureTask<Void> futureTask = new FutureTask<>(); new Thread(runnable, getThreadName()){ @Override public void run() { System.out.println(Thread.currentThread() + " :执行任务"); runnable.run(); System.out.println(Thread.currentThread() + " :完成任务"); futureTask.finish(null); } }.start(); System.out.println(Thread.currentThread() + " :已创建新线程执行任务,将要返回票据即Future对象"); return futureTask; } @Override public Future<OUT> submit(Task<IN, OUT> task, IN input) { System.out.println(Thread.currentThread() + " : 接收Task任务"); FutureTask<OUT> futureTask = new FutureTask<>(); new Thread(getThreadName()){ @Override public void run() { System.out.println(Thread.currentThread() + " :执行任务"); OUT out = task.get(input); System.out.println(Thread.currentThread() + " :完成任务"); futureTask.finish(out); } }.start(); return futureTask; } }

  然而,倘若一时间有很多个任务提交至该对象,则对每一个任务都要使用一个新的工作线程来处理,若为多个耗时较少的任务,则会导致线程的频繁创建与销毁,对CPU造成很大的负担,考虑将任务交给线程池来完成。

测试与执行结果

  使用实现Runnable、无返回值的类作为任务对象。任务类如下:

class TestTask implements Runnable{ private static AtomicInteger atomicInteger = new AtomicInteger(0); private final int id; public TestTask(){ this.id = atomicInteger.getAndIncrement(); } @Override public void run() { System.out.println(Thread.currentThread() + " :任务 :" + this + " 开始执行..."); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + " :任务 :" + this + " 执行结束..."); } @Override public String toString() { return "TestTask " + this.id; } }

  任务的内容为使线程睡眠5秒。   测试代码如下:

public class Test { public static void main(String[] args) { FutureServiceImpl futureService = new FutureServiceImpl(); new Thread(new Runnable() { @Override public void run() { Future future = futureService.submit(new TestTask()); try { System.out.println(Thread.currentThread() + " :提交完任务 此时是空闲的 睡眠1秒"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } try { System.out.println(Thread.currentThread() + " 任务所得 : " + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { Future future = futureService.submit(new TestTask()); try { System.out.println(Thread.currentThread() + " :提交完任务 此时是空闲的 睡眠9秒"); Thread.sleep(9000); } catch (InterruptedException e) { e.printStackTrace(); } try { System.out.println(Thread.currentThread() + " 任务所得 : " + future.get()); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); System.out.println("提交任务的工作做完了,我 " + Thread.currentThread() + " 线程是空闲的..."); } }

  由主线程开启两个子线程,主线程开启完子线程即进入空闲状态。由子线程向FutureService对象中提交任务,提交完后子线程也进入空闲状态。第一个子线程睡眠时间小于任务执行时间,想要获取结果时陷入对象锁等待池中;第二个子线程睡眠时间大于任务执行时间,直接获取到了执行结果,Runnable无返回值,任务执行结果为null。   执行结果如下:

Thread[Thread-0,5,main] : 接收Runnable任务 提交任务的工作做完了,我 Thread[main,5,main] 线程是空闲的... Thread[Thread-1,5,main] : 接收Runnable任务 Thread[Thread-0,5,main] :已创建新线程执行任务,将要返回票据即Future对象 Thread[Thread-1,5,main] :已创建新线程执行任务,将要返回票据即Future对象 Thread[Thread-0,5,main] :提交完任务 此时是空闲的 睡眠1秒 Thread[Thread-1,5,main] :提交完任务 此时是空闲的 睡眠9秒 Thread[FUTURE_THREAD - 1,5,main] :执行任务 Thread[FUTURE_THREAD - 0,5,main] :执行任务 Thread[FUTURE_THREAD - 1,5,main] :任务 :TestTask 1 开始执行... Thread[FUTURE_THREAD - 0,5,main] :任务 :TestTask 0 开始执行... Thread[Thread-0,5,main] :该任务未完成,暂时等待 Thread[FUTURE_THREAD - 1,5,main] :任务 :TestTask 1 执行结束... Thread[FUTURE_THREAD - 0,5,main] :任务 :TestTask 0 执行结束... Thread[FUTURE_THREAD - 1,5,main] :完成任务 Thread[FUTURE_THREAD - 0,5,main] :完成任务 Thread[Thread-0,5,main] :该任务执行完毕 Thread[Thread-0,5,main] 任务所得 : null Thread[Thread-1,5,main] :该任务执行完毕 Thread[Thread-1,5,main] 任务所得 : null

jdk的FutureTask

  jdk的concurrent并发包下也存在着Future接口和间接继承该接口的类FutureTask。在该类对象中持有任务(实现了Callable接口的对象)、执行结果、执行线程以及处于等待执行结果的线程链表,并存在一系列的状态与状态变换。总结如下:   1、在get()方法使用票据获取结果时,存在直接获取和超时等待获取两种结果(在并发包下常见),该方法使用awaitDone()完成目标。   2、awaitDone(boolean timed, long nanos)方法根据标记位timed来判断是否使用超时等待,该方法的超时等待是无限循环自旋实现的,若使用超时等待,只能在任务完成或到达时间后才能返回,若不使用,则只能在任务完成时才能返回。   3、内部维护的处于等待结果的线程链表,由awaitDone()方法具体维护。使用WAITERS.weakCompareAndSet()方法以CAS操作向链表中添加,removeWaiter()方法将已经结束或等待时长已过的线程移出该链表。   4、run()方法负责开启任务并将执行结果赋值给result,执行的任务必须为实现了Callable接口的对象,调用call方法返回任务处理结果。

总结

  在Future设计模式中,实现完成非阻塞式地完成任务是由开启新线程完成的,交由线程池处理较多任务会更好。工作线程和提交任务的子线程通过票据对象完成线程间的通信(wait/notify),子线程若要在提交任务后立即获取结果,仍需要等待工作线程执行完任务。

  本篇为《Java高并发编程详解 多线程与架构设计》汪文君著 第19章笔记。

最新回复(0)