【Java同時】JAVA同時プログラミング実戦-読書ノート4


どのような理由でスレッドの安全を保証する実際の要求も、一線の開発者がコードした瞬間にしか存在しない.スレッドの内部使用法の設定が明確にドキュメント化されていない場合、後期メンテナンス担当者はオブジェクトの逸脱を誤って放任します.
スレッド制限を維持するより規範的な方法は、ThreadLocalを使用して、各スレッドを数値を持つオブジェクトに関連付けることができます.ThreadLocalはsetとgetアクセサを提供し、getは常に現在の実行スレッドによってsetによって設定された最新値を返します.
private static ThreadLocal connectionHolder=

    new ThreadLocal(){
        public Connection initialValue(){
            return DriverManager.getConnection(DB_URL);
        }
    }

public static Connection getConnection(){
    return connectionHolder.get();
}

単一スレッドのアプリケーションをマルチスレッド環境に移行している場合、共有するグローバル変数を
ThreadLocal
グローバル共有の意味を前提としたタイプで、アプリケーション・レベルのキャッシュをスレッド・ローカル・バッファにすると、彼は価値がありません.
作成後の状態を変更できないオブジェクトを可変オブジェクトと呼びます.可変オブジェクトは常にスレッドで安全です.
Java言語仕様にしてもJavaストレージモデルにしても、不変性に関する正式な定義はありませんが、不変性は、オブジェクトのすべてのドメインをfinalタイプとして宣言するのと同じではありません.すべてのドメインがfinalタイプであるオブジェクトは、finalドメインが可変オブジェクトへの参照を得ることができるため、依然として可変であることができます.
可変オブジェクトを使用してすべての変数を保持することで、これらの変数にアクセスして更新する際の競合条件を解消できます.以下に、可変コンテナに数値とその係数をキャッシュします.
class OneValueCache{

    private final BigInteger lastNumber;

    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i,BigInteger[] factors){
        lastNumber=i;
        lastFactors=Arrays.copyOf(factors,factors.length);
    }

    public BigInteger[] getFactors(BigInteger i){
        if(lastNumber==null||!lastNumber.equals(i))
            return null;
        else
           return Arrays.copyOf(lastFactors,lastFactors.length);
   }
}

変数を更新すると、新しいコンテナオブジェクトが作成されますが、その前にどのスレッドも元のコンテナとインタラクティブで、一貫した状態が表示されます.
スレッドがvolatileタイプのcacheドメインを設定して新しいOneValueCacheに参照すると、新しいデータはすぐに他のスレッドに表示されます.OneValueCacheは可変であり、対応するコードパスが毎回1つしかアクセスしないため、cach eドメインに関連する操作は相互に干渉しません.可変コンテナオブジェクトは、不変コンストレイントに関連する複数のステータス変数を持ち、volatileリファレンスを使用してタイムリーな可視性を確保します.これにより、次のコードはロックに使用されていませんが、スレッドが安全になります.
public class VolatileCachedFactorizer implements Servlet{

  private volatile OneValueCache cache=new OneValueCache(null,null);

  public void servie(ServletRequest req,ServletResponse resp){
    BigInteger i=extractFromRequest(req);
    BigInteger[] factors=cache.getFactors(i);
    if(factors==null){
      factos=factor(i);
      cache=new OneValueCache(i,factors);
    }
    encodeIntoResponse(resp,factors);
  }

}
の次の例は失敗しました.可視性の問題のため、コンテナは他のスレッドで不一致の状態に設定されます.彼の不変の制約がコンストラクション関数で正しく作成されていても.この不正なパブリケーションにより、他のスレッドはローカル作成オブジェクトを観察することができます.
public Holder holder;

  public void initialize(){
    holder=new Holder(42);
  }

}

次のコードが上記の方法でパブリッシュされる場合、パブリッシュスレッド以外のスレッドがassertSanityを呼び出すとAssertErrorが放出される可能性があります.この問題はHolderクラス自体ではなく、Holderが正しくパブリッシュされていないが、ドメインnをfinalタイプとして宣言することで、Holderが可変となり、不正パブリケーションの問題を回避できる.
public class Holder{
    private int n;

    private Holder(int n){
        this.n=n;
    }

    public void assertSanity(){
        if(n!=n){
            throw new AssertionError(“This statement is false.”);
        }
    }
}

Holderが他のスレッドに表示されるように同期していないため、2つのエラーが発生します.まず、パブリッシュスレッド以外のどのスレッドでもHolderドメインの有効期限が表示されるため、現在Holderに新しい値が付与されている場合でもnull参照または古い値が表示されます.次に,さらに悪いことに,スレッドが見たHolder参照は最新であるが,Holder状態は期限切れである.これにより、プログラムの実行がより予測不可能になります.コンストラクション関数で設定したドメイン値は、これらのドメインに書き込まれる最初の値であるべきであるため、いわゆるデフォルト値としてより古い値はありませんが、Objectのコンストラクション関数はサブクラスのコンストラクション関数より先に実行され、まずすべてのドメインにデフォルト値が書き込まれ、これらのデフォルト値がドメインの期限切れ値になる可能性があります.スレッドが最初にドメインを読み込むと、期限切れの値が表示され、再びドメインを読み込むと更新値が得られます.これはAssertionErrorが投げ出された理由です.
私たちは自己複製のリスクにあり、十分な同期がなければ、スレッド間でデータを共有するときに非常に奇妙なことが起こります.
オブジェクトを安全にパブリッシュするには、オブジェクトの参照とオブジェクトのステータスを同時に他のスレッドに表示する必要があります.正しく作成されたオブジェクトは、次の条件で安全にパブリッシュできます.
1静的イニシエータによってオブジェクトの参照を初期化する.
2、彼の参照をvolatil eドメインまたはAtomicReferenceに格納する.
3、彼の参照を正しく作成されたオブジェクトのfinalドメインに格納する.
4、または彼の参照をロックによって正しく保護されたドメインに格納する.
最も簡単な方法は、Holderが他のスレッドに表示されることを保証するために同期していないため、静的初期化器を使用することです.2つのエラーが発生します.まず、パブリッシュスレッド以外のどのスレッドでもHolderドメインの有効期限が表示されるため、現在Holderに新しい値が付与されている場合でもnull参照または古い値が表示されます.次に,さらに悪いことに,スレッドが見たHolder参照は最新であるが,Holder状態は期限切れである.これにより、プログラムの実行がより予測不可能になります.コンストラクション関数で設定したドメイン値は、これらのドメインに書き込まれる最初の値であるべきであるため、いわゆるデフォルト値としてより古い値はありませんが、Objectのコンストラクション関数はサブクラスのコンストラクション関数より先に実行され、まずすべてのドメインにデフォルト値が書き込まれ、これらのデフォルト値がドメインの期限切れ値になる可能性があります.スレッドが最初にドメインを読み込むと、期限切れの値が表示され、再びドメインを読み込むと更新値が得られます.これはAssertionErrorが投げ出された理由です.
public static Holder holder=new Holder(42);

静的イニシエータはクラスのイニシャル化段階においてJVMによって実行され、このメカニズムは、JVM内の同期により、このようにしてイニシャル化されたオブジェクトが安全に公開されることを保証する.
1つのオブジェクトは技術的に可変ではありませんが、彼のステータスはパブリケーション後に変更されません.このようなオブジェクトは有効な可変オブジェクトになります.
たとえば、Date自体は可変ですが、彼を可変オブジェクトとして使用するとロックを無視できます.そうでなければ、Dateがスレッド間で共有されるたびに、ロックで安全を確保します.mapを維持し、各ユーザーの最近のログイン時間を格納しているとします.
pulbic Map lastLogin = Collections.synchronizedMap(new HashMap());

Date値がmapに配置された後も変化しない場合、synchronizedMapでの同期の実装は、Date値を安全にパブリッシュするために重要であり、これらのDate値にアクセスする際に追加の同期を必要としない.コンカレント・プログラムでは、オブジェクトを使用して共有するいくつかの有効なポリシーは、スレッドによって制限されたオブジェクトが、スレッドによって制限され、スレッドによって独占され、占有されたスレッドによってのみ変更されるスレッド制限です.≪共有読取り専用|Shared Read Only|emdw≫:共有読取り専用オブジェクトで、追加の同期なしに複数のスレッドで同時にアクセスできますが、任意のスレッドでは変更できません.共有読取り専用オブジェクトには可変オブジェクトと有効な可変オブジェクトが含まれます.共有スレッドセキュリティ:1つのスレッドセキュリティのオブジェクトが内部で同期されるため、他のスレッドは追加の同期を必要とせず、共通インタフェースを通じて任意にアクセスできます.≪デーモン|デーモン|emdw≫:デーモンされたオブジェクトは、特定のロックでのみアクセスできます.保護されたオブジェクトには、スレッドセキュリティオブジェクトによってカプセル化されたオブジェクトと、特定のロックによって保護されていることが知られているパブリッシュされたオブジェクトが含まれます.