Java
 Computer >> コンピューター >  >> プログラミング >> Java

Javaでのスレッド干渉エラーとは?発生原因と対策をわかりやすく解説


マルチスレッドプログラミングでは、複数のスレッドが同一のデータに同時にアクセスすることで、スレッド干渉(Thread Interference)と呼ばれる不具合が発生することがあります。本記事では、具体的なサンプルコードとその実行結果をもとに、スレッド干渉がどのような現象なのか、そしてなぜ起こるのかをわかりやすく解説します。

サンプルコード

import java.io.*;
class Demo_instance{
    static int val_1 = 6;
    void increment_val(){
        for(int j=1;j<11;j++){
            val_1 = val_1 + 1;
            System.out.println("インクリメント後のiの値:" + val_1);
        }
    }
    void decrement_val(){
        for(int j=1;j<11;j++){
            val_1 = val_1 - 1;
            System.out.println("デクリメント後のiの値:" + val_1);
        }
    }
}
public class Demo{
    public static void main(String[] args){
        System.out.println("Demo_instanceのインスタンスを生成しました");
        System.out.println("スレッドのインスタンスを生成しました");
        final Demo_instance my_inst = new Demo_instance();
        Thread my_thread_1 = new Thread(){
            @Override
            public void run(){
                my_inst.increment_val();
            }
        };
        Thread my_thread_2 = new Thread(){
            @Override
            public void run(){
                my_inst.decrement_val();
            }
        };
        my_thread_1.start();
        my_thread_2.start();
    }
}

実行結果

Demo_instanceのインスタンスを生成しました
スレッドのインスタンスを生成しました
インクリメント後のiの値:7
インクリメント後のiの値:7
デクリメント後のiの値:6
インクリメント後のiの値:8
デクリメント後のiの値:7
インクリメント後のiの値:8
インクリメント後のiの値:8
デクリメント後のiの値:7
インクリメント後のiの値:9
デクリメント後のiの値:8
デクリメント後のiの値:7
デクリメント後のiの値:6
デクリメント後のiの値:5
デクリメント後のiの値:4
デクリメント後のiの値:3
デクリメント後のiの値:2
インクリメント後のiの値:3
インクリメント後のiの値:4
インクリメント後のiの値:5
インクリメント後のiの値:6

コードの解説

Demo_instance クラスは、staticな整数型変数 val_1 を定義しています。increment_val メソッドは10回のループ処理の中で変数を1ずつ増加させ、その都度コンソールへ出力します。一方、decrement_val メソッドも同様に10回のループ処理を行い、毎回値を1ずつ減少させて結果を表示します。

Demo クラスの main メソッドでは、まず Demo_instance のインスタンスを生成した上で、新しいスレッドを2つ作成しています。各スレッドでは run メソッドをオーバーライドし、それぞれ increment_val メソッドと decrement_val メソッドを呼び出します。最後に、両方のスレッドに対して start メソッドを呼び出すことで、2つの処理が並行して実行されます。

スレッド干渉が発生する仕組み

実行結果に注目してください。初期値6に対して10回のインクリメントと10回のデクリメントを行っているため、最終的に元の値である6に戻るのは正しい挙動です。しかし、途中の出力を見ると「インクリメント後の値が7」という出力が連続したり、値の増減が期待どおりに進まなかったりする箇所があります。これは、スレッド干渉によって一部の更新が失われている兆候です。

一見すると不可分な操作に見える val_1 = val_1 + 1 は、実際には内部で以下の3つのステップに分けて実行されます。

  1. メモリから現在の値を読み込む
  2. 読み込んだ値に1を加算する
  3. 計算結果をメモリへ書き戻す

例えば、両方のスレッドが同時に「6」という値を読み込み、それぞれ「7」を書き戻した場合を考えてみましょう。本来は2回の加算で8になるはずが、結果は7になってしまいます。このように、スレッド同士の操作が交互に割り込むことで更新が上書きされ、データの整合性が崩れてしまうのです。

さらに、このプログラムは実行するたびに出力結果が変わる点にも注意が必要です。スレッドの実行順序はOSやJVMのスケジューリングに依存するためであり、まさにマルチスレッド処理特有の非決定的な挙動と言えます。

スレッド干渉への対策

スレッド干渉を防ぐには、共有データへのアクセスを同期化する必要があります。最も基本的な方法は、synchronized キーワードを使用することです。

synchronized void increment_val(){
    // このメソッドは一度に1つのスレッドからのみ実行される
    ...
}

synchronized を付与されたメソッドは、同一オブジェクトに対して同時に1つのスレッドしか実行できなくなります。そのほかにも、AtomicInteger などのアトミック変数クラスを活用したり、ReentrantLock による明示的なロック制御を導入したりする方法があります。マルチスレッド環境で共有状態を扱う場合は、常にスレッドセーフ性を意識して設計することが重要です。

  1. Javaのユーザースレッドとデーモンスレッドの違いとは?基本と使い方を解説

    Javaのデーモンスレッドは、主にユーザースレッドに対してバックグラウンドでサービス(補助的な処理)を提供するために使われるスレッドです。アプリケーションのエントリポイントであるmain()メソッドが実行されるメインスレッドは、ユーザースレッド(非デーモンスレッド)です。JVMは、すべてのユーザースレッド(非デーモンスレッド)が終了しない限り、プログラムを終了しません。一方、残っているのがデーモンスレッドだけになった時点で、JVMは即座に終了します。ユーザースレッド内で作成したスレッドをデーモンスレッドとして扱いたい場合は、setDaemon(true)を呼び出して明示的に指定します。なお、こ

  2. Chromeで「ERR_SOCKET_NOT_CONNECTED」エラーが発生したときの対処法

    Google Chromeユーザーの間では、ブラウザからGoogleの各種サイトにアクセスしようとした際に「ERR_SOCKET_NOT_CONNECTED」というエラーメッセージが表示される問題が報告されています。このエラーにはいくつかのパターンがあり、「どのウェブサイトにも一切アクセスできないケース」と「Googleのサイトだけにアクセスできないケース」の2つに大別されます。このエラーメッセージは、ブラウザのソケット(socket)に何らかの問題が発生していることを示しています。ソケットとは、インターネット上で動作する2つのプログラム間の双方向通信における「片方の終端」を指す概念です。ER