[Java] 並列処理 (Thread, Runnable)

Javaで並列処理(マルチスレッド)の実装についてのまとめです。

Javaで並列処理を実装するにはいくつか方法がありますが、ここではThreadクラスを継承する方法と、Runnableインターフェイスを実装したクラスを用意して、Threadクラスのインスタンスを生成する方法についてまとめます。

基本的な並列処理の実装

Threadクラスを継承する

実装

import java.lang.Thread;

public class ThreadTest {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub:" + i);
                }
            }
        };
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main:" + i);
        }
    }
}

実行結果

実行結果は順不同で出力され、並列に処理されているのが分かります。

java ThreadTest.java
main:0
sub:0
main:1
sub:1
main:2
sub:2
main:3
sub:3
main:4
sub:4

Runnableインターフェースを実装する

実装

import java.lang.Runnable;
import java.lang.Thread;

public class ThreadTest {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub:" + i);
                }
            }
        };

        Thread t = new Thread(r);
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main:" + i);
        }
    }
}

実行結果

こちらも順不同で出力されます。

java ThreadTest.java
main:0
sub:0
main:1
sub:1
main:2
sub:2
main:3
sub:3
main:4
sub:4

並列処理にならないケース

Runnableインターフェースを実装し、runメソッドを呼び出しただけでは並列処理にはなりません。

実装

import java.lang.Runnable;

public class ThreadTest {
    public static void main(String[] args) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("sub:" + i);
                }
            }
        };
        r.run();

        for (int i = 0; i < 5; i++) {
            System.out.println("main:" + i);
        }
    }
}

実行結果

このコードは並列処理になりません。何度実行してもコールした順に処理が実行されます。

java ThreadTest.java
sub:0
sub:1
sub:2
sub:3
sub:4
main:0
main:1
main:2
main:3
main:4
  • 並列処理はThreadクラスのstart()メソッドにより開始される。
  • Runnableインターフェースのrun()メソッドをオーバーライドし、スレッドで実行したい処理を記述する。
  • ThreadクラスはRunnableインターフェースを継承している。

排他制御

マルチスレッドアプリケーションで複数スレッドから一つのインスタンスを共有・変更する場合、競合に気を付けなくてはいけません。

排他制御を考慮していない例

コード

以下のコードはMyCounterクラスのインスタンスを複数のスレッドから更新している例です。

しかしこのコードには問題があり、更新するaddメソッド内で値を取り出しから更新が完了するまでタイムラグがあるため高確率で競合が発生します。(ここでは意図的に100ミリ秒のSleepを入れてます。)

import java.lang.Runnable;
import java.lang.Thread;

public class ThreadTest {
    class MyCounter {
        int count;
    }

    MyCounter c;

    ThreadTest() {
        c = new MyCounter();
        c.count = 0;
    }

    public static void main(String[] args) {
        ThreadTest t = new ThreadTest();
        t.test();
    }

    public void test() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                add(300);
            }

            public void add(int count) {
                System.out.println(Thread.currentThread().getId() + ":add start");
                int temp = c.count;
                sleep(100);
                c.count = temp + count;
                System.out.println(Thread.currentThread().getId() + ":add end");
            }
        };

        Thread[] t = new Thread[3];
        for (int i = 0; i < t.length; i++) {
            t[i] = new Thread(r);
            t[i].start();
        }

        sleep(1000);

        System.out.println(c.count);
    }

    public void sleep(int ms) {
        try {
            Thread.sleep(ms);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return;
        }
    }
}

実行結果

本来3つのスレッドで処理をしているので結果は300と出てほしいところですが、競合が発生しているため結果は100になります。

java ThreadTest.java
15:add start
14:add start
16:add start
14:add end
15:add end
16:add end
50

排他制御を考慮した例

上記のコードで発生する競合を解消するにはsynchronizedキーワードを使用します。

コード

以下のようにaddメソッドにsynchronizedキーワードを追加します。

public synchronized void add(int count) {

こうすることでaddメソッドが一つのスレッドから呼び出されているときは、他のスレッドは待ち状態となるので、複数スレッドから同時に更新されるという競合がなくなります。

また、以下のように記述することもできます。

public void add(int count) {
    synchronized (this) {
        System.out.println(Thread.currentThread().getId() + ":add start");
        int temp = c.count;

        sleep(100);

        c.count = temp + count;
        System.out.println(Thread.currentThread().getId() + ":add end");
    }
}

実行結果

synchronizedキーワードを付けて実行すると、結果は3つのスレッドから100ずつ加算され、期待通りの300になります。

また、addメソッドのstartとendのログを確認すると、一つずつ処理されているのが分かります。

java ThreadTest.java
14:add start
14:add end
15:add start
15:add end
16:add start
16:add end
300

コメント

このブログの人気の投稿

docker-compose up で proxyconnect tcp: dial tcp: lookup proxy.example.com: no such host

docker-compose で起動したweb、MySQLに接続できない事象

【PHP】PHP_CodeSnifferを使う(コーディングルールのカスタマイズ)