天天看点

Java多线程Future和FutureTask模式

FutureTask 和Future最直观的区别是:FutureTask 可以在new FutureTask<>(task)的时候就把线程加进去了最后再submit,Future必须先submit()然后再加入List<Future>

原因是:FutureTask 继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

实现runnable接口,说明可以把FutureTask实例传入到Thread中,在一个新的线程中执行。

实现Future接口,说明可以从FutureTask中通过get取到任务的返回结果,也可以取消任务执行(通过interreput中断)

import java.util.concurrent.Callable;

public class Task implements Callable {
    private int n;

    public int getN() {
        return n;
    }

    public void setN(int n) {
        this.n = n;
    }

    public Task(int n) {
        this.n = n;
    }

    @Override
    public Integer call() throws Exception {
        Thread.sleep (1000);
       
        int sum = 0;
        for(int i=0;i<n;i++){
            sum += i;
        }
        System.out.println(Thread.currentThread ().getName ()+"子线程在进行计算,计算结果"+sum);
        return sum;
    }
}
           

使用Callable+FutureTask获取执行结果

import java.util.concurrent.*;

/**
 * @desc:
 * @Author: Yongkang Hou
 * @Date: 2019/2/22
 */
public class Test {
    public static void main(String[] args) {
        //第一种方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();
         
        //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
        /*Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        Thread thread = new Thread(futureTask);
        thread.start();*/
         
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
         
        System.out.println("主线程在执行任务");
         
        try {
            System.out.println("task运行结果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
         
        System.out.println("所有任务执行完毕");
    }
}
           

如果要同时运行多个子线程,并且得到结果汇总

错误代码实例:

public class Test {
    public static void main(String[] args) {

        long startMili=System.currentTimeMillis();
        //启用5个线程
        ExecutorService executor = Executors.newFixedThreadPool(5);
        System.out.println("主线程在执行任务");

        try {

            int n=0;

            for (int i = 0; i < 10; i++) {

                FutureTask<Integer> futureTask = new FutureTask<Integer>(new Task(i));
                executor.submit(futureTask);
                n+=futureTask.get();

            }
            executor.shutdown();
            System.out.println("task运行结果"+n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long endMili=System.currentTimeMillis();

        System.out.println("所有任务执行完毕,用时"+(endMili-startMili));
    }
}
           

运行结果

主线程在执行任务

pool-1-thread-1子线程在进行计算,计算结果0

pool-1-thread-2子线程在进行计算,计算结果0

pool-1-thread-3子线程在进行计算,计算结果1

pool-1-thread-4子线程在进行计算,计算结果3

pool-1-thread-5子线程在进行计算,计算结果6

pool-1-thread-1子线程在进行计算,计算结果10

pool-1-thread-2子线程在进行计算,计算结果15

pool-1-thread-3子线程在进行计算,计算结果21

pool-1-thread-4子线程在进行计算,计算结果28

pool-1-thread-5子线程在进行计算,计算结果36

task运行结果120

所有任务执行完毕,用时10051

用时10064显然是不对的,原因是futureTask.get();这一步会对当前线程堵塞直到拿到结果。

正确的写法应该是先启动线程,最后统一获取结果。

正确代码实例:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

/**
 * @desc:
 * @Author: Yongkang Hou
 * @Date: 2019/2/22
 */
public class Test {
    public static void main(String[] args) {
   
        long startMili = System.currentTimeMillis ();
        //启用5个线程
        ExecutorService executor = Executors.newFixedThreadPool (5);
        System.out.println ("主线程在执行任务");
        try {

            int n = 0;
            List <FutureTask <Integer>> futureTaskList = new ArrayList <> ();
            for (int i = 0; i < 10; i++) {

                FutureTask <Integer> futureTask = new FutureTask <> (new Task (i));
                executor.submit (futureTask);
                futureTaskList.add (futureTask);

            }
            for (FutureTask <Integer> integerFutureTask : futureTaskList) {
                n += integerFutureTask.get ();
            }
            executor.shutdown ();
            System.out.println ("task运行结果" + n);
        } catch (Exception e) {
            e.printStackTrace ();
        }
        long endMili = System.currentTimeMillis ();
        System.out.println ("所有任务执行完毕,用时" + (endMili - startMili));
    }
}
           

运行结果

主线程在执行任务

pool-1-thread-1子线程在进行计算,计算结果0

pool-1-thread-3子线程在进行计算,计算结果1

pool-1-thread-4子线程在进行计算,计算结果3

pool-1-thread-2子线程在进行计算,计算结果0

pool-1-thread-5子线程在进行计算,计算结果6

pool-1-thread-3子线程在进行计算,计算结果10

pool-1-thread-2子线程在进行计算,计算结果28

pool-1-thread-4子线程在进行计算,计算结果21

pool-1-thread-1子线程在进行计算,计算结果15

pool-1-thread-5子线程在进行计算,计算结果36

task运行结果120

所有任务执行完毕,用时2023

RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

如果异步的执行方法比较简单可以不用定义Task类,直接写内部方法

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {
   static ExecutorService pool = Executors.newFixedThreadPool (5);

    public static void main(String[] args) {
        Long startTime = System.currentTimeMillis ();

        List <Student> list = new ArrayList <> ();
        List <Future <Student>> futureArrayList = new ArrayList <> ();
        Future <Student> future;
        //模拟学生数量
        for (int i = 0; i < 30; i++) {
            int age = i;
            String name = "序号:" + i;
            //批量给学生添加信息
            future = pool.submit (() -> {
                Thread.sleep (1000);
                System.out.println ("线程:" + Thread.currentThread ().getName () + "正在执行");
                Student student = new Student ();
                student.setName (name);
                student.setAge (age);

                return student;
            });


            //此处千万不要直接get()(注释部分),因为future.get ()会对当前子线程堵塞直到获取结果,这样就会照成子线程运行完毕才会进行下一个子线程,失去多线程的效果

//                Student student = future.get ();
//                list.add (student);
//                return list;

            //将子线程结果预加入集合中,最后统一get(),这样就可以达到多线程同时执行的效果。
            futureArrayList.add (future);
        }
        //关闭线程池
        pool.shutdown ();

        //把子线程结果赋给List
        for (Future <Student> futures : futureArrayList) {
            Student student = null;
            try {
                student = futures.get ();
            } catch (Exception e) {
                e.printStackTrace ();
            }
            list.add (student);
        }


        for (Student student : list) {
            System.out.println (student.getName ());
            System.out.println (student.getAge ());
            System.out.println ("___________");
        }
        Long endTime = System.currentTimeMillis ();
        System.out.println ("耗时:" + (endTime - startTime));
    }


}