zoukankan      html  css  js  c++  java
  • 对线程,线程池,及线程返回值的一点总结

    1 线程的实现方式,无论怎么封装,只有三种,本质上就是两种实现方法,对run()方法的重写和对call()方法的重写,继承Thread类和实现Runnable接口都是对run()方法的重写,而实现Callable()接口则是对call()方法的重写

    run()方法不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则更麻烦一些。

    (1)Callable规定的方法是call(),而Runnable规定的方法是run()。
    
    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
    
    (3)call()方法可抛出异常,而run()方法是不能抛出异常的。
    
    (4)运行Callable任务可拿到一个Future对象。

    Callable任务返回Future对象。即:Callable和Future一个产生结果,一个拿到结果。

    Future 表示异步计算的结果。Future接口中有如下方法:

    •     boolean cancel(boolean mayInterruptIfRunning)

    取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束

    •     boolean isCancelled() 

    任务是否已经取消,任务正常完成前将其取消,则返回 true

    •     boolean isDone()

    任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true

    •     V get()

    等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException

    •     V get(long timeout, TimeUnit unit) 

    同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出TimeoutException

    Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果。也可以设置任务执行的超时时间,这个设置超时的方法就是实现Java程序执行超时的关键。

    2 线程的初始化,线程可以通过线程池初始化

    Java通过Excutors提供四种线程池的实现,具体可以百度或查阅JDk文档,这里只讨论线程池初始化线程的方式

    先初始化一个线程池,

     ExecutorService mExecutor = Executors.newSingleThreadExecutor();(四种方式中的一种)

    (1) 初始化一个Runnable()的线程

      mExecutor.submit(new Runnable() {

      @Override
      public void run() {
      fibc(20);
      }
      });

    斐波那契数列的实现方法,后面都是用这个,不再一一都写

    static int fibc(int num) {
    if (num == 0) {
    return 0;
    }
    if (num == 1) {
    return 1;
    }
    return fibc(num - 1) + fibc(num - 2);
    }

    这个线程是没有返回值的,当然你可以用Future来接收,不过get()的也是null

    如:

    Future<?> future = mExecutor.submit(new Runnable() {

    @Override
    public void run() {
    fibc(20);
    }
    });

    future.get()值为null

    (2)  初始化一个Callable()的线程,我们知道Callable()线程是带返回值的

    Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    return fibc(20);
    }
    });

    可以拿到返回值result2.get(),值为6765

    (3) 用futureTask来初始化一个Callable()线程

    FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
    return fibc(20);
    }
    });

      mExecutor.submit(futureTask);

    这个线程的返回值是这样获取的futureTask.get(),值为6765

    3 对多个线程返回值队列的处理

    (1) 方式1 自己写集合来实现获取线程池中任务的返回结果 

    public void testByQueue() throws Exception {
    // 创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(5);
    BlockingQueue<Future<String>> queue = new LinkedBlockingQueue<Future<String>>();

    // 向里面扔任务
    for (int i = 0; i < 5; i++) {
    Future<String> future = pool.submit(new MyThread("Thread" + i));
    queue.add(future);
    }

    // 检查线程池任务执行结果
    for (int i = 0; i < 5; i++) {
    System.out.println("method1:" + queue.take().get());
    }

    // 关闭线程池
    pool.shutdown();
    }

    方法2 通过CompletionService来实现获取线程池中任务的返回结果  

    public void testByCompetion() throws Exception {
    // 创建线程池
    ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
    CompletionService<String> cService = new ExecutorCompletionService<String>(pool);

    // 向里面扔任务
    for (int i = 0; i < TOTAL_TASK; i++) {
    cService.submit(new MyThread("Thread" + i));
    }

    // 检查线程池任务执行结果
    for (int i = 0; i < TOTAL_TASK; i++) {
    Future<String> future = cService.take();
    System.out.println("method2:" + future.get());
    }

    // 关闭线程池
    pool.shutdown();
    }

    使用方法一,自己创建一个集合来保存Future存根并循环调用其返回结果的时候,主线程并不能保证首先获得的是最先完成任务的线程返回值。它只是按加入线程池的顺序返回。因为take方法是阻塞方法,后面的任务完成了,前面的任务却没有完成,主程序就那样等待在那儿,只到前面的完成了,它才知道原来后面的也完成了。

    使用方法二,使用CompletionService来维护处理线程不的返回结果时,主线程总是能够拿到最先完成的任务的返回值,而不管它们加入线程池的顺序。

    (2) 使用CompletionService

    public interface CompletionService<V>
    将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。
    1. public class CompleteServiceTest {  
    2. public static void main(String[] args) throws InterruptedException, ExecutionException {  
    3.     ExecutorService executorService = Executors.newFixedThreadPool(10);  
    4.     CompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);  
    5. /** 
    6.      * 产生一个随机数,模拟不同的任务的处理时间不同 
    7.      */  
    8. for (int i = 0; i < 10; i++) {  
    9.         completionService.submit(new Callable<String>() {  
    10. public String call(){  
    11. int rnt = new Random().nextInt(5);  
    12. try {  
    13.                     Thread.sleep(rnt*1000);  
    14.                 } catch (InterruptedException e) {  
    15. // TODO Auto-generated catch block  
    16.                     e.printStackTrace();  
    17.                 }  
    18.                 System.out.println("run rnt = "+rnt);  
    19. return String.valueOf(rnt*1000);  
    20.             }  
    21.         });  
    22.     }  
    23. /** 
    24.      * 获取结果时,总是先拿到队列上已经存在的对象,这样不用依次等待结果 
    25.      * 显然效率更高 
    26.      */  
    27. for (int i = 0; i < 10; i++) {  
    28.         Future<String> future = completionService.take();  
    29.         System.out.println(future.get());  
    30.     }  
    31.     executorService.shutdown();  
    32. }  
    33. }  

      

          通过CompletionService包装ExecutorService,然后调用其take()方法去取Future对象。

      以前没研究过这两者之间的区别。今天看了源代码之后就明白了。

    ExecutorService 与 CompletionService 两者最主要的区别在于submit的task不一定是按照加入自己维护的list顺序完成的。

    从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。

    而CompletionService的实现是维护一个保存Future对象的BlockingQueue。只有当这个Future对象状态是结束的时候,才会加入到这个Queue中,take()方法其实就是Producer-Consumer中的Consumer。它会从Queue中取出Future对象,如果Queue是空的,就会阻塞在那里,直到有完成的Future对象加入到Queue中。

    所以,先完成的必定先被取出。这样就减少了不必要的等待时间

      

  • 相关阅读:
    ElasticSearch7.6学习使用及问题总结
    phpstorm2020.1破解及使用
    大规模网站开发技术
    备份数据库、恢复数据库、定时
    Centos7系统tmp目录下文件默认保留时长
    linux删除指定文件夹中某个文件除外的其他文件
    python resource模块使用
    python logging 日志轮转文件不删除问题的解决方法
    linux 常用命令快捷键
    shell学习笔记(4)
  • 原文地址:https://www.cnblogs.com/zhaoblog/p/8550018.html
Copyright © 2011-2022 走看看