多线程实战——Callable接口在项目中的实际应用

1.Callable接口介绍

我们在实现多线程的时候一般都是实现Runnable接口,这种方式简单高效,但缺点也同样明显,就是无法在线程结束后返回相应结果,去过需要返回结果,只能手动在多线程中使用公共变量进行传递,容易发生线程安全问题,所以,现在让我们来看一下可以返回信息的Callable接口的多线程具体使用!

Callable接口定义:

public interface Callable<V> {
    
    
    V call() throws Exception;
}

观察发现:

  • 与Runnable接口相同,Callable接口通让只有一个抽象方法,我们只需要实现此方法即可。
  • Callable接口是一个泛型接口,泛型的类型即为返回值类型,需要手动指定。

现在我们实战来使用Callable接口来实现一个线程:

public class CallableTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        long start = System.currentTimeMillis();
        Callable<String> callable = ()->{
    
    
            System.err.println("线程已开始......");
            Thread.sleep(5000);
            System.err.println("线程即将结束.....");
            return "你好";
        };
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        String result = futureTask.get();
        long time = (System.currentTimeMillis() - start)/1000;
        System.err.println("线程已结束,返回值:" + result + ",用时:" + time);
    }
}

不同于Runnable接口直接使用Thread类运行线程,Callable接口需要使用FutureTask类来包装接口,并通过此类来获取结果!

FutureTask类实现了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;
}
  • boolean cancel(boolean mayInterruptIfRunning);
    此方法用于取消当前线程,并返回是否取消成功
  • boolean isCancelled();
    此方法用于获取线程是否被取消成功
  • boolean isDone();
    此方法用于获取线程是否执行完毕
  • V get() throws InterruptedException, ExecutionException;
    此方法用于在线程执行结束后获取返回结果
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    此方法用与获取线程返回结果,如果在指定时间内线程没有结束,则直接返回NULL

FutureTask类同样实现了Runnable接口,所以我们在包装后直接通过Tread类来运行线程,让我们看一下运行结果:
在这里插入图片描述
我们可以看到,线程开始运行,但是我们调用get()方法会阻塞主线程的运行,直到线程结束后,返回结果后主线程现场才继续运行。

那么可能就有不少人有这样一个疑问:既然get()方法会阻塞当前线程,那么我们使用Callable接口还有什么意义呢,让我们向下看!

2.Callable接口使用实战

设想一下如果我们需要在项目中农调用一个费时的远程接口,并需要拿到相应的结果!

2.1在所有操作结束后调用get()方法等待结果返回即可:

在我们实际开发中,在Controller中我们可能会调用费时远程接口,并且在调用的同时做一些本地的操作,这个时候就可以在Controller开始时就直接开始线程,并且将所有本地操作都做完之后等待远程接口返回即可!

public class CallableTest 
    public static void main(String[] args) throws Exception {
    
    
        long start = System.currentTimeMillis();
        Callable<String> callable = ()->{
    
    
            System.err.println("接口开始调用......");
            Thread.sleep(5000);//使用延时操作模拟调用远程接口
            System.err.println("接口调用结束.....");
            return "你好";
        };
        FutureTask<String> futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.err.println("主线程的剩余操作开始.....");
        Thread.sleep(2000);
        System.err.println("主线程的剩余操作结束.....");
        String result = futureTask.get();
        long time = (System.currentTimeMillis() - start)/1000;
        System.err.println("线程已结束,返回值:" + result + ",用时:" + time);
    }

}

因为Callable线程只是在调用get()方法后阻塞线程等待返回,那么如果这个时候主线程仍有一些费时操作(暂时不需要线程结果的),直接执行即可,在所有操作结束后调用get()方法等待返回即可!

在这里插入图片描述

2.2配合线程池同时执行多个费时操作

如果在一个Controller中需要调用多个远程接口,就可以使用此方法配合线程池使用,Callable接口线程主要应对的就是这种情况!

public class CallableTest {
    
    
    public static void main(String[] args) throws Exception {
    
    
        long start = System.currentTimeMillis();
        Callable<String> c1 = ()->{
    
    
            Thread.sleep(5000);//使用延时操作模拟调用远程接口
            System.err.println("接口1调用结束.....");
            return "返回值1";
        };
        Callable<String> c2 = ()->{
    
    
            Thread.sleep(1000);//使用延时操作模拟调用远程接口
            System.err.println("接口2调用结束.....");
            return "返回值2";
        };
        Callable<String> c3 = ()->{
    
    
            Thread.sleep(4000);//使用延时操作模拟调用远程接口
            System.err.println("接口3调用结束.....");
            return "返回值3";
        };
        Callable<String> c4 = ()->{
    
    
            Thread.sleep(3000);//使用延时操作模拟调用远程接口
            System.err.println("接口4调用结束.....");
            return "返回值4";
        };
        Callable<String> c5 = ()->{
    
    
            Thread.sleep(2000);//使用延时操作模拟调用远程接口
            System.err.println("接口5调用结束.....");
            return "返回值5";
        };
        List<Callable<String>> list = new ArrayList<>();
        list.add(c1);
        list.add(c2);
        list.add(c3);
        list.add(c4);
        list.add(c5);
        ExecutorService executor = Executors.newFixedThreadPool(5);
        List<Future<String>> results =  executor.invokeAll(list);
        executor.shutdownNow();
        long time = (System.currentTimeMillis() - start)/1000;
        System.err.println("操作结束,用时:" + time);
    }
}

我们在一个Controller中分别调用了5个远程接口,那么我们就可以将所有Callable线程对象加入到集合中,使用线程池同时执行!
在这里插入图片描述

可以看到,单线程执行需要15秒的方法,使用Callable配合线程池只需要5秒(最长的一个接口用时)
即可完成,并且在执行后,线程池会自动等待所有线程执行完毕后再向下执行,此时我们如果需要拿到结果,循环调用get()方法即可!

for(Future<String> result : results)
{
    
    
    System.err.println(result.get());
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_42628989/article/details/107392300