【构建并发程序】2-线程池-的注意事项与缺点

线程池-的注意事项与缺点

  • 为何在如下代码中添加sleep?
  • 为什么将Executor对象放在第一位?
  • 如何理解线程池中的shutdown?
  • 如何确保ForkJoinPool(Executor)对象的所有任务都处理完呢?使用awaitTermination方法。---此时就可以不必使用sleep了。
  • 为什么shutdown和awaitTermination要组合使用,且shutdown在前面?

为何在如下代码中添加sleep?

 object ceshi extends App{
 	val pool = new forkjoin.ForkJoinPool(2)
 	val a = ExecutionContext.fromExecutorService(pool)
 	a.execute(new Runnable{
 		def run()=log.info("创建")
 	})
 	Thread.sleep(500)
 }

 用以防止ForkJoinPool对象中的 “守护线程” 在调用Runnable对象中的run方法之前被终止。(默认情况下ForkJoinPool对象创建的线程都是守护线程)

为什么将Executor对象放在第一位?

  • 可以在不影响Runnable对象的情况下轻松更改executor对象。
  • Executor用于去除并发操作逻辑与执行这些操作方式之间的耦合性。这样我们就能够集中精力去处理通过并发方式执行的代码部份,而无需考虑这些代码部份,执行的时机和方式。

如何理解线程池中的shutdown?

1. 注意是ForkJoinPool(Executor)对象处理完所有任务后,shutdown才会停止所有进程,如果没有处理完所有任务,shutdown是不会停止所有进程的

2. 因为ForkJoinPool(Executor)对象中的线程是守护线程,因此shutdown要与awaitTermination组合使用,要不然主线程很快结束,线程还没执行呢,也就结束了

  • ForkJoinPool还实现了一个更为精巧的Executor接口-ExecutorService。
  • ExecutorService扩展了Executor接口,其中最重要的是shutdown方法。使用shutdown方法能够确保Executor对象能够在处理完所有已提交任务后停止所有进程。虽然ForkJoinPool创建的线程都是守护线程(就算不用shutdown,main线程结束后,守护线程也会结束),但我们也应该在程序结束前调用下shutdown。
object one_Executor extends App {
  /**
   * 下面的代码展示了实例化ForkJoinPool接口的方式,
   * 以及对其提交能够通过,异步处理任务的方式
   * */

  import scala.concurrent._

  val executor = new forkjoin.ForkJoinPool()
  executor.execute(new Runnable {
    override def run(): Unit = println("This task is run asynchronously")
  })

  /**
   * 这里加一个sleep的原因是?
   * 用以防止ForkJoinPool对象中的 “守护线程” 在调用Runnable对象中的run方法之前被终止。
   * (默认情况下ForkJoinPool对象创建的线程都是守护线程)
   * */
  //  Thread.sleep(500) //有了awaitTermination就不用在用sleep了
  executor.shutdown() //调用shutdown时,不能再往线程池中添加任何任务,否则将会抛出RejectedExecutionException异常

  //awaitTermination是一个阻塞方法。它必须等待线程池(ForkJoinPool(Executor))“退出”后才会结束自身。(shutdown放在其后面根本不会运行,因为都已经堵塞了)
  // 1、因为是守护线程,main线程没退出,守护线程也不会“退出”,所以就在这堵塞了
  // 2、shutdown放在其前面,awaitTermination等待期间,shutdown也在等待任务的结束。
  // 等待期间,任务结束了shutdown才会关闭线程并“退出”,awaitTermination发现shutdown退出,则表示任务结束,此时会返回true。
  // 等待期间,awaitTermination发现shutdown没有退出,则表示任务没结束,此时会返回false。
  val b: Boolean = executor.awaitTermination(2, TimeUnit.SECONDS)
  println(b) //true
}

如何确保ForkJoinPool(Executor)对象的所有任务都处理完呢?使用awaitTermination方法。—此时就可以不必使用sleep了。

  • 第一个参数指定的是时间,第二个参数指定的是时间单位(当前是秒)。返回值类型为boolean型。
    val b: Boolean = executor.awaitTermination(60,TimeUnit.SECONDS) //此时就可以不用sleep了
  • Main线程执行到shutdown的时候(可能会由于过快,导致守护线程还没有执行,程序就结束了,因此其后再加上一个awaitTermination),当继续执行下方代码awaitTermination后,程序进入等待状态,等待60秒,在这等待期间如果Executor中的守护线程都执行完毕了,那么就会返回一个true,程序结束。(如果等待期间守护线程没执行完,或者超时了都会返回一个false)。

为什么shutdown和awaitTermination要组合使用,且shutdown在前面?

 awaitTermination定义:awaitTermination是一个阻塞方法。它必须等待线程池(ForkJoinPool(Executor))“退出”后才会结束自身。(shutdown放在其后面根本不会运行,因为都已经堵塞了)。

 不写shutdown为什么堵塞:因为是守护线程,main线程没退出,守护线程也不会“退出”, awaitTermination不会退出,所以就在这堵塞了shutdown放在其前面,awaitTermination等待期间,shutdown也在等待任务的结束。

  • A、等待期间,任务结束了shutdown才会关闭线程并“退出”,awaitTermination发现shutdown退出,则表示任务结束,此时会返回true。
  • B、等待期间,awaitTermination发现shutdown没有退出,则表示任务没结束,此时会返回false。

(本文章虽然采用的代码为scala代码,但java代码与Scala代码可以互相转换,且本质上两者所阐述的东西都是一致的)

你可能感兴趣的:(#,构建并发程序,并发编程,Executor,awaitTermina,shutdown,线程池)