`
Everyday都不同
  • 浏览: 713424 次
  • 性别: Icon_minigender_1
  • 来自: 宇宙
社区版块
存档分类
最新评论

多线程:如何确定所有任务都执行完成了?

阅读更多

--------------------20180502更新-----------------------------

今天学习到了一个比较强大的类:ExecutorCompletionService,它是将 Executor和BlockQueue结合的jdk类,其实现的主要目的是:提交任务线程,每一个线程任务直线完成后,将返回值放在阻塞队列中,然后可以通过阻塞队列的take()方法返回 对应线程的执行结果!!

所以还可以这样写:

 

ExecutorCompletionService<String> completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5));
        for(int i=0; i<10; i++) {
            int j = i;
            completionService.submit(()-> Thread.currentThread().getName() + "------>" + j);
        }

        try {
            for(int i=0; i<10; i++) {
                Future<String> future = completionService.take();
                if(future != null) {
                    String str = future.get();
                    System.out.println(str);
                }
            }


        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("---------->结束");

 同样可以达到阻塞的效果!(注:其中有用到jdk1.8的lambda表达式~)

---------------------------------------------------------------------------------------------------------

 

之前我有写过一篇博客,是关于多线程写同一个sheet文件的。类似的场景很多,当我们想用多线程提高效率时,面临的关键问题就是线程安全和确定所有任务都完成。线程安全的问题那篇博客有说,就是确保对公共资源的写操作是安全的,比如List的add操作采用synchronized来包装或直接采用线程安全的集合;Sheet的addRow加锁等… 而本篇的重点是“如何确保所有任务都完成,才能进行下一步?”。

 

先来看现象:

 

public static void m() {
    for(int i=0; i<10; i++) {
      int j = i;
      new Thread(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
        }
      }).start();
    }

    System.out.println("---------->结束");
  }

 这段代码创建了10个线程,每个线程的任务会打印当前线程的名字,运行后发现可能出现以下结果(顺序不一定是下面这样):

 

Thread-1------>1
---------->结束

Thread-0------>0
Thread-2------>2
Thread-4------>4
Thread-3------>3
Thread-5------>5
Thread-6------>6
Thread-7------>7
Thread-8------>8
Thread-9------>9

会发现“---------->结束”没有在所有线程都运行完就打印出来了,映射到实际场景就是用多线程去帮我们干活,还没干完呢就直接下一步了,如此的话没有实际意义(除非这个多线程的任务是异步的,其他逻辑不需要等待它完成才能进行)。

 

我们知道,多线程执行任务可以用原始的线程提交(上述代码),也可以用线程池(比较推荐这种方式,便于对线程进行管理)。为了解决上述问题,可以用CountDownLatch计数器,计数器的初始大小要跟任务数的大小一致(跟线程数无关),每执行一次任务,计数器减一(countDown),await()方法会一直阻塞主线程,直到计数器的值减为0,才会释放锁,如此便可以达到确保所有任务都完成才继续下一步的效果。

 

先用原始线程结合计数器的方式来试试效果:

 

public static void m1() {
    CountDownLatch latch = new CountDownLatch(10);
    for(int i=0; i<10; i++) {
      int j = i;
      new Thread(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
          latch.countDown();
        }
      }).start();
    }
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("---------->结束");
  }

 无论运行多少次,会发现"---------->结束"始终会在多线程所有任务都执行完毕后打印,比如某次结果:

 

Thread-0------>0
Thread-1------>1
Thread-2------>2
Thread-3------>3
Thread-4------>4
Thread-7------>7
Thread-8------>8
Thread-9------>9
Thread-5------>5
Thread-6------>6
---------->结束

 

再用线程池结合计数器的方式来尝试:

 

public static void m2() {
    CountDownLatch latch = new CountDownLatch(10);
    ExecutorService es = Executors.newFixedThreadPool(5);
    for(int i=0; i<10; i++) {
      int j = i;
      es.submit(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
          latch.countDown();
        }
      });
    }
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("---------->结束");
  }

 同样地,无论运行多少次,"---------->结束"都会在所有任务都完成以后再进行!比如某次打印结果为:

pool-1-thread-2------>1
pool-1-thread-2------>5
pool-1-thread-3------>2
pool-1-thread-1------>0
pool-1-thread-1------>8
pool-1-thread-1------>9
pool-1-thread-3------>7
pool-1-thread-4------>3
pool-1-thread-2------>6
pool-1-thread-5------>4
---------->结束

 

这充分印证了CountDownLatch计数器的强大!下面我们再看一个比较容易忽略的方式:

 

public static void m3() {
    ExecutorService es = Executors.newFixedThreadPool(5);

    List<Future<String>> list = new ArrayList<>();
    for(int i=0; i<10; i++) {
      int j = i;
      Future<String> future = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
          return Thread.currentThread().getName() + "------>" + j;
        }
      });
      list.add(future);
    }

    try {
      for(Future<String> future : list) {
        System.out.println(future.get());
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }

    System.out.println("---------->结束");
  }

 Callable不用多说,它可以表示一个有返回值的线程,Future则用于接收返回的结果。Future的get方法具有阻塞作用,它会一直阻塞直至获取到结果。Callable&Future一般都是结合线程池来使用。

运行多次,也会发现"---------->结束"总是在最后运行的,同样达到了目的。

 

说到线程池管理线程,需要注意的是比如:

 

Executors.newFixedThreadPool(40)

 

实际上是new了一个corePoolSize=maximumPoolSize的特殊情况的线程池:

 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

 

Executors提供的静态方法创建线程池,内部都是去构造一个ThreadPoolExecutor,只是不同类型的线程池,

 

corePoolSize和maximumPoolSize的大小关系不同,还有采用的任务队列的类型也不同。

 

 

 

关于多线程和线程池的一些知识补充,见以下手工笔记:

 

1.一些多线程的基本概念:

 



 

 

2、线程池类型以及提交线程的过程:

 




 

3、常见的线程相关类的关系图:

 



 

 

 

 

 

  • 大小: 427.1 KB
  • 大小: 472.7 KB
  • 大小: 616.7 KB
分享到:
评论

相关推荐

    C#判断线程池中所有的线程是否已经完成

    C#判断线程池中所有的线程是否已经完成.doc

    单任务多线程多断点下载Demo

    多条线程同时下载一个任务,下载中断时,将以下载的数据大小存入数据库,网络恢复时,自动查询数据库中是否存在未下载完成任务,有就下载,没有就查询任务表执行下一个下载任务; 有必要的话 自己来建一个下载任务表...

    110104010104.rar_MFC多任务_MFC多线程挂起_mfc 多线程_mfc 生产者

    线程是程序独立运行的基本单位,一个程序通过执行多个线程可以提高机器本身资源的利用率,同时也可以完成多任务并行运行的操作,多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。 互斥体 互斥体与临界区...

    多线程操作实例源码

    多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。浏览器就是一个很好的多线程的例子,在浏览器中你可以...

    多线程操作实例源码,,

    多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。浏览器就是一个很好的多线程的例子,在浏览器中你可以...

    批量多线程下发密钥及执行任务

    可以对几百台设备批量完成密钥下发,下发后可以对系统执行批量任务。

    C#多线程系列教程PDF文件

    C#多线程学习(一) 多线程的相关概念 ...多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

    Delphi线程池实现多线程FTP分段下载组件

    Delphi线程池实现多线程FTP分段下载组件 by :renshouren mail:114032666@qq.com QQ:114032666 2019.10.05 使用的组件 1、TIdFTP Indy FTP客户端 2、TThreadsPool 线程池 工作原理及流程 调用本单元,将自动在程序...

    PB9多线程Demo

    注意事项: 1、主进程传入的对象或变量不管...2、“任务线程类”中有一个内部timing类,监控自己是否执行完成,会改标志。“任务管理类”也有一个timing监控“任务信息类”和“任务线程类”的情况,把完成的结束。

    带任务描述及执行时间的线程池执行简易框架

    带任务描述及执行时间的线程池执行框架, 使用Java 编写, 可以方便地完成多线程任务

    C#线程池 所有线程运行完毕

    C#线程池 所有线程运行完毕,只是测试代码,用来弄清楚,打印信息很明确,适合初学者

    C#多任务多线程下载组件V1.1(源代码)

    支持多个下载任务,分块多线程下载,断点续传。 (对比上个版本,完成了遗留的功能,修改了事件触发过程及处理方式,重新整理了代码及注释) 内含两个窗体,可视为演示程序。 DownloadForm 下载窗体,含相关事件...

    C#多任务多线程下载组件V1.1[源代码]

    支持多个下载任务,分块多线程下载,断点续传。 (对比上个版本,完成了遗留的功能,修改了事件触发过程及处理方式,重新整理了代码及注释) 内含两个窗体,可视为演示程序。 DownloadForm 下载窗体,含相关事件...

    C#多线程操作技术

    理解线程的概念 理解.NET中线程的属性和方法 创建和使用线程 理解线程的特点、优点及使用场合 在.NET程序设计中,线程是使用Thread类(或...Timer类:适用于间隔性的完成任务。 ThreadPool类:适用于多个小的线程。

    Java第19讲:多线程(1).txt

    多线程并发执行可以提高程序的效率,可以同时完成多项工作 举例:电脑管家同时进行清理垃圾、查杀修复、优化加速,这就是多线程。 如果是单线程的话,在运行清理垃圾时,其它两个都得在等待,不能运行。 ...

    Python—-多线程—-多任务

    任务1执行一点时间,任务2执行一点时间,交替着来,执行速度够快,就感觉所有任务都在同时执行。 但是,只有在多核CPU上的才叫并行,单核的叫并发 2、并发,并行 并发:指的是任务数多于cpu核数,通过操作系统的各种...

    C#的多线程机制探索

    多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。浏览器就是一个很好的多线程的例子,在浏览器中你可以...

    java多线程编程_java多线程_

    1.讲解了Java多线程的基础, 包括Thread类的核心API的使用。2.讲解了在多线程中对并发访问的控制, 主要就是synchronized的使用, 由于此关键字在使用上非常灵活, 所以书中用了很多案例来介绍此关键字的使用, 为...

    VC学习笔记之四:多线程知识.doc

    多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。浏览器就是一个很好的多线程的例子,在浏览器中你可以...

Global site tag (gtag.js) - Google Analytics