ホームページ  >  記事  >  Java  >  Java プログラミング思考の学習クラス (8) 第 21 章 - 同時実行性

Java プログラミング思考の学習クラス (8) 第 21 章 - 同時実行性

php是最好的语言
php是最好的语言オリジナル
2018-08-09 15:01:541282ブラウズ

シーケンシャル プログラミング、つまり、プログラム内のすべてのものはいつでも 1 つのステップしか実行できません。 並行プログラミング、プログラムはプログラムの複数の部分を並行して実行できます。

21.2.1 タスクを定義する

スレッドはタスクを駆動できるため、タスクを記述する方法が必要です。これは Runnable インターフェイスによって提供されます。タスクを定義するには、Runnable インターフェイスを実装し、タスクがコマンドを実行できるように run() メソッドを記述するだけです。
クラスが Runnable から派生する場合、そのクラスには run() メソッドが必要ですが、このメソッドには特別なことは何もありません。組み込み関数は生成されません。スレッドの機能。スレッド動作を実装するには、Runnable接口来提供。要想定义任务,只需实现Runnable接口并编写run()方法,使得该任务可以执行你的命令。
当从Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现线程行为,你必须显式地将一个任务附着到线程上

21.2.3 使用Executor

  FixedThreadPoolCachedThreadPool  

  • FixedThreadPool, 可以一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间,因为你不用为每个任务都固定地付出创建线程的开销。在事件驱动的系统中,需要线程的事件处理器,通过直接从池中获取线程,也可以如你所愿地得到服务。你不会滥用可获得的资源,因为FixedThreadPool使用的Thread对象的数量是有界的。

  注意,在任何线程池中,现有线程在可能的情况下,都会被自动复用。

  • 尽管本书将使用CachedThreadPool,但是也应该考虑在产生线程的代码中使用FiexedThreadPoolCachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选。只有当这种方式会引发问题时,你才需要切换到FixedThreadPool

  • SingleThreadExecutor就像是线程数量为1FixedThreadPool。(它还提供了一种重要的并发保证,其他线程不会(即没有两个线程会)被调用。这会改变任务的加锁需求)
    如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务将使用相同的线程。在下面的示例中,你可以看到每个任务都是按照它们被提交的顺序,并且是在下一个任务开始之前完成的。因此,SingleThreadExecutor会序列化所有提交给它的任务,并会维护它自己(隐藏)的悬挂任务队列。

21.2.4 从任务中产生返回值

  Runnable是执行工作的独立任务,但是它不返回任务值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值,并且必须使用ExecutorService.submit()方法调用它。

21.2.9 编码的变体

  另一种可能会看到的惯用法是自管理的Runnable

  这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口使得你可以继承另一个不同的类,而从Thread继承将不行。

  注意,自管理的Runnable是在构造器中调用的。这个示例相当简单,因此可能是安全的,但是你应该意识到,在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务能够访问处于不稳定状态的对象。这是优选Executor而不是显式地创建Thread对タスクをスレッドに明示的にアタッチする

必要があります。

21.2.3 Executor の使用

FixedThreadPoolCachedThreadPool

FixedThreadPool > を使用すると、負荷の高いスレッド割り当てを事前に一度に実行できるため、スレッド数が制限されます。これにより、タスクごとにスレッドを作成する固定オーバーヘッドを支払う必要がなくなるため、時間を節約できます。イベント駆動型システムでは、スレッドを必要とするイベント ハンドラーは、プールから直接スレッドを取得することで、必要に応じてサービスを提供することもできます。 FixedThreadPool で使用される Thread オブジェクトの数には制限があるため、利用可能なリソースを乱用することはありません。

🎜 どのスレッドプールでも、可能な場合には既存のスレッドが自動的に再利用されることに注意してください。 🎜
  • 🎜この本では CachedThreadPool を使用しますが、スレッドを生成するコードで FiexedThreadPool を使用することも検討する必要があります。 CachedThreadPool は通常、プログラムの実行中に必要な数のスレッドを作成し、古いスレッドをリサイクルするときに新しいスレッドの作成を停止するため、Executor の最初の選択肢として適切です。この方法で問題が発生する場合にのみ、FixedThreadPool に切り替える必要があります。 🎜
  • 🎜SingleThreadExecutor は、スレッド数が 1FixedThreadPool に似ています。 (また、他のスレッドが呼び出されない (つまり、2 つのスレッドが呼び出されない) という重要な同時実行性の保証も提供されます。これにより、タスクのロック要件が変更されます)
    SingleThreadExecutor に尋ねた場合複数のタスクが送信されると、タスクはキューに入れられ、次のタスクが開始される前に各タスクが完了するまで実行されます。すべてのタスクが同じスレッドを使用します。以下の例では、各タスクが送信された順序で次のタスクが開始される前に完了していることがわかります。したがって、SingleThreadExecutor は、送信されたすべてのタスクをシリアル化し、保留中のタスクの独自の (非表示の) キューを維持します。 🎜
🎜21.2.4 タスクからの戻り値の生成🎜🎜 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 で起こった他の変更についても何度も尋ねてきました。ノーベル経済学賞受賞者であるジョセフ・スティグリッツの人生哲学は、「コミットメント増大理論」と呼ばれるもので、次のように説明できます。ベア』

21.2.14 例外のキャッチ

スレッドの性質上、スレッドから抜け出す例外はキャッチできません。例外がタスクの run() メソッドをエスケープすると、この誤った例外をキャッチするための特別な手順を実行しない限り、例外はコンソールに伝播します。 run()方法,它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。

21.3 共享受限资源

  可以把单线程程序当作在问题域求解的单一实体,每次只能做一件事情。

21.3.1 不正确地访问资源

  因为canceled标志是boolean类型的,所以它是原子性的,即诸如赋值和返回值这样的简单操作在发生时没有中断的可能,因此你不会看到这个域处于在执行这些简单操作的过程中的中间状态。

  有一点很重要,那就是要注意到递增程序自身也需要多个步骤,并且在递增过程中任务可能会被纯种机制挂起——也就是说,在Java中,递增不是原子性的操作。因此,如果不保护任务,即使单一的递增也不是安全的。

21.4 终结任务

21.4.3 中断

  Executor上调用shutdownNow(),它将发送一个interrupt()调用给它启动的所有线程。

  Executor 通过调用submit()而不是excutor()来启动任务,就可以持有该任务的上下文。submit()将返回一个泛型的Future<?>,持有这种Future的关键在于你可以在其上调用cancel(),并因此可以使用它来中断某个特定任务。如果你将true传递给cancel(),那么它就会拥有在该线程上调用interrupt()以停止这个线程的权限。因此,cancel()是一个种中断由Excutor启动的单个线程的方式。

  SleepBlock()是可中断的阻塞,而IOBlockedSynchronizedBlocked是不可中断的阻塞。上面三个类的示例证明I/O和在synchronized块上的等待是不可中断的。无论是I/O还是尝试调用synchronized方法,都不需要任何InterruptedException处理器。
从关于上面三个类的示例的输出中可以看到,你能够中断对sleep()的调用(或者任何要求抛出InterruptedException的调用)。但是,你不能中断试图获取synchronized锁或者试图执行I/O操作的线程。这有点令人烦恼,特别是在妊I/O的任务时,因为这意味着IO具有锁住你的多线程程序的潜在可能。特别是对于基于Web的程序,这更是关乎利害。

  对于这类问题,有一个略显笨拙但是有时确实行之有效的解决方案,即关闭任务在其上发生阻塞的底层资源:

21.5 线程之间的协作

21.5.1 wait()与notifyAll()

  wait()使你可以等待某个条件发生变化,而改变这个条件超出了当前方法的控制能力。通常,这种条件将由另一个任务来改变。你肯定不想在你的任务测试这个条件的同时,不断地进行空循环,这被称为忙等待, 通常是一种不良的周期使用方式。因此wait()会在等等外部世界产生变化的时候将任务挂起,并且只有在notify()notifyAll() 发生时,即表示发生了某些感兴趣的事物,这个任务才会被唤醒并去检查所产生的变化。因此,wait()提供了一种在任务之间对活动同步的方式。

  调用sleep()的时候锁并没有被 释放,调用yield()也属于这种情况,理解这一点很重要。
wait(), notify()以及notifyAll()有一个比较特殊的方面,那就是这些方法是基类Object的一个部分,而不是属于Thread

21.3 制限されたリソースの共有

シングルスレッドプログラムは、問題領域を解決する単一のエンティティとみなされ、一度に 1 つのことしか実行できません。

21.3.1 リソースへの不正なアクセス🎜🎜 canceled フラグは boolean 型であるため、アトミック、つまり単純な代入と戻り値であるため、操作は中断することなく発生します。これらの単純な操作を実行している間は、ドメインが中間状態にあることはありません。 🎜🎜 インクリメント手順自体には複数のステップが必要であり、インクリメントプロセス中に純粋なメカニズムによってタスクが一時停止される可能性があることに注意することが重要です。つまり、Java では、インクリメントはアトミック操作ではありません。したがって、タスクを保護しなければ、たとえ 1 回の増分であっても安全ではありません。 🎜

21.4 タスクを終了

🎜21.4.3 割り込み🎜🎜 ExecutorshutdownNow() を呼び出すと、interrupt() それによって開始されたすべてのスレッドに対して呼び出されます。 🎜🎜 <code>Executor は、excutor() の代わりに submit() を呼び出してタスクを開始することで、タスクのコンテキストを保持できます。 submit() は汎用の Future<?> を返します。この Future を保持する鍵は、それを で呼び出すことができることです。 >cancel() なので、特定のタスクを中断するために使用できます。 cancel()true を渡すと、そのスレッドで interrupt() を呼び出してスレッドを停止する権限が与えられます。したがって、cancel() は、Excutor によって開始された単一のスレッドを中断する方法です。 🎜🎜 SleepBlock() は割り込み可能なブロックですが、IOBlockedSynchronizedBlocked は割り込み不可能なブロックです。上記の 3 つのクラスの例は、I/O と synchronized ブロックでの待機が中断されないことを証明しています。 I/O も、synchronized メソッドの呼び出し試行も、InterruptedException ハンドラーを必要としません。
上記の 3 つのクラスの例の出力からわかるように、sleep() (または InterruptedException のスローが必要なその他の呼び出し) への呼び出しを中断できます。 >転送)。ただし、synchronized ロックを取得しようとしているスレッドや、I/O 操作を実行しようとしているスレッドを中断することはできません。これは、特に I/O タスクを実行する場合、IO によってマルチスレッド プログラムがロックされる可能性があることを意味するため、少し面倒です。特に Web ベースのプログラムの場合、これは一大事です。 🎜🎜 この種の問題に対するやや不器用ではあるが、場合によっては効果的な解決策は、タスクがブロックされている基になるリソースを閉じることです。 🎜

21.5 スレッド間の連携

🎜21.5 .1 wait() および NoticeAll() 🎜🎜 wait() を使用すると、特定の条件が変化するのを待つことができますが、この条件の変更は現在のメソッドの制御の範囲を超えています。多くの場合、この条件は別のタスクによって変更されます。タスクがこの条件をテストしている間、空のループを実行し続けることは絶対に望ましくありません。これはビジー待機と呼ばれるもので、一般にサイクルの使い方としては不適切です。したがって、wait() は、notify() または notifyAll() の場合にのみ、外部の変更を待機している間タスクを一時停止します。それが発生した場合は、興味深い何かが発生したことを意味し、タスクが起動されて変更が確認されます。したがって、wait() はタスク間でアクティビティを同期する方法を提供します。 🎜🎜 sleep() を呼び出した場合もロックは解除されません。これを理解することが重要です。
wait()notify()、および notifyAll() には特別な側面があります。つまり、これらのメソッドは基本クラス A です。 スレッドの一部ではなく、オブジェクトの一部です。 🎜🎜 信号を見逃しました。 🎜

21.5.2 Notice() と NotifyAll()

Java のスレッド機構に関する議論の中で、notifyAll() は「以下のすべてのタスク」を起動するという紛らわしい記述があります。これは、wait() 状態にあるタスクは、プログラム内の任意の場所で notifyAll() を呼び出すことによって起動されるという意味ですか?これが当てはまらないことを示す例があります。実際、特定のロックに対して notifyAll() が呼び出された場合、このロックを待機しているタスクのみが起動されます。 notifyAll()将唤醒“所有下在等等的任务”。这是否意味着在程序中任何地方,任何处于wait()状态中的任务都将被任何对notifyAll()的调用唤醒呢?有示例说明情况并非如此——事实上,当notifyAll()因某个特定锁而被调用时,只有等待这个锁的任务才会被唤醒。

21.6 死锁

  由Edsger Dijkstrar提出的哲学家就餐问题是一个经典的死锁例证。

  要修正死锁问题,你必须明白,当以下四个条件同时满足时,就会发生死锁:

  • 互斥条件。任务使用的资源中至少有一个是不能共享的。这里,一根Chopstick一次就只能被一个Philosopher使用。

  • 至少有一个任务它必须持有一个资源且正在等待获取一个当前被别的任务持有的资源。也就是说,要发生死锁,Philosopher必须拿着一根Chopstick并且等待另一根。

  • 资源不能被任务抢占,任务必须把资源释放当作普通事件。Philosopher很有礼貌,他们不会从其他Philosopher那里抢占Chopstick。

  • 必须有循环等待,这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的浆,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家都被锁住。在DeadlockingDiningPhilosophers.java中,因为每个Philosopher都试图先得到右边的Chopstick,然后得到左边的Chopstick,所以发徨了循环等待。

  所以要防止死锁的话,只需破坏其中一个即可。防止死锁最容易的方法是破坏第4个条件。

21.7 新类库中的构件

21.7.1 CountDownLatch

  适用场景:它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。即一个或多个任务需要等待,等待到其它任务,比如一个问题的初始部分,完成为止。

  你可以向CountDownLatch对象设置一个初始值,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值到达0.其他因结束其工作时,可以在访对象上调用countDown()来减小这个计数值。CountDownLatch被设计为只解发一次,计数值不能被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier

  调用countDown()的任务在产生这个调用时并没有被阻塞,只有对await()的调用会被阻塞,直至计数值到达0

  CountDownLatch的典型用法是将一个程序分为n个互相独立的可解决任务,并创建值为nCountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown()。等待问题被解决的任务在这个锁存器上调用await(),将它们自己挂起,直至锁存器计数结束。

21.7.2 CyclicBarrier

  适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一下步骤之前等待,直至所有任务都完成(看起来有些像Join())。它使得所有的并行任务都将在栅栏处列队,因此可以一致地向前移动。

  例如程序赛马程序:HorseRace.java

21.7.3 DelayQueue

  DelayQueue是一个无界的BlockingQueue(同步队列),用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象是最先到期的对象。如果没有到期的对象,那么队列就没有头元素,所以poll()将返回null(也正因为此,我们不能将null放置到这种队列中)。如上所述,DelayQueue

21.6 デッドロック

エドガー・ダイクストラールが提案した食事の哲学者問題は、デッドロックの典型的な例です。

デッドロックの問題を解決するには、次の 4 つの条件が同時に満たされた場合にデッドロックが発生することを理解する必要があります:

  • 相互に排他的な条件。タスクで使用されるリソースの少なくとも 1 つが共有できません。ここでは、箸は一度に一人の哲学者だけが使用できます。 🎜
  • 🎜少なくとも 1 つのタスクがリソースを保持する必要があり、別のタスクが現在保持しているリソースの取得を待機しています。つまり、デッドロックが発生するには、哲学者は 1 つの箸を持ち、別の箸を待たなければなりません。 🎜
  • 🎜 タスクによってリソースをプリエンプトすることはできず、タスクはリソースの解放を通常のイベントとして扱う必要があります。哲学者は礼儀正しく、他の哲学者から箸を取り上げることはありません。 🎜
  • 🎜このとき、あるタスクは他のタスクが保持するリソースを待機し、後者は別のタスクが保持するパルプを待機します。これは、ループ待機が存在するまで続きます。最初のタスクが保持するリソースを待機している間、全員がロックされます。 DeadlockingDiningPhilosophers.java では、各 Philosopher が最初に右側の Chopstick を取得し、次に左側の Chopstick を取得しようとするため、ループ待機が発生します。 🎜
🎜 つまり、デッドロックを防ぎたければ、どちらか 1 つを破壊するだけで済みます。デッドロックを防ぐ最も簡単な方法は、条件 4 に違反することです。 🎜

21.7 新しいクラス ライブラリのコンポーネント

🎜21.7.1 CountDownLatch🎜🎜 適用可能なシナリオ: 1 つ以上のタスクを同期し、他のタスクによって実行される一連の操作の完了を強制的に待機させるために使用されます。タスク。つまり、1 つ以上のタスクは、問題の最初の部分など、他のタスクが完了するまで待機する必要があります。 🎜🎜 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 要素がない場合、リーダーは直接ブロックされます。 🎜

21.7.5 ScheduledExecutor を使用した室温コントローラー

「温室制御システム」は同時実行の問題とみなすことができ、目的の温室イベントはそれぞれ、スケジュールされた時間に実行されるタスクです。
ScheduledThreadPoolExecutor はこの問題を解決できます。このうち、schedule()はタスクを1回実行するために使用され、scheduleAtFixedRate()は指定された時間ごとにタスクを繰り返し実行します。どちらのメソッドも、delayTime パラメーターを受け取ります。実行可能オブジェクトは、将来のある時点で実行されるように設定できます。 ScheduledThreadPoolExecutor可以解决这种问题。其中schedule()用来运行一次任务,scheduleAtFixedRate()每隔规定的时间重复执行任务。两个方法接收delayTime参数。可以将Runnable对象设置为在将来的某个时刻执行。

21.7.6 Semaphre

21.8  仿真

21.8.1 银行出纳员

21.8.2 饭店仿真

  BlockingQueue: 同步队列,当第一个元素为空或不可用时,执行.take()时,等待(阻塞、Blocking)。

  SynchronousQueue

21.7.6 Semaphre

21.8 シミュレーション

21.8.1 銀行窓口

21.8.2 レストランのシミュレーション

BlockingQueue: 最初の要素が空か利用できない場合の同期キュー、 の実行時。 take()、待機 (ブロッキング、ブロッキング)。

SynchronousQueue: 内部容量のないブロッキング キューであるため、各 put() は take() を待機する必要があり、その逆も同様です (つまり、各 take() は put() を待機する必要があります())。それは、物体を誰かに手渡すようなものです。物体を置くテーブルがないので、その人が手を伸ばして物体を受け取る準備ができている場合にのみ作業できます。この場合、SynchronousQueue は、いつでも 1 つの料理だけを提供できるという概念を強化するために、ダイナーの前に設定された場所を表します。

この例で観察すべき非常に重要なことの 1 つは、タスク間の通信にキューを使用する管理の複雑さです。この 1 つの手法により、制御を反転することで並行プログラミングのプロセスが大幅に簡素化されます。タスクは直接相互に干渉せず、キューを介してオブジェクトを相互に送信します。受信タスクはオブジェクトを処理し、オブジェクトにメッセージを送信するのではなく、メッセージとして扱います。可能な限りこの手法に従えば、堅牢な同時システムを構築できる可能性が大幅に高まります。


21.8.3 作業の分散

    21.9 パフォーマンスチューニング
  • 21.9.1 ミューテックステクノロジーの比較

  • 「マイクロベンチマーク」の危険性: この用語は通常、単独でコンテキストから独立した機能のパフォーマンステストを指します。もちろん、「ロックは同期よりも速い」などのアサーションを検証するテストを作成する必要がありますが、これらのテストを作成するときは、コンパイル中および実行時に実際に何が起こっているかを認識する必要があります。
  • この点はコンパイラやランタイムシステムによって異なるため、何が起こるかを正確に知ることは困難ですが、コンパイラが結果の可能性を予測できないようにする必要があります。

  • 通常、Lock を使用する方が synchronized を使用するよりもはるかに効率的であり、synchronized のオーバーヘッドはばらつきが大きすぎるように見えますが、Lock は比較的一貫しています。
これは、synchronized キーワードを決して使用してはいけないという意味ですか?ここで考慮すべき要素は 2 つあります:

まず、相互排他的なメソッドのメソッド本体のサイズです。

第二に、synchronized キーワードによって生成されたコードは、Lock に必要な「lock-try/finally-unlock」イディオムによって生成されたコードよりもはるかに読みやすいです。

コードは書かれるよりも読まれる方がはるかに多い。プログラミングでは、コンピューターとの通信よりも他のユーザーとの通信の方がはるかに重要であるため、コードの読みやすさが非常に重要です。したがって、synchronized キーワードから開始し、パフォーマンス チューニング中にのみそれを Lock オブジェクトに置き換えることが実際的に重要です。

21.9.2 ロックフリーコンテナ

これらのロックフリーウィンドウの一般的な戦略は、リーダーが完了した変更の結果のみを確認できる限り、コンテナへの変更は読み取り操作と同時に行うことができます。変更はコンテナ データ構造の一部の別のコピー (場合によってはデータ構造全体のコピー) に対して実行され、このコピーは変更プロセス中には表示されません。変更が完了した場合にのみ、変更された構造がメインのデータ構造と自動的に交換され、読者は変更を確認できます。 🎜🎜 楽観的ロック 🎜🎜 主にロックのないコンテナから読み取りを行っている限り、ロックの取得と解放のオーバーヘッドが排除されるため、同期されたコンテナよりもはるかに高速になります。ロックフリーのコンテナーに対して少量の書き込みを実行する必要がある場合にはこれが当てはまりますが、何が「少量」とみなされるのでしょうか?これは非常に興味深い質問です。 🎜🎜21.11 まとめ🎜🎜 スレッドの追加の利点は、重量のあるプロセス コンテキスト スイッチ (数千の命令) ではなく、軽量な実行コンテキスト スイッチ (約 100 命令) を提供することです。特定のプロセス内のすべてのスレッドは同じメモリ空間を共有するため、軽量コンテキスト切り替えではプログラムの実行シーケンスとローカル変数のみが変更されます。プロセス スイッチ (ヘビーウェイト コンテキスト スイッチ) は、すべてのメモリ空間を変更する必要があります。 🎜🎜関連記事: 🎜

Java プログラミング思考の学習クラス (6) 第 19 章 - 列挙型

Java プログラミング思考の学習クラス (7) 第 20 章 - 注釈

以上がJava プログラミング思考の学習クラス (8) 第 21 章 - 同時実行性の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。