ホームページ >Java >&#&チュートリアル >Java プログラミング思考の学習クラス (8) 第 21 章 - 同時実行性
シーケンシャル プログラミング、つまり、プログラム内のすべてのものはいつでも 1 つのステップしか実行できません。 並行プログラミング、プログラムはプログラムの複数の部分を並行して実行できます。
スレッドはタスクを駆動できるため、タスクを記述する方法が必要です。これは Runnable
インターフェイスによって提供されます。タスクを定義するには、Runnable
インターフェイスを実装し、タスクがコマンドを実行できるように run()
メソッドを記述するだけです。
クラスが Runnable
から派生する場合、そのクラスには run()
メソッドが必要ですが、このメソッドには特別なことは何もありません。組み込み関数は生成されません。スレッドの機能。スレッド動作を実装するには、Runnable
接口来提供。要想定义任务,只需实现Runnable
接口并编写run()
方法,使得该任务可以执行你的命令。
当从Runnable
导出一个类时,它必须具有run()
方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上。
FixedThreadPool
与 CachedThreadPool
FixedThreadPool
, 可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。
注意,在任何线程池中,现有线程在可能的情况下,都会被自动复用。
尽管本书将使用CachedThreadPool
,但是也应该考虑在产生线程的代码中使用FiexedThreadPool
。CachedThreadPool
在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor
的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool
。
SingleThreadExecutor
就像是线程数量为1
的FixedThreadPool
。(它还提供了一种重要的并发保证,其他线程不会(即没有两个线程会)被调用。这会改变任务的加锁需求)
如果向SingleThreadExecutor
提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。在下面的示例中,你可以看到每个任务都是按照它们被提交的顺序,并且是在下一个任务开始之前完成的。因此,SingleThreadExecutor
会序列化所有提交给它的任务,并会维护它自己(隐藏)的悬挂任务队列。
Runnable
是执行工作的独立任务,但是它不返回任务值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable
接口而不是Runnable
接口。在Java SE5中引入的Callable
是一种具有类型参数的泛型,它的类型参数表示的是从方法call()
(而不是run()
)中返回的值,并且必须使用ExecutorService.submit()
方法调用它。
另一种可能会看到的惯用法是自管理的Runnable
。
这与从Thread
继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread
继承将不行。
注意,自管理的Runnable
是在构造器中调用的。这个示例相当简单,因此可能是安全的,但是你应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor
而不是显式地创建Thread对
タスクをスレッドに明示的にアタッチする
🎜 どのスレッドプールでも、可能な場合には既存のスレッドが自動的に再利用されることに注意してください。 🎜
FixedThreadPool
とCachedThreadPool
FixedThreadPool
> を使用すると、負荷の高いスレッド割り当てを事前に一度に実行できるため、スレッド数が制限されます。これにより、タスクごとにスレッドを作成する固定オーバーヘッドを支払う必要がなくなるため、時間を節約できます。イベント駆動型システムでは、スレッドを必要とするイベント ハンドラーは、プールから直接スレッドを取得することで、必要に応じてサービスを提供することもできます。 FixedThreadPool で使用される Thread オブジェクトの数には制限があるため、利用可能なリソースを乱用することはありません。🎜21.2.4 タスクからの戻り値の生成🎜🎜
- 🎜この本では
CachedThreadPool
を使用しますが、スレッドを生成するコードでFiexedThreadPool
を使用することも検討する必要があります。CachedThreadPool
は通常、プログラムの実行中に必要な数のスレッドを作成し、古いスレッドをリサイクルするときに新しいスレッドの作成を停止するため、Executor
の最初の選択肢として適切です。この方法で問題が発生する場合にのみ、FixedThreadPool
に切り替える必要があります。 🎜- 🎜
SingleThreadExecutor
は、スレッド数が1
のFixedThreadPool
に似ています。 (また、他のスレッドが呼び出されない (つまり、2 つのスレッドが呼び出されない) という重要な同時実行性の保証も提供されます。これにより、タスクのロック要件が変更されます)SingleThreadExecutor
に尋ねた場合複数のタスクが送信されると、タスクはキューに入れられ、次のタスクが開始される前に各タスクが完了するまで実行されます。すべてのタスクが同じスレッドを使用します。以下の例では、各タスクが送信された順序で次のタスクが開始される前に完了していることがわかります。したがって、SingleThreadExecutor
は、送信されたすべてのタスクをシリアル化し、保留中のタスクの独自の (非表示の) キューを維持します。 🎜Runnable
は作業を実行する独立したタスクですが、タスク値を返しません。タスクが完了時に値を返すようにしたい場合は、Runnable
インターフェイスの代わりにCallable
インターフェイスを実装できます。 Java SE5 で導入されたCallable
は、型パラメータを持つジェネリック型で、その型パラメータは (run ()
ではなく) メソッドcall()
を表します。ExecutorService.submit()
メソッドを使用して呼び出す必要があります。 🎜🎜21.2.9 コーディングのバリエーション 🎜🎜 あなたが目にするかもしれないもう 1 つのイディオムは、自己管理のRunnable
です。 🎜🎜 これはThread
からの継承と特に変わりませんが、構文が少しわかりにくくなっています。ただし、インターフェイスを実装すると、別のクラスから継承できますが、Thread
から継承すると継承できません。 🎜🎜 自己管理のRunnable
がコンストラクター内で呼び出されることに注意してください。この例は非常に単純であるため、おそらく安全ですが、コンストラクターが終了する前に別のタスクの実行が開始される可能性があるため、コンストラクター内でスレッドを開始すると問題が発生する可能性があることに注意してください。これは、タスクが不安定な状態のオブジェクトにアクセスできることを意味します。これは、Thread オブジェクト
を明示的に作成するよりもExecutor
を選択するもう 1 つの理由です。 🎜🎜21.2.13 スレッドグループ🎜🎜🎜 スレッドグループはスレッドの集合を保持します。スレッド グループの価値は、Joshua Bloch の言葉を引用することで要約できます。「スレッド グループは、無視できる失敗した試みとして考えるのが最善です 🎜 🎜🎜」。
(私のように)スレッド グループの価値を発見するために多くの時間と労力を費やしてきた人なら、何年も同じ質問をしてきたにもかかわらず、なぜこの件に関して Sun から公式の声明がないのかに驚かれるかもしれません。私はそれらについて、Java で起こった他の変更についても何度も尋ねてきました。ノーベル経済学賞受賞者であるジョセフ・スティグリッツの人生哲学は、「コミットメント増大理論」と呼ばれるもので、次のように説明できます。ベア』
スレッドの性質上、スレッドから抜け出す例外はキャッチできません。例外がタスクの run()
メソッドをエスケープすると、この誤った例外をキャッチするための特別な手順を実行しない限り、例外はコンソールに伝播します。 run()
方法,它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。
可以把单线程程序当作在问题域求解的单一实体,每次只能做一件事情。
因为canceled
标志是boolean
类型的,所以它是原子性的,即诸如赋值和返回值这样的简单操作在发生时没有中断的可能,因此你不会看到这个域处于在执行这些简单操作的过程中的中间状态。
有一点很重要,那就是要注意到递增程序自身也需要多个步骤,并且在递增过程中任务可能会被纯种机制挂起——也就是说,在Java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也不是安全的。
Executor
上调用shutdownNow()
,它将发送一个interrupt()
调用给它启动的所有线程。
Executor
通过调用submit()
而不是excutor()
来启动任务,就可以持有该任务的上下文。submit()
将返回一个泛型的Future<?>
,持有这种Future
的关键在于你可以在其上调用cancel()
,并因此可以使用它来中断某个特定任务。如果你将true
传递给cancel()
,那么它就会拥有在该线程上调用interrupt()
以停止这个线程的权限。因此,cancel()
是一个种中断由Excutor
启动的单个线程的方式。
SleepBlock()
是可中断的阻塞,而IOBlocked
和SynchronizedBlocked
是不可中断的阻塞。上面三个类的示例证明I/O和在synchronized
块上的等待是不可中断的。无论是I/O还是尝试调用synchronized
方法,都不需要任何InterruptedException
处理器。
从关于上面三个类的示例的输出中可以看到,你能够中断对sleep()
的调用(或者任何要求抛出InterruptedException
的调用)。但是,你不能中断试图获取synchronized
锁或者试图执行I/O操作的线程。这有点令人烦恼,特别是在妊I/O的任务时,因为这意味着IO具有锁住你的多线程程序的潜在可能。特别是对于基于Web的程序,这更是关乎利害。
对于这类问题,有一个略显笨拙但是有时确实行之有效的解决方案,即关闭任务在其上发生阻塞的底层资源:
wait()
使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。通常,这种条件将由另一个任务来改变。你肯定不想在你的任务测试这个条件的同时,不断地进行空循环,这被称为忙等待, 通常是一种不良的周期使用方式。因此wait()
会在等等外部世界产生变化的时候将任务挂起,并且只有在notify()
或notifyAll()
发生时,即表示发生了某些感兴趣的事物,这个任务才会被唤醒并去检查所产生的变化。因此,wait()
提供了一种在任务之间对活动同步的方式。
调用sleep()
的时候锁并没有被 释放,调用yield()
也属于这种情况,理解这一点很重要。 wait()
, notify()
以及notifyAll()
有一个比较特殊的方面,那就是这些方法是基类Object
的一个部分,而不是属于Thread
canceled
フラグは boolean
型であるため、アトミック、つまり単純な代入と戻り値であるため、操作は中断することなく発生します。これらの単純な操作を実行している間は、ドメインが中間状態にあることはありません。 🎜🎜 インクリメント手順自体には複数のステップが必要であり、インクリメントプロセス中に純粋なメカニズムによってタスクが一時停止される可能性があることに注意することが重要です。つまり、Java では、インクリメントはアトミック操作ではありません。したがって、タスクを保護しなければ、たとえ 1 回の増分であっても安全ではありません。 🎜Executor
で shutdownNow()
を呼び出すと、interrupt() それによって開始されたすべてのスレッドに対して呼び出されます。 🎜🎜 <code>Executor
は、excutor()
の代わりに submit()
を呼び出してタスクを開始することで、タスクのコンテキストを保持できます。 submit()
は汎用の Future<?>
を返します。この Future
を保持する鍵は、それを で呼び出すことができることです。 >cancel()
なので、特定のタスクを中断するために使用できます。 cancel()
に true
を渡すと、そのスレッドで interrupt()
を呼び出してスレッドを停止する権限が与えられます。したがって、cancel()
は、Excutor
によって開始された単一のスレッドを中断する方法です。 🎜🎜 SleepBlock()
は割り込み可能なブロックですが、IOBlocked
と SynchronizedBlocked
は割り込み不可能なブロックです。上記の 3 つのクラスの例は、I/O と synchronized
ブロックでの待機が中断されないことを証明しています。 I/O も、synchronized
メソッドの呼び出し試行も、InterruptedException
ハンドラーを必要としません。 sleep()
(または InterruptedException
のスローが必要なその他の呼び出し) への呼び出しを中断できます。 >転送)。ただし、synchronized
ロックを取得しようとしているスレッドや、I/O 操作を実行しようとしているスレッドを中断することはできません。これは、特に I/O タスクを実行する場合、IO によってマルチスレッド プログラムがロックされる可能性があることを意味するため、少し面倒です。特に Web ベースのプログラムの場合、これは一大事です。 🎜🎜 この種の問題に対するやや不器用ではあるが、場合によっては効果的な解決策は、タスクがブロックされている基になるリソースを閉じることです。 🎜wait()
を使用すると、特定の条件が変化するのを待つことができますが、この条件の変更は現在のメソッドの制御の範囲を超えています。多くの場合、この条件は別のタスクによって変更されます。タスクがこの条件をテストしている間、空のループを実行し続けることは絶対に望ましくありません。これはビジー待機と呼ばれるもので、一般にサイクルの使い方としては不適切です。したがって、wait()
は、notify()
または notifyAll()
の場合にのみ、外部の変更を待機している間タスクを一時停止します。それが発生した場合は、興味深い何かが発生したことを意味し、タスクが起動されて変更が確認されます。したがって、wait()
はタスク間でアクティビティを同期する方法を提供します。 🎜🎜 sleep()
を呼び出した場合もロックは解除されません。これを理解することが重要です。 wait()
、notify()
、および notifyAll()
には特別な側面があります。つまり、これらのメソッドは基本クラス A です。 スレッド
の一部ではなく、オブジェクト
の一部です。 🎜🎜 信号を見逃しました。 🎜 Java のスレッド機構に関する議論の中で、notifyAll()
は「以下のすべてのタスク」を起動するという紛らわしい記述があります。これは、wait()
状態にあるタスクは、プログラム内の任意の場所で notifyAll()
を呼び出すことによって起動されるという意味ですか?これが当てはまらないことを示す例があります。実際、特定のロックに対して notifyAll()
が呼び出された場合、このロックを待機しているタスクのみが起動されます。 notifyAll()
将唤醒“所有下在等等的任务”。这是否意味着在程序中任何地方,任何处于wait()
状态中的任务都将被任何对notifyAll()
的调用唤醒呢?有示例说明情况并非如此——事实上,当notifyAll()
因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。
由Edsger Dijkstrar提出的哲学家就餐问题是一个经典的死锁例证。
要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:
互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用。
至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。
资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢占Chopstick。
必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的浆,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophers.java中,因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发徨了循环等待。
所以要防止死锁的话,只需破坏其中一个即可。防止死锁最容易的方法是破坏第4个条件。
适用场景:它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。即一个或多个任务需要等待,等待到其它任务,比如一个问题的初始部分,完成为止。
你可以向CountDownLatch
对象设置一个初始值,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值到达0.其他因结束其工作时,可以在访对象上调用countDown()来减小这个计数值。CountDownLatch
被设计为只解发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier
。
调用countDown()
的任务在产生这个调用时并没有被阻塞,只有对await()
的调用会被阻塞,直至计数值到达0
。
CountDownLatch
的典型用法是将一个程序分为n
个互相独立的可解决任务,并创建值为n
的CountDownLatch
。当每个任务完成时,都会在这个锁存器上调用countDown()
。等待问题被解决的任务在这个锁存器上调用await()
,将它们自己挂起,直至锁存器计数结束。
适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一下步骤之前等待,直至所有任务都完成(看起来有些像Join())。它使得所有的并行任务都将在栅栏处列队,因此可以一致地向前移动。
例如程序赛马程序:HorseRace.java
DelayQueue
是一个无界的BlockingQueue
(同步队列),用于放置实现了Delayed
接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象是最先到期的对象。如果没有到期的对象,那么队列就没有头元素,所以poll()
将返回null
(也正因为此,我们不能将null
放置到这种队列中)。如上所述,DelayQueue
CountDownLatch
オブジェクトに初期値を設定できます。このオブジェクトで wait() を呼び出すメソッドは、カウント値が 0 に達するまでブロックされます。他の要因が作業を完了すると、countDown() を呼び出すことができます。 ) をアクセスオブジェクトに適用すると、カウント値が減少します。 CountDownLatch
は 1 回だけ発行されるように設計されており、カウント値はリセットできません。カウントをリセットするバージョンが必要な場合は、CyclicBarrier
を使用できます。 🎜🎜 この呼び出しが行われたとき、countDown()
を呼び出すタスクはブロックされません。カウント値が 0 に達するまで、<code>await()
の呼び出しのみがブロックされます。 コード>。 🎜🎜 CountDownLatch
の一般的な使用法は、プログラムを n
個の独立した解決可能なタスクに分割し、値 n
を持つ を作成することです >CountDownLatch
コード>。各タスクが完了すると、このラッチで countDown()
が呼び出されます。問題の解決を待っているタスクは、このラッチで await()
を呼び出し、ラッチ カウントが終了するまでタスク自体を一時停止します。 🎜🎜21.7.2 CyclicBarrier🎜🎜 作業を並行して実行する一連のタスクを作成し、すべてのタスクが完了するまで待ってから次のステップに進みたい状況に適しています (Join() に少し似ています)。これにより、すべての並列タスクがフェンスでキューに入れられるため、均一に前進します。 🎜🎜 たとえば、競馬プログラム:HorseRace.java🎜🎜21.7.3 DelayQueue🎜🎜 DelayQueue
は、無制限の BlockingQueue
(同期キュー) であり、 Delayed
インターフェイスのオブジェクト。オブジェクトは有効期限が切れたときにのみキューから削除できます。このキューには順序が付けられています。つまり、先頭のオブジェクトが最初に期限切れになります。期限切れのオブジェクトがない場合、キューには head 要素がないため、poll()
は null
を返します (このため、null を渡すことはできません)コード> コード> がこのキューに配置されます)。前述したように、<code>DelayQueue
はプライオリティ キューの一種になります。 🎜🎜21.7.4 PriorityBlockingQueue🎜🎜 これは、読み取り操作をブロックする非常に基本的な優先キューです。このキューのブロック特性により、必要なすべての同期が提供されるため、ここでは明示的な同期は必要ないことに注意してください。キューから読み取るときに、このキューに要素があるかどうかを心配する必要はありません。 queue 要素がない場合、リーダーは直接ブロックされます。 🎜 「温室制御システム」は同時実行の問題とみなすことができ、目的の温室イベントはそれぞれ、スケジュールされた時間に実行されるタスクです。 ScheduledThreadPoolExecutor
はこの問題を解決できます。このうち、schedule()はタスクを1回実行するために使用され、scheduleAtFixedRate()は指定された時間ごとにタスクを繰り返し実行します。どちらのメソッドも、delayTime パラメーターを受け取ります。実行可能オブジェクトは、将来のある時点で実行されるように設定できます。 ScheduledThreadPoolExecutor
可以解决这种问题。其中schedule()用来运行一次任务,scheduleAtFixedRate()每隔规定的时间重复执行任务。两个方法接收delayTime参数。可以将Runnable对象设置为在将来的某个时刻执行。
BlockingQueue
: 同步队列,当第一个元素为空或不可用时,执行.take()时,等待(阻塞、Blocking)。
SynchronousQueue
BlockingQueue
: 最初の要素が空か利用できない場合の同期キュー、 の実行時。 take()、待機 (ブロッキング、ブロッキング)。 SynchronousQueue
: 内部容量のないブロッキング キューであるため、各 put() は take() を待機する必要があり、その逆も同様です (つまり、各 take() は put() を待機する必要があります())。それは、物体を誰かに手渡すようなものです。物体を置くテーブルがないので、その人が手を伸ばして物体を受け取る準備ができている場合にのみ作業できます。この場合、SynchronousQueue は、いつでも 1 つの料理だけを提供できるという概念を強化するために、ダイナーの前に設定された場所を表します。 この例で観察すべき非常に重要なことの 1 つは、タスク間の通信にキューを使用する管理の複雑さです。この 1 つの手法により、制御を反転することで並行プログラミングのプロセスが大幅に簡素化されます。タスクは直接相互に干渉せず、キューを介してオブジェクトを相互に送信します。受信タスクはオブジェクトを処理し、オブジェクトにメッセージを送信するのではなく、メッセージとして扱います。可能な限りこの手法に従えば、堅牢な同時システムを構築できる可能性が大幅に高まります。
21.8.3 作業の分散
21.9.1 ミューテックステクノロジーの比較
この点はコンパイラやランタイムシステムによって異なるため、何が起こるかを正確に知ることは困難ですが、コンパイラが結果の可能性を予測できないようにする必要があります。
Java プログラミング思考の学習クラス (6) 第 19 章 - 列挙型
Java プログラミング思考の学習クラス (7) 第 20 章 - 注釈
以上がJava プログラミング思考の学習クラス (8) 第 21 章 - 同時実行性の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。