一体化外墙保温板|苏洋建材新闻资讯

四川建材 2021-10-18 阅读:229

一体化外墙保温板

  在juc-executors框架概述的章节中,我们已经简要介绍过ThreadPoolExecutor了,通过Executors工厂,用户可以创建自己需要的执行器对象。ThreadPoolExecutor,它是J.U.C在JDK1.5时提供的一种实现了ExecutorService接口的执行器,或者说线程池。

  ThreadPoolExecutor并没有自己直接实现ExecutorService接口,因为它只是其中一种Executor的实现而已,所以Doug Lea把一些通用部分封装成一个抽象父类——AbstractExecutorService,供J.U.C中的其它执行器继承。如果读者需要自己实现一个Executor,也可以继承该抽象类。

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第1张

  AbstractExecutorService提供了 ExecutorService 接口的默认实现——主要实现了 submit、invokeAny 、invokeAll这三类方法,如果读者看过上一篇综述文章,就应该知道,ExecutorService的这三类方法几乎都是返回一个Future对象。而Future是一个接口,AbstractExecutorService既然实现了这些方法,必然要实现该Future接口,我们来看下AbstractExecutorService实现的submit方法:

  可以看到,上述方法首先对Runnable和返回值value进行了封装,通过newTaskFor方法,封装成了一个FutureTask对象,然后通过execute方法执行任务,最后返回异步任务对象。

  这里其实是模板方法模式的运用,execute是抽象方法,需要由继承AbstractExecutorService的子类来实现。

  上述需要注意的是newTaskFor方法,该方法创建了一个Future对象:

  FutureTask其实就是Future接口的实现类:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第2张

  我们之前讲过,J.U.C中的Future接口是“Future模式”的多线程设计模式的实现,可以让调用方以异步方式获取任务的执行结果。而FutureTask便是这样一类支持异步返回结果的任务,既然是任务就需要实现Runnable接口,同时又要支持异步功能,所以又需要实现Future接口。J.U.C为了方便,新定义了一个接口——RunnableFuture,该接口同时继承Runnable和Future,代表支持异步处理的任务,而FutureTask便是它的默认实现。

  本节不会在Futrure模式上花费太多笔墨,以后我们会专门讲解J.U.C对Future模式的支持。

  回到ThreadPoolExecutor,从该类的命名也可以看出,这是一种线程池执行器。线程池大家应该并不陌生,应用开发中经常需要用到数据库连接池,数据库连接池里维护着一些数据库连接,当应用需要连接数据库时,并不是自己创建连接,而是从连接池中获取可用连接;当关闭数据库连接时,只是将该连接还给连接池,以供复用。

  而线程池也是类似的概念,当有任务需要执行时,线程池会给该任务分配线程,如果当前没有可用线程,一般会将任务放进一个队列中,当有线程可用时,再从队列中取出任务并执行,如下图:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第3张

  线程池的引入,主要解决以下问题:

  减少系统因为频繁创建和销毁线程所带来的开销;自动管理线程,对使用方透明,使其可以专注于任务的构建。

  了解了线程池和ThreadPoolExecutor的继承体系,接下来,我们来看下J.U.C是如何实现一个普通线程池的。

  我们先来看下ThreadPoolExecutor的构造器,其实之前在讲Executors时已经接触过了,Executors工厂方法创建的三种线程池:newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool,内部都是通过ThreadPoolExecutor的下面这个构造器实例化了ThreadPoolExecutor对象:

  为了用户使用方便,ThreadPoolExecutor一共提供了4种构造器,但其它三种内部其实都调用了上面的构造器。

  正是通过上述参数的组合变换,使得Executors工厂可以创建不同类型的线程池。这里先简要讲一下corePoolSize和maximumPoolSize这两个参数:

  ThreadPoolExecutor在逻辑上将自身管理的线程池划分为两部分:核心线程池(大小对应为corePoolSize)、非核心线程池(大小对应为maximumPoolSize-corePoolSize)。

  当我们向线程池提交一个任务时,将创建一个工作线程——我们称之为Worker,Worker在逻辑上从属于下图中的【核心线程池】或【非核心线程池】,具体属于哪一种,要根据corePoolSize、maximumPoolSize、Worker总数进行判断:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第4张

  注意:我们上面一直在提【工作线程】、【核心线程池】、【非核心线程池】,读者可能都看晕了,包括我自己第一次学习ThreadPoolExecutor时也被网上和垃圾国产技术书籍的错误描述给误导了。我这里先提一下,后面我们分析线程池的任务调度流程时会再详细说明:

  ThreadPoolExecutor中只有一种类型的线程,名叫Worker,它是ThreadPoolExecutor定义的内部类,同时封装着Runnable任务和执行该任务的Thread对象,我们称它为【工作线程】,它也是ThreadPoolExecutor唯一需要进行维护的线程;

  【核心线程池】【非核心线程池】都是逻辑上的概念,ThreadPoolExecutor在任务调度过程中会根据corePoolSize和maximumPoolSize的大小,判断应该如何调度任务.

  到这里,读者可能会思考一个问题:既然是线程池,那么必然有线程池状态,同时也涉及对其中的工作线程(Worker)的管理,ThreadPoolExecutor是如何做的呢?

  ThreadPoolExecutor内部定义了一个AtomicInteger变量——ctl,通过按位划分的方式,在一个变量中记录线程池状态和工作线程数——低29位保存线程数,高3位保存线程池状态:

  可以看到,ThreadPoolExecutor一共定义了5种线程池状态:

  RUNNING : 接受新任务, 且处理已经进入阻塞队列的任务SHUTDOWN : 不接受新任务, 但处理已经进入阻塞队列的任务STOP : 不接受新任务, 且不处理已经进入阻塞队列的任务, 同时中断正在运行的任务TIDYING : 所有任务都已终止, 工作线程数为0, 线程转化为TIDYING状态并准备调用terminated方法TERMINATED : terminated方法已经执行完成

  各个状态之间的流转图:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第5张

  另外,我们刚才也提到工作线程(Worker),Worker被定义为ThreadPoolExecutor的内部类,实现了AQS框架,ThreadPoolExecutor通过一个HashSet来保存工作线程:

  工作线程的定义如下:

  通过Worker的定义可以看到,每个Worker对象都有一个Thread线程对象与它相对应,当任务需要执行的时候,实际是调用内部Thread对象的start方法,而Thread对象是在Worker的构造器中通过getThreadFactory.newThread(this)方法创建的,创建的Thread将Worker自身作为任务,所以当调用Thread的start方法时,最终实际是调用了Worker.run方法,该方法内部委托给runWorker方法执行任务,这个方法我们后面会详细介绍。

  ThreadFactory用来创建单个线程,当线程池需要创建一个线程时,就要调用该类的newThread(Runnable r)方法创建线程(ThreadPoolExecutor中实际创建线程的时刻是在将任务包装成工作线程Worker时)。

  ThreadPoolExecutor在构造时如果用户不指定ThreadFactory,则默认使用Executors.defaultThreadFactory创建一个ThreadFactory,即Executors.DefaultThreadFactory:

  这里的关键是要明白为什么需要用ThreadFactory来创建线程,而不是直接通过new Thread的方式。这个问题在executors框架概述中已经谈过了,这样做的好处是:一来解耦对象的创建与使用,二来可以批量配置线程信息(优先级、线程名称、是否守护线程等),以自由设置池子中所有线程的状态。

  ExecutorService的核心方法是submit方法——用于提交一个待执行的任务,如果读者阅读ThreadPoolExecutor的源码,会发现它并没有覆写submit方法,而是沿用了父类AbstractExecutorService的模板,然后自己实现了execute方法:

  ThreadPoolExecutor的execute方法定义如下:

  上述execute的执行流程可以用下图描述:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第6张

  这里需要特别注意的是 CASE2中的addWorker(null, false),当将任务成功添加到队列后,如果此时的工作线程数为0,就会执行这段代码。

  一般来讲每个工作线程(Worker)都有一个Runnable任务和一个对应的执行线程Thread,当我们调用addWorker方法时,如果不传入相应的任务,那么就只是新建了一个没有任务的工作线程(Worker),该Worker就会从工作队列中取任务来执行(因为自己没有绑定任务)。如果传入了任务,新建的工作线程就会执行该任务。

  所以execute方法的CASE2中,将任务添加到队列后,需要判断工作线程数是否为0,如果是0那么就必须新建一个空任务的工作线程,将来在某一时刻它会去队列取任务执行,否则没有工作线程的话,该队列中的任务永远不会被执行。

  另外,这里又要回到【工作线程】、【核心线程池】、【非核心线程池】、【总线程池】的概念上了。

  再强调一遍,maximumPoolSize限定了整个线程池的大小,corePoolSize限定了核心线程池的大小,corePoolSize≤maximumPoolSize(当相等时表示为固定线程池);maximumPoolSize-corePoolSize表示非核心线程池。

  execute的整个执行流程关键是下面两点:

  如果工作线程数小于核心线程池上限(CorePoolSize),则直接新建一个工作线程并执行任务;如果工作线程数大于等于CorePoolSize,则尝试将任务加入到队列等待以后执行。如果加入队列失败了(比如队列已满的情况),则在总线程池未满的情况下(CorePoolSize ≤ 工作线程数 < maximumPoolSize)新建一个工作线程立即执行任务,否则执行拒绝策略。

  了解了ThreadPoolExecutor的整个执行流程,我们来看下它是如何添加工作线程并执行任务的,execute方法内部调用了addWorker方法来添加工作线程并执行任务:

  整个addWorker的逻辑并不复杂,分为两部分:

  第一部分是一个自旋操作,主要是对线程池的状态进行一些判断,如果状态不适合接受新任务,或者工作线程数超出了限制,则直接返回false。

  这里需要注意的就是core参数,为true时表示新建的工作线程在逻辑上归属于核心线程池,所以需要判断条件 工作线程数 < corePoolSize 是否满足;core为false时表示在新增的工作线程逻辑上属于非核心线程池,所以需要判断条件 工作线程数 < maximumPoolSize是否满足。

  经过第一部分的过滤,第二部分才真正去创建工作线程并执行任务:

  首先将Runnable任务包装成一个Worker对象,然后加入到一个工作线程集合中(名为workers的HashSet),最后调用工作线程中的Thread对象的start方法执行任务,其实最终是委托到Worker的下面方法执行:

  工作线程的执行#

  runWoker用于执行任务,整体流程如下:

  while循环不断地通过getTask方法从队列中获取任务(如果工作线程自身携带着任务,则执行携带的任务);控制执行线程的中断状态,保证如果线程池正在停止,则线程必须是中断状态,否则线程必须不是中断状态;调用task.run执行任务;处理工作线程的退出工作。

  这里要特别注意第一个IF方法,该方法的核心作用,用一句话概括就是:

  确保正在停止的线程池(STOP/TIDYING/TERMINATED)不再接受新任务,如果有新任务那么该任务的工作线程一定是中断状态;确保正常状态的线程池(RUNNING/SHUTDOWN),其所执行的任务都是不能被中断的。

  另外,getTask方法用于从任务队列中获取一个任务,如果获取不到任务,会跳出while循环,最终会通过processWorkerExit方法清理工作线程。注意这里的completedAbruptly字段,它表示该工作线程是否是因为中断而退出,while循环的退出有以下几种可能:

  正常情况下,工作线程会存活着,不断从任务队列获取任务执行,如果获取不到任务了(getTask返回null),会置completedAbruptly 为false,然后执行清理工作——processWorkerExit(worker,false);异常情况下,工作线程在执行过程中被中断或出现其它异常,会置completedAbruptly 为true,也会执行清理工作——processWorkerExit(worker,true);

  processWorkerExit的作用就是将该退出的工作线程清理掉,然后看下线程池是否需要终止。

  processWorkerExit执行完之后,整个工作线程的生命周期也结束了,我们可以通过下图来回顾下它的整个生命周期:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第7张

  getTask方法的主要作用就是:通过自旋,不断地尝试从阻塞队列中获取一个任务,如果获取失败则返回null。

  阻塞队列就是在我们构建ThreadPoolExecutor对象时,在构造器中指定的。由于队列是外部指定的,所以根据阻塞队列的特性不同,getTask方法的执行情况也不同。我们曾经在J.U.C之collections框架系列中全面剖析过J.U.C中的所有阻塞队列:

  队列特性有界队列近似无界队列无界队列特殊队列有锁算法ArrayBlockingQueueLinkedBlockingQueue、LinkedBlockingDeque/PriorityBlockingQueue、DelayQueue无锁算法//LinkedTransferQueueSynchronousQueue

  我们可以根据业务需求、任务特点等选择上表中的某一种阻塞队列,根据Oracle官方文档的提示,任务在阻塞队列中排队一共有三种情况:

  1.直接提交

  即直接将任务提交给等待的工作线程,这时可以选择SynchronousQueue。因为SynchronousQueue是没有容量的,而且采用了无锁算法,所以性能较好,但是每个入队操作都要等待一个出队操作,反之亦然。

  使用SynchronousQueue时,当核心线程池满了以后,如果不存在空闲的工作线程,则试图把任务加入队列将立即失败(execute方法中使用了队列的offer方法进行入队操作,而SynchronousQueue在调用offer时如果没有另一个线程等待出队操作,则会立即返回false),因此会构造一个新的工作线程(未超出最大线程池容量时)。

  由于,核心线程池是很容易满的,所以当使用SynchronousQueue时,一般需要将maximumPoolSizes设置得比较大,否则入队很容易失败,最终导致执行拒绝策略,这也是为什么Executors工作默认提供的缓存线程池使用SynchronousQueue作为任务队列的原因。

  2.无界任务队列

  无界任务队列我们的选择主要有LinkedTransferQueue、LinkedBlockingQueue(近似无界,构造时不指定容量即可),从性能角度来说LinkedTransferQueue采用了无锁算法,高并发环境下性能相对更好,但如果只是做任务队列使用相差并不大。

  使用无界队列需要特别注意系统资源的消耗情况,因为当核心线程池满了以后,会首先尝试将任务放入队列,由于是无界队列所以几乎一定会成功,那么系统瓶颈其实就是硬件了。如果任务的创建速度远快于工作线程处理任务的速度,那么最终会导致系统资源耗尽。Executors工厂中创建固定线程池的方法内部就是用了LinkedBlockingQueue。

  3.有界任务队列

  有界任务队列,比如ArrayBlockingQueue ,可以防止资源耗尽的情况。当核心线程池满了以后,如果队列也满了,则会创建归属于非核心线程池的工作线程,如果非核心线程池也满了 ,才会执行拒绝策略。

  ThreadPoolExecutor在以下两种情况下会执行拒绝策略:

  当核心线程池满了以后,如果任务队列也满了,首先判断非核心线程池有没满,没有满就创建一个工作线程(归属非核心线程池), 一体化外墙保温板 否则就会执行拒绝策略;提交任务时,ThreadPoolExecutor已经关闭了。

  所谓拒绝策略,就是在构造ThreadPoolExecutor时,传入的RejectedExecutionHandler对象:

  ThreadPoolExecutor一共提供了4种拒绝策略:

  1.AbortPolicy(默认)

  AbortPolicy策略其实就是抛出一个RejectedExecutionException异常:

  2.DiscardPolicy

  DiscardPolicy策略其实就是无为而治,什么都不做,等任务自己被回收:

  3.DiscardOldestPolicy

  DiscardOldestPolicy策略是丢弃任务队列中的最近一个任务,并执行当前任务:

  4.CallerRunsPolicy

  CallerRunsPolicy策略相当于以自身线程来执行任务,这样可以减缓新任务提交的速度。

  ExecutorService接口提供两种方法来关闭线程池,这两种方法的区别主要在于是否会继续处理已经添加到任务队列中的任务。

  shutdown方法将线程池切换到SHUTDOWN状态(如果已经停止,则不用切换),并调用interruptIdleWorkers方法中断所有空闲的工作线程,最后调用tryTerminate尝试结束线程池:

  这里要注意,如果执行Runnable任务的线程本身不响应中断,那么也就没有办法终止任务。

  shutdownNow方法的主要不同之处就是,它会将线程池的状态至少置为STOP,同时中断所有工作线程(无论该线程是空闲还是运行中),同时返回任务队列中的所有任务。

  最后,我们来回顾下ThreadPoolExecutor的整体结构,ThreadPoolExecutor的核心方法是execute,控制着工作线程的创建和任务的执行,如下图:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第8张

  同时,ThreadPoolExecutor中有几个比较重要的组件:阻塞队列、核心线程池、拒绝策略,它们的关系如下图,图中的序号表示execute的执行顺序,可以配合上面的流程图来理解:

  一体化外墙保温板|苏洋建材新闻资讯  一体化外墙保温板 第9张

  关于ThreadPoolExecutor这个线程池,最重要的是根据系统实际情况,合理进行线程池参数的设置以及阻塞队列的选择。现实情况下,一般会自己通过ThreadPoolExecutor的构造器去构建线程池,而非直接使用Executors工厂创建,因为这样更利于对参数的控制和调优。

  另外,根据任务的特点,要有选择的配置核心线程池的大小:

  如果任务是 CPU 密集型(需要进行大量计算、处理),则应该配置尽量少的线程,比如 CPU 个数 + 1,这样可以避免出现每个线程都需要使用很长时间但是有太多线程争抢资源的情况;如果任务是 IO密集型(主要时间都在 I/O,CPU 空闲时间比较多),则应该配置多一些线程,比如 CPU 数的两倍,这样可以更高地压榨 CPU。

  ThreadPoolExecutor到此就介绍完了,下一节我们将介绍一种可控制任务执行周期的线程池——ScheduledThreadPoolExecutor,其实我们之前讲ScheduledExecutorService接口的时候已经接触过了,下一节会深入它的实现原理。


外墙岩棉保温板 一体化外墙保温板 外墙保温板每平方米几个胀钉 外墙一体保温板 外墙保温板做法及施工要求 外墙防火保温板 外墙复合保温板设备 外墙保温板怎么安装



这是外墙保温板外墙保温板施工 16:42:40)

评论(0)