【多线程】线程池Future和FutureTask

【一】Future概述

【1】Future的出现原因

我们在Java线程池ThreadPoolExecutor使用详解中利用execute(Runnable r)方法来异步执行任务,但是有一个缺点,就是无法执行带有返回值的任务。

【2】Future结构图

在这里插入图片描述
FutureTask实现了RunnableFuture接口,而RunnableFuture继承了Runnable和Future,也就是说FutureTask既是Runnable,也是Future。

【二】Future详解

Future表示一个任务的周期,并提供了相应的方法来判断是否已经完成或者取消,以及获取任务的结果和取消任务。

(1)Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果

(2)Future是一个接口,是无法生成一个实例的,所以又有了FutureTask。FutureTask实现了RunnableFuture接口,RunnableFuture接口又实现了Runnable接口和Future接口。所以FutureTask既可以被当做Runnable来执行,也可以被当做Future来获取Callable的返回结果。

【1】Future接口源码

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

【2】Future的5个方法

1、cancel():取消一个任务,并返回取消结果。参数表示是否中断线程
2、isCancelled():任务是否取消成功
3、isDone():判断当前任务是否执行完毕,包括正常执行完毕、执行异常或者任务取消
4、get():获取任务执行结果,任务结束之前会阻塞
5、get(long count,TimeUnit):在指定时间内尝试获取执行结果。若超时则抛出超时异常

在这里插入图片描述

【3】ThreadPoolExecutor提供了三个方法,来获取返回值

在这里插入图片描述

(1)submit(Runnable r)

因为Runnable的run方法是void的,没有返回值,所以Future.get()是null。

源码

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

将runnable封装为一个自定义的Callable对象,result为null,具体的流程可以看最底下的源码。

(2)submit(Runnable r,T result)

T result就是传入的对象。我们可以通过自定义Runnable来将result传入到自定义的Runnable中,在run()方法中对结果写入到result中。其中返回的对象future.get()==result。

案例

public class TestThreadExecutor {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        BlockingQueue queue = new LinkedBlockingQueue(10);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(4,10,2,TimeUnit.SECONDS,queue);

        //设置coreThread如果超时也会被取消
        executor.allowCoreThreadTimeOut(true);

        int[] arr = new int[2];
        MyRunnable runnable = new MyRunnable(arr);
        Future f = executor.submit(runnable,arr);
        System.out.println(Arrays.toString((int[])f.get()));
    }
}
-------------------------------------------------------------------------
class MyRunnable implements Runnable {

    private int[] arr;

    public MyRunnable(int[] arr){
        this.arr = arr;
    }

    @Override
    public void run() {
        arr[0] = 111;
    }
}

在这里插入图片描述
源码

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

将runnable封装为一个自定义的Callable对象,result为传入的对象,具体的流程可以看最底下的源码。

(3)submit(Callable c) 【推荐】

submit(Runnable r,T result)不太好用,需要每次都自定义Runnable和result,太麻烦了。

Callable更加好用,传入一个Callable对象就行。
Callable和Runnable是大致一样,只不过Callable能够返回值。

Callable callable = new Callable() {
    @Override
    public Object call() throws Exception {
        String name = "777";
        name += "6666";
        return name;
    }
};

Future f = executor.submit(callable);
System.out.println(f.get());

在这里插入图片描述
从上述代码可以看出,Callable中call()返回的对象就是future.get()得到的对象。

源码

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);//利用callable新建了一个FutureTask
    execute(ftask);//会调用ftask.run()方法
    return ftask;//返回一个FutureTask对象
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;  
}

public void run() {
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
                //执行callable.call()
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)
                //将outcome设置为result
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

protected void set(V v) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        //这里这里这里
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}

future.get()

public V get() throws InterruptedException, ExecutionException {
    int s = state;
    if (s <= COMPLETING)
        //阻塞
        s = awaitDone(false, 0L);
    //这里这里这里
    return report(s);
}

private V report(int s) throws ExecutionException {
    //这里这里这里
    Object x = outcome;
    if (s == NORMAL)
        //这里这里这里
        return (V)x;
    if (s >= CANCELLED)
        throw new CancellationException();
    throw new ExecutionException((Throwable)x);
}

【4】Future使用案例

(1)Future的案例一

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> resultList = new ArrayList<Future<String>>();

        // 创建10个任务并执行
        for (int i = 0; i < 10; i++) {
            // 使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<String> future = executorService.submit(new TaskWithResult(i));
            // 将任务执行结果存储到List中
            resultList.add(future);
        }
        executorService.shutdown();

        // 遍历任务的结果
        for (Future<String> fs : resultList) {
            try {
                System.out.println(fs.get()); // 打印各个线程(任务)执行的结果
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                executorService.shutdownNow();
                e.printStackTrace();
                return;
            }
        }
    }
}
class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    /**
     * 任务的具体过程,一旦任务传给ExecutorService的submit方法,则该方法自动在一个线程上执行。
     *
     * @return
     * @throws Exception
     */
    public String call() throws Exception {
        System.out.println("call()方法被自动调用,干活!!!             " + Thread.currentThread().getName());
        if (new Random().nextBoolean())
            throw new TaskException("Meet error in task." + Thread.currentThread().getName());
        // 一个模拟耗时的操作
        for (int i = 999999999; i > 0; i--)
            ;
        return "call()方法被自动调用,任务的结果是:" + id + "    " + Thread.currentThread().getName();
    }
}

class TaskException extends Exception {
    public TaskException(String message) {
        super(message);
    }
}

执行结果:

call()方法被自动调用,干活!!! pool-1-thread-2
call()方法被自动调用,干活!!! pool-1-thread-6
call()方法被自动调用,干活!!! pool-1-thread-1
call()方法被自动调用,任务的结果是:0 pool-1-thread-1
call()方法被自动调用,任务的结果是:1 pool-1-thread-2
call()方法被自动调用,干活!!! pool-1-thread-4
call()方法被自动调用,干活!!! pool-1-thread-9
call()方法被自动调用,干活!!! pool-1-thread-8
call()方法被自动调用,干活!!! pool-1-thread-10
call()方法被自动调用,干活!!! pool-1-thread-5
call()方法被自动调用,干活!!! pool-1-thread-3
call()方法被自动调用,干活!!! pool-1-thread-7
call()方法被自动调用,任务的结果是:2 pool-1-thread-3
call()方法被自动调用,任务的结果是:3 pool-1-thread-4
java.util.concurrent.ExecutionException: com.company.TaskException: Meet error in task.pool-1-thread-5
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.company.Main.main(Main.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Caused by: com.company.TaskException: Meet error in task.pool-1-thread-5
at com.company.TaskWithResult.call(Main.java:56)
at com.company.TaskWithResult.call(Main.java:40)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

(2)FutureTask的案例二

FutureTask是一个具体的实现类,ThreadPoolExecutor的submit方法返回的就是一个Future的实现,这个实现就是FutureTask的一个具体实例,FutureTask帮助实现了具体的任务执行,以及和Future接口中的get方法的关联。

import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 初始化一个Callable对象和FutureTask对象
        Callable pAccount = new PrivateAccount();
        FutureTask futureTask = new FutureTask(pAccount);
        // 使用futureTask创建一个线程
        Thread pAccountThread = new Thread(futureTask);
        System.out.println("futureTask线程现在开始启动,启动时间为:" + df.format(new Date()));
        pAccountThread.start();
        System.out.println("futureTask线程开始执行其他任务");
        // 从其他账户获取总金额
        int totalMoney = new Random().nextInt(100000);
        System.out.println("现在你在其他账户中的总金额为" + totalMoney);
        System.out.println("等待私有账户总金额统计完毕...");
        // 测试后台的计算线程是否完成,如果未完成则等待
        while (!futureTask.isDone()) {
            try {
                Thread.sleep(500);
                System.out.println("私有账户计算未完成继续等待...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("futureTask线程计算完毕,此时时间为" + df.format(new Date()));
        Integer privateAccountMoney = null;
        try {
            privateAccountMoney = (Integer) futureTask.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("您现在的总金额为:" + totalMoney +"||"+privateAccountMoney.intValue());
    }
}

class PrivateAccount implements Callable {
    Integer totalMoney;

    @Override
    public Object call() throws Exception {
        Thread.sleep(5000);
        totalMoney = new Integer(new Random().nextInt(10000));
        System.out.println("您当前有" + totalMoney + "在您的私有账户中");
        return totalMoney;
    }
}

执行结果

futureTask线程现在开始启动,启动时间为:2016-11-30 16:34:57
futureTask线程开始执行其他任务
现在你在其他账户中的总金额为17113
等待私有账户总金额统计完毕…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
私有账户计算未完成继续等待…
您当前有8776在您的私有账户中
私有账户计算未完成继续等待…
futureTask线程计算完毕,此时时间为2016-11-30 16:35:02
您现在的总金额为:17113||8776

(3)FutureTask的案例三

import java.util.Date;
import java.text.SimpleDateFormat;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        FutureTask<String> futureTask=new FutureTask<>(new Callable<String>() {
            //@Override
            public String call() throws Exception {
                // TODO Auto-generated method stub
                return "回调完成";
            }
        });
        Thread thread = new Thread(futureTask);
        System.out.println("启动时间为:" + df.format(new Date()));
        thread.start();
        try {
            String str=futureTask.get();

            if(str.equals("回调完成"))
                System.out.println("异步任务完成!");
            else
                System.out.println("Completed!");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

执行结果

启动时间为:2016-12-01 09:37:03
异步任务完成!

(4)FutureTask的案例四

public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        service.submit(futureTask);
        service.shutdown();
        System.out.println("主线程正在执行任务。。。");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            //阻塞直至任务完成
            System.out.println("-------------------------------------");
            System.out.println("执行结果为:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("任务执行完成。。。");
    }

    static class Task implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            try {
                System.out.println("从线程正在执行任务。。。");
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int num = 0;
            for (int i = 0; i <= 100; i++) {
                num += i;
            }
            return num;
        }
    }

futureTask.get()执行时如果该任务已经执行完了则直接返回执行结果,如果没有执行完则线程会阻塞在这里,直至任务执行完毕。还可以用get(long timeout, TimeUnit unit)来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

【5】总结

总的来说Future模式的思想就是在子线程进行执行的时候,主线程不阻塞继续执行。等到主线程需要子线程的结果的时候再去获取子线程的结果(此时子线程没有执行完成的话就会阻塞直至执行完成)。主线程还可以根据一定的业务逻辑去判断是否要取消执行子线程,还可以设置一个超时时间。若阻塞时间超过了超时时间子线程仍然没有执行完成的话,可以直接返回null。

【三】其他案例

【1】FutureTask获取分布式锁

String taskInstanceKey = getTaskInstanceKey(beSubmitedTask);
//对实例加锁
RLock instanceLock = redissonClient.getLock(taskInstanceKey);
FutureTask<Boolean> futureTask = new FutureTask<>(() -> instanceLock.tryLock());
new Thread(futureTask).start();
if (!futureTask.get()) {
    log.info("指标固化获取锁失败{}", taskInstanceKey);
    return;
}

使用futureTask 取调用tryLock尝试获取锁,如果返回值为空,说明获取锁失败

【2】FutureTask监控任务的处理时长是否超时

这里采用FutureTask开启一个线程调用jdbc执行方法,主方法在10分钟后进行查询是否完成,根据isDone的结果去处理,简单介绍下FutureTask几个常用的方法:
(1)cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
(2)isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
(3)isDone方法表示任务是否已经完成,若任务完成,则返回true;
(4)get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
(5)get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
这里使用的是isDone去完成,当然也可以使用et(long timeout, TimeUnit unit)定时去获取返回结果
上代码

public static void readSqlThred(String sqlTest) {
	ExecutorService executor = Executors.newSingleThreadExecutor();
	FutureTask<String> future = new FutureTask<String>(new Callable<String>() {
		@Override
		public String call() throws Exception {
			dealWithSql(sqlTest);//执行sql
			return "执行完成";
		}
	});
	
	executor.execute(future);
	try {
		Thread.sleep(1000*6);
		if(future.isDone()) {
			System.out.println("sql执行完毕");
		}else {
			System.out.println("警告,sql未执行完毕,开始发送邮件..............");
		}
	}catch (InterruptedException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	}
}

这里我是让线程睡了10分钟,10分钟后去取这个方法是否执行完成

【3】FutureTask监控任务的处理时长是否超时(2)

(1)程序中某方法挺耗时,但想超过XX数值则按失败算;
(2)程序中需要调用外部接口,要考虑外部接口不可用,或卡死问题。

针对以上问题,我们都想在程序中给某方法添加超时判断;运行程序等待该方法多长时间,超过则另作处理。


public static void main(String[] args){
	Thread t = null;
	try{
		FutureTask<Boolean> task = new FutureTask<Boolean>(new Callable<Boolean>() {
			public Boolean call() throws Exception {
				Thread.sleep(10*100);//处理方法
				return true;  //返回处理结果
			}
		});
		t = new Thread(task);
		t.start();
		boolean bl = task.get(10*10, TimeUnit.MILLISECONDS); //允许阻塞等待方法处理1秒,超出则抛异常
		
		System.out.println(bl);
		System.out.println(t.getState());
		
	}catch(Exception e1){
		e1.printStackTrace();
		System.out.println(t.getState());
		t.interrupt(); //在超时情况下上面新生成的线程不会关闭直到方法处理完毕,需要中断
	}

}
  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值