らこらこブログ

唐揚げとアニメとプログラミングが大好きです

Javaマルチスレッディングその3(スレッドセーフなコレクション)

 前回、前々回は非同期処理の実行についての記事でしたが今回は非同期処理「される」側の実装についてです。特に今回はスレッドセーフなコレクションの実装について書きたいと思います。

スレッドセーフ
 スレッドセーフについてWikipediaでは

あるコードがスレッドセーフであるという場合、そのコードを複数のスレッドが同時並行的に実行しても問題が発生しないことを意味する。

とあります。
 マルチスレッドプログラミングではひとつのインスタンスに複数のスレッドからアクセスすることが少なくありません。特にデータストア、データベースでは複数のスレッドから並行して追加、削除などがめまぐるしく行われることがあるかと思います。そのためコレクションを扱う実装はスレッドセーフにすることが重要です。

synchronizedとロックオブジェクト
 Javaでスレッドセーフな実装を行う方法の一つにsynchronizedの利用があります。synchronizedにはメソッドへの修飾語と、ブロックの2種類があります。synchronizedメソッドは、そのインスタンスの操作においてそのメソッドが複数のスレッドから同時に呼び出されないようにします。言うなれば「インスタンス変数の同期の保証」です。synchronizedブロックは指定したインスタンスを操作できるスレッドをそのスレッドに限定します。単純なデータストアの実装を例にして説明します。

public class DataStoreTest
{
	private List<Object> list = new ArrayList<Object>();
	
	public void add(Object obj)
	{
		list.add(obj);
	}
	
	public Object get(int index)
	{
		return list.get(index);
	}
}

これはスレッドセーフではない実装です。別のスレッドからaddとgetが並行して呼び出された場合addによるインデックスの変更がgetに反映されているか保証出来ません。なのでこれをsynchronizedを使ってスレッドセーフにしてみます。

public class DataStoreTest
{
	private List<Object> list = new ArrayList<Object>();
	
	synchronized public void add(Object obj)
	{
		list.add(obj);
	}
	
	synchronized public Object get(int index)
	{
		return list.get(index);
	}
}

 synchronizedメソッドがそのインスタンスに複数ある場合はすべて同期対象になります。なのでgetしようとした時に別スレッドでaddされていればadd終了まで待たされ、同期がとれた上でgetされます。これと同じことをsynchronizedブロックでも書いてみます。

public class DataStoreTest
{
	private List<Object> list = new ArrayList<Object>();
	private final Object lock = new Object();
	
	public void add(Object obj)
	{
		synchronized (lock)
		{
			list.add(obj);
		}
	}
	
	public Object get(int index)
	{
		synchronized (lock)
		{
			return list.get(index);	
		}
	}
}

 突然出てきたlockというObject型のインスタンスですが、これを「ロックオブジェクト」と言います。『listに対して同期を取ればいいんだからsynchronizedブロックにはlistを指定すればいいじゃないか』と思うかもしれないですが確かにこの場合はそれでも大丈夫です。ですがlistインスタンスが変わった場合(代入されたり再生成されたり)した場合ロックが外れることになります。なので鍵となるインスタンスはfinalで不変なものにすることが推奨されます。ちなみに同期したい変数がこの場合インスタンス変数ですが、もしこのlistがstaticな場合はlockもstaticにしなければ意味がありません。ご注意を。

Concurrentシリーズ
 ここまでsynchronizedを使ったスレッドセーフの実装でしたが、実はこれを内部で実装したスレッドセーフなコレクションクラスがすでに組み込みで用意されています。それがjava.util.concurrentパッケージに含まれるコレクション達です。
 代表的なものではConcurrentLinkedQueue、ConcurrentHashMapなどがあります。これらはすべてスレッドセーフな実装がされており、そのため今までのコレクションからは若干使い勝手は落ちますが上記のような同期処理を自分で記述する必要がありません。とくにこだわりがないのであれば積極的にこの新しいコレクションクラスを使ってみるべきだと思います。

 以上3回にわたってJavaのマルチスレッドプログラミングについての解説でした。間違ってたところがあるかもしれませんが良かったら教えて下さい。