volatile修飾子でJavaのスレッド変数を制御する方法
  • facebookページ
  • twitterページ
  • 2021.03.19

    volatile修飾子でJavaのスレッド変数を制御する方法

    「volatile」はjavaの修飾子の一つで、マルチスレッド処理で使われます。この記事では、javaのvolatile修飾の概要と使い方を解説します。

    volatileの概要

    Webアプリや、大量データ処理では、性能向上を図るためにマルチスレッドで処理をします。特にWebアプリは不特定多数からのリクエストを同時に処理する必要があるためマルチスレッド環境での処理は必須と言えるでしょう。

    マルチスレッド環境では同時に複数処理が実行されるため、Javaのスレッドは、メインメモリ上にあるフィールド値を、スレッド固有の領域にキャッシュし、メインメモリへの読み書きを減らすことで性能向上を図っています。

    このように、 volatile修飾子は並列処理において変数の可視性を確保するのに用います。

    マルチスレッドと言えば、Javaには synchronized修飾子があります。こちらはマルチスレッド環境においで、特定のメソッドや特定のコードブロックのみ排他制御を行い、 synchronizedと定義された処理が同時に呼び出された時に、先に処理に入ったスレッドが、 synchronizedと定義されたメソッドや、コードブロックを抜けるまで、後から来たスレッド待たせることで、スレッド間で共有する変数などのアトミック性を保ち、スレッドセーフを実現しています。

    スレッド処理における変数のキャッシュとは?

    volatile修飾子は、スレッドがメインメモリ上にある、変数のキャッシュコピーを取得することを禁止する機能であることは先述の通りですが、そもそも、スレッドが変数のキャッシュコピーをすると、どのようなケースで問題が発生するのでしょうか?

    実際に、 volatile修飾子を使わないことによって問題が発生するコードを見てみましょう。

    次のサンプルコードは、並列プログラミングを実行する ExecutorServiceクラスを用いて、5つのスレッドを起動し、各スレッドでは変数 countの値を1ずつカウントアップしています。

    普通に考えれば、 count変数を5回加算しているため、結果は当然「5」になると思いますが、実際に実行してみると、次のように結果は「4」と表示されました。

    実行環境やタイミングによっては、正しく「5」と表示されたり、さらに少ない数になることもありますが、スレッドが変数の値をキャッシュコピーすることにより、キャッシュ領域にコピーした変数の値をメインメモリに反映する前に、他のスレッドが古い値で加算処理を行なってしまったことにより、このような事象が発生します。

    volatileの使いどころ

    synchronizedは複数スレッド間の処理を排他的にしアトミック性を確保しているのに対し、 volatileはスレッドによる変数のキャッシュコビーを禁止することにより、スレッド間で共有するリソースの「可視性」を保ちますが「アトミック性」はありません。

    そのため、 ArrayListなど、並列処理において同時に要素を追加すると変数の中身に不整合が生じるようなケースでは synchronizedで処理を排他的にし、ステータスフラグなど、スレッド間でリアルタイムに変更を共有しなければならない変数には、 volatile修飾子を付けることを知っておきましょう。

    ポテパンダの一言メモ

    一部で volatilesynchronized修飾子を混同し、 volatileがマルチスレッドにおける排他制御で使えるという認識を持ってしまいがちですが、あくまで変数のキャッシュコピーを禁止するための修飾子であるため、 volatileは排他制御には使えず、さらに volatileを付けた変数はスレッドセーフにはならないことに注意が必要です。

    volatileを使ってみよう

    volatileは変数宣言時に、修飾子として指定します。

    先ほどのサンプルコードで宣言した count変数に volatile修飾子を付けて、もう一度処理を実行してみましょう。

    コードを実行すると、今度は結果が「5」と表示され、正しく加算処理が行われていることが分かります。

    count変数に volatile修飾子を付けたことで、スレッドが変数のキャッシュコピーをせず、メインメモリの変数に対して読み書きするようになったため、常に最新の値に対して加算が行われ想定通りの結果になってくれました。

    volatileとsynchronizedとの違い

    synchronized修飾子を指定したメソッドやブロックを使うことでも、 volatile修飾子と同じく変数の値は必ず元のメモリー上から読み込まれます。

    synchronizedvolatileの違いは、スレッド・セーフであるかどうかです。 synchronizedはマルチスレッド環境下で同時処理を排他的に制御することで、 synchronized修飾子で囲まれた処理の中はスレッド・セーフであることが保証されます。

    それに対し volatile修飾子は、変数の可視性は保証されますが、スレッド・セーフは保証されないので注意しましょう。 ArrayListなどの非スレッド・セーフなクラスに対し volatile修飾子を付けても、同時にアクセスされた時にデータが不正になる可能性があります。

    まとめ

    Javaの volatile修飾子の使い方について解説してきました。

    マルチスレッド処理は、最初は複雑に見えるかもしれませんが、Java8で導入された ExecutorServiceクラスなどを使用することにより、よりシンプルに並列処理が実装できるようになりました。

    是非、マルチスレッドや volatile修飾子による制御を学んで、効率がよく安全な並列処理を実装していきましょう。

    【関連記事】
    【初心者向け】Javaのスレッド(Thread)・マルチスレッドとは?使い方も紹介!



    優良フリーランス案件多数掲載中!
    フリーランスエンジニアの案件をお探しなら
    ポテパンフリーランス

    この記事をシェア

    • Facebookシェア
    • Twitterシェア
    • Hatenaシェア
    • Lineシェア
    pickup









    ABOUT US

    ポテパンはエンジニアと企業の最適なマッチングを追求する企業です。

    READ MORE