メインメニューを開く

差分

ダブル・チェック・ロッキング

3,166 バイト追加, 2023年5月19日 (金) 05:08
'''ダブル・チェック・ロッキング'''(英語:Double Check Locking)とは、初回は([[英語]]:double check locking)とは、[[マルチスレッド]]環境下における[[変数]]に対して、初回は[[ロック]]なしで状態チェックを行い、そこで必要であれば再度せず大雑把に状態チェックを行い、そこで必要であれば再度[[ロック]]を掛けたのちに状態チェックを行うというを掛けた後に厳密に状態チェックを行うという[[ソフトウェア]]の[[最適化]]技法、[[デザインパターン]]のひとつである。
==概要==ダブル・チェック・ロッキングは、主に[[ロックマルチスレッド]]は非常に環境下での[[シングルトンパターン]]を実装する際に[[オーバーヘッド]]の大きい重い処理であるため、その発生回数を可能な限り減らすことで高速化を実現しようというものである。の低減を目的として使われることが多い。
ダブル・チェック・ロッキングは、主にマルチスレッド下における[[マルチスレッド変数]]環境下での[[シングルトンパターンロック]]を実装する際には非常に[[オーバーヘッド]]の低減を目的として使われることが多い。が大きくスレッド数に比例して重くなる。ダブル・チェック・ロッキングはそのような状況下でロックの発生回数を可能な限り減らすことで[[プログラム]]の高速化を実現しようというものである。 なお、シビアな速度を要求されないのであれば[[ソースコード]]の[[可読性]]としては何も考えず常にロックした方が簡潔明瞭ではある。
== 主なプログラミング言語での実装例 ==
public static MySingleton GetInstance()
{
// 1回目のチェック1回目の大雑把なチェック // ロックしていないので高速に処理されるロックしていない「nullではない場合」の処理が高速化されます。
if (null == _instance)
{
// ロック
// ※このブロック内はクソ重いここで処理は急激に重くなります。
lock (_sync)
{
// 2回目のチェック2回目の厳密なチェック
if (null == _instance)
{
=== C# (Lazy) ===
[[.NET Framework 4.0]]では、標準でLazyから標準でLazy<T>クラスが用意されており、それを使うことでよりシンプルにダブル・チェック・ロッキングを記述できるようになったクラス(System名前空間)が用意されており、これを使うことで簡潔明瞭にダブル・チェック・ロッキングを記述できるようになった<!--<ref>{{cite
|title=C# 4.0 in a Nutshell
|last=Albahari
|chapterurl=http://www.albahari.com/threading/part3.aspx#_LazyT
|quote=<code>Lazy&lt;T&gt;</code> actually implements […] double-checked locking. Double-checked locking performs an additional volatile read to avoid the cost of obtaining a lock if the object is already initialized.
}}</ref>-->。
この記述方法は[[C Sharp|C#]]に依存した機能も特に使われておらず、[[.NET Framework]]系の様々な[[プログラミング言語]]へも特に悩むことなく移植・実装可能であると思われる。
<source lang="csharp">
return _instance.Value;
}
}
</source>
 
=== Java ===
[[Java]]では[[仕様]]において[[アウトオブオーダー]]を用いるメモリモデルが可能となっていたため、この[[イディオム]]を使うことには問題があり、一部の[[実装]]では実際に正しく働かない可能性があることが知られている。詳細は http://www.ibm.com/developerworks/jp/java/library/j-dcl/ を参照。大雑把にいえばJavaでは「new(メモリ確保)」と「コンストラクタ実行」のタイミングが異なる。
 
よって、[[Java]]では絶対にダブルチェックロッキングを使用してはならない。
 
たとえば以下のような[[Java]]の[[ソースコード]]があったとする。
<source lang="java">
Person hage = new Person();
hage.say();
</source>
これが一部のJava実装では以下のような動作になる。
# 1行目で[[メモリ]]が確保される。
#: いわゆるmallocが実行されhage[[変数]]自体は[[null]]ではなくなる。
#: ただし[[コンストラクタ]]はまだ実行されていない。
# 2行目のインスタンスを初めて使うときにコンストラクタが実行される。
#: [[コンストラクタ]]が実行されるタイミングは、オリジナル(newを実行した[[スレッド]]の持つ)インスタンスに対して外部から[[メンバー関数]]や[[メンバー変数]]に初回アクセスがあったときとなる。これを[[遅延初期化]](lazy initialization)という。これによりhage[[変数]]自体は[[null]]ではないので後続スレッドはコンストラクタを実行していない不完全な[[インスタンス]]の[[参照]]を取得できてしまう。
 
[[Objective-C]]のallocとinitの動作に近いものが全自動で実行されてしまうような感じである。
<source lang="objc">
Parson* hage = [[Parson alloc] init];
</source>
 
<source lang="java">
public static Singleton getInstance()
{
// 1: 先行スレッドから見た場合はnull
//
// 3: 後続スレッドから見た場合は非null、
// ただしコンストラクタが実行されていない不完全なものである場合がある。
if (instance == null)
{
synchronized(Singleton.class)
{
if (instance == null)
{
// 2:
// 先行スレッドによりメモリが割り当てられてnullではなくなるが、
// この時点でコンストラクタは実行されていない。
instance = new Singleton();
}
}
}
return instance;
}
</source>
== 参考文献 ==
{{reflist}}
 
== 外部リンク ==
 
{{stub}}