java中Runnable,Callable,Future和FutureTask的使用场景

Thread 和 Runnable 的区别:

Thread 才是 Java 里对线程的唯一抽象, Runnable 只是对任务(业务逻辑)的抽象。 Thread 可以接受任意一个 Runnable 的实例并执行。

Callable、 Future 和 FutureTask的关系:

Runnable 是一个接口,在它里面只声明了一个 run()方法, 由于 run()方法返回值为 void 类型,所以在执行完任务之后无法返回任何结果。

 public static void main(String[] args) {
        new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
              System.out.println(Thread.currentThread().getName()+"执行"); 
            }
        }).start();
   }

Callable 位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做 call(), 这是一个泛型接口, call()函数返回的类型就是传递进来的 V 类型。

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Future 就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。
Future接口方法:
在这里插入图片描述
因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就有了 FutureTask 实现类。它既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

FutureTask 实现了RunnableFuture接口:
在这里插入图片描述
RunnableFuture 接口 继承了 RunnableFuture 继承了 Runnable接口和 Future 接口:
在这里插入图片描述
因此在使用Callable 接口来实现线程任务时,可将其封装成一个FutureTask对象,交给Thread执行
程序案例1:

public class CallableTest {

    static class myCallable implements Callable<String>{

        @Override
        public String call() throws Exception {
            return Thread.currentThread().getName()+"的执行结果";
        }
    }
    @SneakyThrows
    public static void main(String[] args) {
    	// FutureTask接收一个Callable类
        FutureTask<String> futureTask= new FutureTask<>(new myCallable());
        // 执行线程任务
        new Thread(futureTask).start();
        // 获取线程执行后的返回值
        String s = futureTask.get();
        System.out.println("获取到"+s);
    }
}

运行结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/1f8c383ce343451287edd5cb5c4d4f53.png
使用CountDownLatch与Callable 实现对多个线程执行结果的汇总调度
程序案例2:

public class CallableTest {

    static CountDownLatch countDownLatch = new CountDownLatch(3);

    static class myCallable1 implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Thread.sleep(1000);
            countDownLatch.countDown();
            return 1;
        }
    }
    static class myCallable2 implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Thread.sleep(3000);
            countDownLatch.countDown();
            return 2;
        }
    }
    static class myCallable3 implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Thread.sleep(2000);
            countDownLatch.countDown();
            return 3;
        }
    }
    @SneakyThrows
    public static void main(String[] args) {
        FutureTask<Integer> futureTask1= new FutureTask<>(new myCallable1());
        FutureTask<Integer> futureTask2= new FutureTask<>(new myCallable2());
        FutureTask<Integer> futureTask3= new FutureTask<>(new myCallable3());
        new Thread(futureTask1).start();
        new Thread(futureTask2).start();
        new Thread(futureTask3).start();
        // 阻塞,等待全部线程执行结束
        countDownLatch.await();
        Integer a = futureTask1.get();
        Integer b = futureTask2.get();
        Integer c = futureTask3.get();
        System.out.println("汇总三个线程的结果:"+(a+b+c));
    }
}

执行结果
在这里插入图片描述
FutureTask的run方法探究:
我们先来看一段程序:

public class CallableTest {
    

    static class myCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName());
            return 1;
        }
    }
    public static void main(String[] args) {
        FutureTask<Integer> futureTask= new FutureTask<>(new myCallable());
        IntStream.range(0,30).forEach(l->{
            futureTask.run();
        });
    }
}

上面的程序里,我们调用30次 futureTask.run() 方法,并打印执行该方法的线程
运行结果:
在这里插入图片描述
结果发现,我们通过futureTask.run() 调用Callable的 call() 方法只执行了一次,执行该方法的是主线程。我们看下FutureTask的源码
在源码中,我们主要观察两部分,其一是FutureTask中定义的几种描述状态转换的静态变量:

	private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

NEW:表示一个新的任务,初始状态
COMPLETING:当任务被设置结果时,处于COMPLETING状态,这是一个中间状态。
NORMAL:表示任务正常结束。
EXCEPTIONAL:表示任务因异常而结束
CANCELLED:任务还未执行之前就调用了cancel(true)方法,任务处于CANCELLED
INTERRUPTING:当任务调用cancel(true)中断程序时,任务处于INTERRUPTING状态,这是一个中间状态。
INTERRUPTED:任务调用cancel(true)中断程序时会调用interrupt()方法中断线程运行,任务状态由INTERRUPTING转变为INTERRUPTED
我们接下来重点关注下上面标亮的两种状态

其二是FutureTask的run()方法

public void run() {
		// 当一次执行run()方法时,state == NEW 1.1
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        // 首次执行时走try中的逻辑 1.2
        try {
        	// 拿到初始化时的callable 1.3
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                // 执行了callcable中的call()方法 1.4
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                // 第一次走下面的set()方法 1.5
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

我们来看下set()方法中的逻辑:

 protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            // 1.6 设置state为NORMAL
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); 
            // 1.7 
            finishCompletion();
        }
    }

接下来进入步骤1.7的 finishCompletion()方法

private void finishCompletion() {
        // assert state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();
		// 1.8 置空 callable 
        callable = null; 
    }

从上面的第一次执行流程 1.1 ---->1.8 我们看到,在call() 方法执行完之后,主要做了两件事:

  1. 将state的状态从NEW 更新到了 NORMAL
  2. 将FutureTask的Callable 对象释放掉,防止因强引用无法正常GC,导致内存泄露

因此当我们再次执行run()方法时就会:

public void run() {
		// 当二次执行run()方法时,state == NORMAL 直接return
        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 { 
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

通过上面的源码分析,我们知道,当调用FutureTask.run()方法后,FutureTask中的状态就会随之发生改变,只要状态不再是任务初始状态NEW的时候,我们都无法再次执行。此外,FutureTask.run()的默认执行线程就是当前线程,并不是单独新开启了一个新的线程来执行。在java中只有Thread.start() 方法可以开启多线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王钧石的技术博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值