Javaマルチスレッドプログラミングで注意すべきいくつかのキーを例に挙げて解析します.

7421 ワード

1.同期方法または同期コードブロック?このメソッド呼び出しを同期化するか、またはメソッドのスレッドセキュリティサブセットのみを同期化するかをたまに考えることがあります.これらの場合、Javaコンパイラがソースコードをバイトコードに変換するときに役立つことを知っておくと、同期方法と同期コードブロックを処理する方法がまったく異なります.JVMが同期メソッドを実行すると、実行中のスレッドはメソッドのmethod_を識別するinfo構造にACCがあるかどうかSYNCRONIZEDタグ設定は、オブジェクトのロックを自動的に取得し、メソッドを呼び出し、最後にロックを解除します.異常が発生した場合、スレッドは自動的にロックを解除します.一方,一つのメソッドブロックを同期化すると,JVMによるオブジェクトロックの取得と例外処理の組み込みサポートを越え,バイトコードで明示的な書き込み機能が要求される.同期メソッドを使用してメソッドのバイトコードを読み込むと、この機能を管理するための追加の操作が十数件表示されます.インベントリ1は、同期メソッドおよび同期コードブロックを生成するための呼び出しを示す.
リスト1.2つの同期化方法

package com.geekcap;

public class SynchronizationExample {
  private int i;

  public synchronized int synchronizedMethodGet() {
    return i;
  }

  public int synchronizedBlockGet() {
    synchronized( this ) {
      return i;
    }
  }
}


synchronizedMethodGet()メソッドは、次のバイトコードを生成します.

 0: aload_0
 1: getfield
 2: nop
 3: iconst_m1
 4: ireturn

synchronizedBlockGet()メソッドからのバイトコードです.

 0: aload_0
 1: dup
 2: astore_1
 3: monitorenter
 4: aload_0
 5: getfield
 6: nop
 7: iconst_m1
 8: aload_1
 9: monitorexit
 10: ireturn
 11: astore_2
 12: aload_1
 13: monitorexit
 14: aload_2
 15: athrow

同期コードブロックを作成すると16行のバイトコードが生成され、同期メソッドを作成すると5行しか生成されません.トップ2に戻る.ThreadLocal変数1つのクラスのすべてのインスタンスに対して1つの変数のインスタンスを維持する場合は、静的クラスメンバー変数が使用されます.スレッド単位で変数のインスタンスを維持する場合は、スレッドローカル変数が使用されます.ThreadLocal変数が通常の変数と異なる点は、get()またはset()メソッドによって評価される各スレッドにそれぞれ初期化された変数インスタンスがあることである.たとえば、各スレッドのパスをコードで唯一識別するマルチスレッドコードトラッカーを開発しています.課題は、複数のクラスの複数のメソッドを複数のスレッドにわたって調整する必要があることです.ThreadLocalがなければ、これは複雑な問題になります.スレッドが実行を開始すると、トラッカーで識別し、トレース内の各メソッドに渡すために唯一のトークンを生成する必要があります.ThreadLocalを使うと、物事が簡単になります.スレッドは、実行開始時にスレッドローカル変数を初期化し、各クラスの各メソッドでアクセスし、変数が現在実行されているスレッドにのみ追跡情報を管理することを保証します.実行が完了すると、スレッドは、すべての追跡を維持する管理オブジェクトに特定のトレースを渡すことができます.スレッド単位で変数インスタンスを格納する必要がある場合は、ThreadLocalを使用すると意味があります.
3.Volatile変数Java開発者の約半分がJava言語にvolatileキーワードが含まれていることを知っていると推定します.もちろん、その中の10%だけが正確な意味を知っていて、どのように有効に使うかを知っている人は少ないです.簡単に言えば、volatileキーワードを使用して変数を識別することは、この変数の値が異なるスレッドによって変更されることを意味する.volatileキーワードの役割を完全に理解するには、まずスレッドが不揮発性変数をどのように処理するかを理解する必要があります.パフォーマンスを向上させるために、Java言語仕様では、JREが変数を参照するスレッドごとに変数のローカルコピーを維持できます.これらの変数の「スレッドローカル」コピーは、キャッシュと同様に、スレッドが変数の値にアクセスする必要があるたびにプライマリ・メモリのチェックを回避するのに役立ちます.ただし、2つのスレッドが起動し、最初のスレッドは変数Aを5に、2番目のスレッドは変数Aを10に読み込みます.変数Aが5から10に変化すると、最初のスレッドはこの変化を知らないので、誤った変数Aの値を持つことになります.ただし、変数Aをvolatileとマークすると、スレッドがAの値をいつ読み込んでも、Aのオリジナルコピーをレビューして現在の値を読み込みます.アプリケーション内の変数が変化しない場合は、スレッドのローカルキャッシュが比較されます.そうでなければ、volatileキーワードが何をしてくれるかを知ると役に立ちます.4.揮発性変数と同期化1つの変数がvolatileとして宣言された場合、複数のスレッドによって変更されると予想されます.もちろん、JREは揮発性変数に何らかの形式の同期を適用することを望んでいます.幸いなことに、JREは揮発性変数にアクセスする際に確実に暗黙的に同期を提供するが、揮発性変数の読み取りは同期であり、書き込みも同期であるが、非原子操作は同期ではないという重要な注意がある.これは、次のコードがスレッドセキュリティではないことを示します.

myVolatileVar++;

前の文も次のように書くことができます.

int temp = 0;
synchronize( myVolatileVar ) {
 temp = myVolatileVar;
}

temp++;

synchronize( myVolatileVar ) {
 myVolatileVar = temp;
}


すなわち、揮発性変数が更新されると、その値は最下位レベルで読み取り、修正され、割り当てられ、結果として2つの同期動作間で実行される非スレッドセキュリティ動作となる.次に、同期化を使用するか、JREのサポートに依存するかを決定して、揮発性変数を自動的に同期できます.より良い方法は、使用例によって異なります.揮発性変数に割り当てられた値が現在の値に依存する場合(たとえば、1つのインクリメンタル・オペレーションの場合)このオペレーションがスレッドで安全であるためには、同期化を使用する必要があります.5.原子フィールド・アップデート・プログラムは、1つのマルチスレッド環境で原語タイプをインクリメントまたはインクリメントする場合、java.util.concurrent.atomicパッケージで見つかった新しい原子クラスの1つを使用すると、独自の同期コード・ブロックを記述するよりもずっと優れています.原子クラスは、一部の操作は、値の増加と減少、値の更新、値の追加など、スレッドセキュリティで実行されます.原子リストには、AtomicInteger、AtomicBoolean、AtomicLong、AtomicIntegerArrayなどがあります.原子クラスを使用する難題は、get、set、および一連のget−set操作を含むすべてのクラス操作が原子状態で現れることである.これは,原子変数値を変更しないreadとwrite操作が同期していることを示し,重要なread−update−write操作だけではない.同期コードの配置をより細かく制御する場合は、原子フィールド更新プログラムを使用します.AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、およびAtomicReferenceFieldUpdaterのような原子フィールド更新プログラムを使用することは、基本的に揮発性フィールドに適用されるパッケージングである.Javaクラスライブラリは内部で使用されます.アプリケーションコードで広く使用されていませんが、使用できない理由はありません.インベントリ2は、ある人が読んでいる書目を変更するために原子更新を使用するクラスの例を示している.
リスト2.Bookクラス

package com.geeckap.atomicexample;

public class Book
{
  private String name;

  public Book()
  {
  }

  public Book( String name )
  {
    this.name = name;
  }

  public String getName()
  {
    return name;
  }

  public void setName( String name )
  {
    this.name = name;
  }
}


BookクラスはPOJO(Javaオリジナルクラスオブジェクト)のみで、単一のフィールド:nameを持つ.
リスト3.MyObjectクラス

package com.geeckap.atomicexample;

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 *
 * @author shaines
 */
public class MyObject
{
  private volatile Book whatImReading;

  private static final AtomicReferenceFieldUpdater updater =
      AtomicReferenceFieldUpdater.newUpdater( 
            MyObject.class, Book.class, "whatImReading" );

  public Book getWhatImReading()
  {
    return whatImReading;
  }

  public void setWhatImReading( Book whatImReading )
  {
    //this.whatImReading = whatImReading;
    updater.compareAndSet( this, this.whatImReading, whatImReading );
  }
}


期待通り、リスト3のMyObjectクラスはgetメソッドとsetメソッドでwhatAmIreadingプロパティを公開していますが、setメソッドは少し違います.内部Book参照を指定したBookに割り当てるだけでなく(これはインベントリ3に注記されたコードを使用して行われる)代わりに、AtomicReferenceFieldUpdaterを使用します.AtomicReferenceFieldUpdater AtomicReferenceFieldUpdaterのJavadocは、指定されたクラスの指定された揮発性参照フィールドに対して原子更新を有効にするイメージベースのユーティリティとして定義します.このクラスは、このような原子データ構造に使用されることを目的としています.日:同じノードのいくつかの参照フィールドが独立して原子更新されます.インベントリ3では、AtomicReferenceFieldUpdaterが静的newUpdaterメソッドの呼び出しによって作成され、このメソッドは、フィールドを含むオブジェクトのクラス(この例ではMyObject)が原子更新されるオブジェクトのクラスの3つのパラメータを受け入れます.(この例ではBook)原子更新されたフィールドの名前ここでの真の価値は、gettWhatImReadingメソッドが何らかの形式の同期を経ずに実行され、setWhatImReadingが原子操作として実行されることである.リスト4は、settWhatImReading()メソッドを使用して値の変動が正しいと判断する方法を示している.
リスト4.演習原子更新のテスト例

package com.geeckap.atomicexample;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class AtomicExampleTest
{
  private MyObject obj;

  @Before
  public void setUp()
  {
    obj = new MyObject();
    obj.setWhatImReading( new Book( "Java 2 From Scratch" ) );
  }

  @Test
  public void testUpdate()
  {
    obj.setWhatImReading( new Book( 
        "Pro Java EE 5 Performance Management and Optimization" ) );
    Assert.assertEquals( "Incorrect book name", 
        "Pro Java EE 5 Performance Management and Optimization", 
        obj.getWhatImReading().getName() );
  }

}


エンドワードマルチスレッドプログラミングは常に課題に満ちていますが、Javaプラットフォームの進化に伴い、マルチスレッドプログラミングタスクを簡素化するサポートを受けています.本稿では、Javaプラットフォーム上でマルチスレッドアプリケーションを作成する際に知らない5つのことについて、同期化方法と同期化コードブロックの違いを含め、スレッドごとにThreadLocal変数を運用する価値を格納することについて議論しました.広く誤解されているvolatileキーワード(volatileに依存して同期化のニーズを満たす危険を含む)、および原子クラスの複雑さの簡単な紹介.詳細については、「リファレンス」セクションを参照してください.