🦝

Java: Javaメモリモデルの特性 と 原子性の保証について

に公開

プロローグ

こんにちは。私は九州生まれ九州在住ですが、こちらもここ最近急に寒くなって、夏服を着ることもなくなり服装をどうしたものかと悩んでます。パーカーが好きなので そろそろ新しいパーカーほしいなぁ~ なんて思っています。

さて前置きですが、この記事は、私が本やネットで勉強して自分なりに言葉にしたものです。自分の備忘録みたいなものです。 なので内容に関して誤りもあるかもしれませんが、あしからずご了承くださいませ。

テーマ

今回、JMMの特性 と 原子性の保証について 初心者なりにまとめたものを記載しました。

Java Memory Model の話

と本に書いてあった。

まず前提として、スレッドは、以下3パターンを共有しません。

  1. メソッドのローカル変数 の 値
  2. メソッドのパラメータ変数 の 値
  3. ThreadLocal変数 の 値
    ※対象が参照オブジェクトだと共有される可能性がある
    ※ThreadLocal型の変数は、各スレッドごとに自動的にコピーされ、スレッド間で共有されない

しかし、最初にあった「オブジェクトのフィールド値」について 1~3に含まれていないではないか
と思う私のような初心者は多いと思います。

Javaのプラットフォームスレッドや仮想スレッドは、OSのスレッド上で動いています。
(OSのスレッド <= プラットフォームスレッド <= 仮想スレッド)
各スレッドには CPU キャッシュやレジスタといった「高速なローカルメモリ領域」がある。

上の絵のスレッドAがあるフィールドを読んだとき、JVMやCPUは「何度もメインメモリにアクセスするのは遅い」と指示し、ローカルキャッシュに値を保持して再利用します。

これが

「JMMは、スレッドがオブジェクトを参照するとき、オブジェクトのフィールド値を、
各スレッドが保持する(キャッシュする)ことを許可している」

という言葉の意味となります。

こういったJavaメモリモデルの可視性問題を、回避する方法として、排他制御 や volatile修飾子の使用、アトミック処理があげられる。
※排他制御は別記事にあげるつもりです。

volatile修飾子

volatile修飾子をつけたフィールド宣言の例は以下となります。

private volatile int nCount;
private static volatile int nValue; 

また、long型 や double型の変数の読み書きにおいて、アトミック性を保証させることができる
※アトミック性 とは 1回の操作が途中で分割されない(中断されない)こと

long,double以外のプリミティブ型はアトミック性を保証している。
じゃあ なぜ、このlong型 と double型だけ特別なのかは、

64bitになっていることに理由があるとのこと。

昔の Java(特に32bit JVM)では、64bit値の読み書きを「2回に分けて」処理することがあった。

上位32bitの書き込み

下位32bitの書き込み

もし他のスレッドがその途中で読み込むと、半分古くて半分新しい値(いわゆる“壊れた値”)を読む
ことが起こりえるらしい。

最後に、重要なことで、volatile修飾子をつけても排他制御はできません。オブジェクト等の値を保証することで有効だと考えます。

アトミック処理

アトミック処理とは

つまりスレッドが複数あっても、他のスレッドがその処理の途中に割り込めない。

java.utill.concurrent.atomicパッケージを使用したアトミック処理の例として

import java.util.concurrent.atomic.AtomicInteger;
class MyItem {
	private final AtomicInteger nCount = new AtomicInteger();
	public void increment() {
		this.nCount.getAndIncrement();
	}
}

こうすることで、単純な nCount++ した場合の不正値を回避することができた。

エピローグ

記事にしていて、自分でもあれって思うところが多く誤りが多いかも。。。
今回のテーマでもし誤り、もしくはこうしたらいいのに と思うものがあればご指摘頂ければと思います。

以上です。

Discussion