JAVA多线程—Callable详解

目录

1.两种接口的区别

2.Callable两种执行方式

2.1借助FutureTask执行

2.1.1什么是Future

2.2借助线程池来运行执行

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。

3.示例和结果


1.两种接口的区别

  • 与使用Runnable接口相比, Callable功能更强大些

    • 相比run()方法,可以有返回值

    • 方法可以抛出异常

    • 支持泛型的返回值(需要借助FutureTask类,获取返回结果)

  • Callable规定的方法是call(),Runnable规定的方法是run().
      //Callable 接口
      public interface Callable {
         V call() throws Exception;
      }
      // Runnable 接口
      public interface Runnable {
          public abstract void run();
      }
    
  • 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可cancel()取消任务的执行,还可get()获取执行结果。
  • 缺点:在获取分线程执行结果的时候,当前线程(或是主线程)受阻塞,效率较低。

 2.Callable两种执行方式

   2.1借助FutureTask执行
   2.1.1什么是Future

它有以下五种方法:

public interface Future {
    boolean cancel(boolean mayInterruptIfRunning);
    //isCancelled:如果此任务在正常完成之前被取消,则返回true。
    boolean isCancelled();
    //isDone:如果此任务完成,则返回true。
    boolean isDone();
    //get:等待任务完成,然后返回其结果。是一个阻塞方法
    V get() throws InterruptedException, ExecutionException;
    //get(long timeout, TimeUnit unit):等待任务完成,然后返回其结果。
    //如果在指定时间内,还没获取到结果,就直接返回null。
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
/*cancel:用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
*参数mayInterruptIfRunning表示是否取消正在执行却没有执行完毕的任务,如果设置true,
*则表示可以取消正在执行过程中的任务。
*如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,
*若mayInterruptIfRunning设置为false,不会取消恩物,返回false;如果任务还没有执行则无论*mayInterruptIfRunning为true还是false,肯定返回true。
*如果任务已经完成则无论mayInterruptIfRunning为true还是false,一定会返回false;*/
  • FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
  • FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。

例如:创建Callable线程计算和并获取

//1.创建一个实现Callable的实现类
class MyCallable implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        System.out.println("子线程在进行计算");
        int sum = 0;
        for (int i = 0;i < 5; i++) {
            sum = sum + i;
        }
        return sum;
    }
}
public class TestMyCallable {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        MyCallable mc = new MyCallable();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask ft = new FutureTask<>(mc);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(ft).start();

        System.out.println("主线程在执行任务");

        //6.get()接收返回值
        try {
            Object sum = ft.get();
            System.out.println("和为:"+sum);
        } catch (ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("所有任务执行完毕");
    }
}

执行结果: 

JAVA多线程—Callable详解_第1张图片

   2.2借助线程池来运行执行

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

JAVA多线程—Callable详解_第2张图片

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    void execute(Runnable command) 执行任务/命令,没有返回值,一般用来执行Runnable
    Future submit(Callable task) 执行任务,有返回值,一般又来执行Callable
    void shutdown()  关闭连接池
  • Executors:一个线程池的工厂类,通过此类的静态工厂方法可以创建多种类型的线程池对象。
    Executors.newCachedThreadPool() 创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor() 创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

3.示例和结果:

class MyCallable implements Callable {
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() {
        System.out.println("Callable子线程在进行计算偶数和");
        int evenSum = 0;
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                evenSum += i;
            }
        }
        return evenSum;
    }
}

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("Runnable子线程在进行计算奇数和");
        int oddSum = 0;
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                oddSum += i;
            }
        }
        System.out.println("奇数和为:"+oddSum);
        System.out.println("Runnable子线程执行完毕");
    }
}

public class TestMyCallable2 {
    public static void main(String[] args) {
        System.out.println("主线程在执行任务");
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new MyRunnable());//适合适用于Runnable

        try {
            Future future = service.submit(new MyCallable());//适合使用于Callable
            System.out.println("偶数和为:" + future.get());
            System.out.println("Callable子线程执行完毕");
        } catch (Exception e) {
            e.printStackTrace();
        }
        //3.关闭连接池
        service.shutdown();
        System.out.println("所有任务执行完毕");
    }
}

 运行结果:

JAVA多线程—Callable详解_第3张图片

你可能感兴趣的:(JAVA,java,开发语言)