第六に、並行プログラミング(スレッドプール)

 

まず、なぜスレッドプールを使用できますか?

 プールのシーンでのJavaスレッドは、スレッドプールを使用することができ、フレーム、またはタスクのほぼすべての必要性の非同期同時実行のほとんどを使用することによって複雑になります。開発中に、スレッドプールの使用の合理化は、3つの利益をもたらすことができます。

まず:リソースの消費を削減します。スレッドを再利用することにより、消費に起因するスレッドの作成と破壊を軽減するために作成されています。

第二:応答速度を向上させることができます。ミッションが到着すると、タスクは、スレッドの作成を直ちに実施することが可能になるまで待つ必要がないかもしれません。T1は、スレッドでタスクを実行するための時間T2をスレッド時間を作成し、T3は、スレッドの時間を破壊する:タスクを完了するために必要なサーバー時間があるとします。場合:T1 + T3がT2よりもはるかに大きい、スレッドプールは、サーバのパフォーマンスを向上させるために用いることができます。スレッドプールは、短くしたりすることにより、サーバプログラムの性能を向上させる、T1、時間のT3技術を調整する方法に焦点を当てています。これT1、T3は、クライアントがサーバプログラムの処理を要求したときに、オーバーヘッドT1、T3のがないことを、スタートアッププログラムとサーバまたはいくつかのアイドル時間期間が終了して配置されています。

第三に:スレッドの管理性を向上させるために。無制限の作成、システムリソースを消費するだけでなく、スレッドプールを使用して、システムの安定性を減らすだけでなく、配布、チューニングや監視を統一することができた場合、スレッドは、希少資源です。

処理サーバに50000のリクエスト日を想定し、各要求は別々のスレッドの完了を必要とします。スレッドプールは、スレッドの数は、一般的に固定されているので、生成されたスレッドの合計数は、プール内のスレッドの数を超えないように、サーバーがスレッド50,000の合計数にこれらの要求を処理するスレッドプールを使用している場合。一般的なスレッドプールのサイズが50000よりもはるかに小さいです。だから、効率を高めるための要求を処理している間に50,000を作成するために時間を無駄にしないスレッドプールのサーバープログラムを使用しています。

二、クラス関係のThreadPoolExecutor

エグゼキュータは基礎エグゼキュータフレームワークであるインタフェースであり、それはタスクのタスク分離にコミットします。

ExecutorServiceのインタフェースは、いくつかのシャットダウンを行う上で執行は、()()拡張を提出延び、それは言うことができる実際のスレッドプールのインターフェイスです。

抽象クラスが実装AbstractExecutorServiceほとんどのメソッドのExecutorServiceのインタフェース。

ThreadPoolExecutor実装クラスが提出されたタスクを実行するためにコアスレッドプール、です。

ScheduledExecutorServiceインターフェースは、「実行サイクル」機能ExecutorServiceので提供ExecutorServiceのインタフェースを拡張します。

ScheduledThreadPoolExecutorのコマンドは、指定された遅延時間後に実行することができ、または定期的に実行するために、実装したものです。より強力なタイマー、より柔軟性のScheduledThreadPoolExecutor。

【図面

 

第三に、各パラメータの意味を作成するには、スレッドプール(スターキー)

    公共 ThreadPoolExecutor(int型corePoolSize、
                               int型maximumPoolSize、
                               長いkeepAliveTimeが、
                              TimeUnitでユニット、
                              BlockingQueueの <Runnableを> ワークキュー){
         この(corePoolSize、maximumPoolSize、keepAliveTimeが、ユニット、ワークキュー、
             Executors.defaultThreadFactory()、たDefaultHandler)。
    }

corePoolSize: 

线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize: 

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

keepAliveTime: 

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用

TimeUnit: keepAliveTime的时间单位

workQueue: 

workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能(定义值的时候最好使用有界值,这样节省资源,一般会使用的阻塞队列为:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue),

常用阻塞队列包括:(这些队列在 并发编程-第五篇(容器中会详细解释))

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

DelayQueue:一个使用优先级队列实现的无界阻塞队列。

SynchronousQueue:一个不存储元素的阻塞队列。

LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

threadFactory: 

创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名,当然还可以更加自由的对线程做更多的设置,比如设置所有的线程为守护线程。只需要创建一个类实现threadFactory接口,实现其中的newThread方法即可。

RejectedExecutionHandler:

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略

(1)AbortPolicy:直接抛出异常,默认策略;

(2)CallerRunsPolicy:用调用者所在的线程来执行任务;

(3)DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

(4)DiscardPolicy:直接丢弃任务;

当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

 

四、拓展线程池(即线程池AOP)

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService threadPool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10),
                new ThreadPoolExecutor.DiscardOldestPolicy()) {
            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println("Ready Execute " + ((Worker) r).getName());
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                System.out.println("Complete Execute " + ((Worker) r).getName());
            }

            @Override
            protected void terminated() {
                System.out.println("thread pool exit ");
            }
        };
  }
  注:重写线程池中的在执行之前执行,在执行完成之后执行,在线程池退出时执行。

五、线程池的工作机制

1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。

3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务。

4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

 

作者俗语:将工作原理看成搬运一批货物,那么首先货物来到,我们需要查看货物的类型,来选择货物是需要什么样的人来搬运,货物就好比任务(任务分为CPU计算型,IO密集型以及混合型),而选择什么样的人搬运和选多少人搬运,这个就需要我们自己来把控,

例如,这批货物有10件,我们可以有三个数量会影响这10件货物的搬运:

1)可以找出多少个空闲的员工来搬运 = 》corePoolSize

2)货物需要及时卸下来,那么我有多大的格子可以存放这些货物 = 》maximumPoolSize

3)最多可以找来多少人搬运 = 》 workQueue

比如:我有3个员工闲着;有4个格子可以放货物,那么我至少还需要再增加3个人来帮我搬这批货物,才能保证这10个货物不会丢失

空闲员工的数量和最多可以找多少人来搬这批货物,由公司内部决定,也就是相对比较固定的

而格子是相对灵活的,那么这里就可以考虑怎么配置这些格子的数量,放少了,不够用,放多了,纯属浪费;所以这个需要酌情而定,而且不要使用无限大的值。

六、提交任务

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

submit()方法用于提交需要返回值的任务。

线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

七、关闭线程池

可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。

它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。

但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。

至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。

おすすめ

転載: www.cnblogs.com/monco/p/12057862.html