18.Javaのマルチスレッドと同期処理

独習JAVA

マルチスレッドは、Javaにおいて複数のスレッド(実行単位)を同時に動作させる技術です。これにより、プログラムのパフォーマンスを向上させることが可能となり、特に並列処理を必要とするアプリケーションで非常に有効です。しかし、複数のスレッドが同じリソースにアクセスする場合、データの不整合が発生する可能性があるため、「同期処理」を正しく理解し実装することが不可欠です。本記事では、マルチスレッドと同期処理に関する基本概念と、実践的なサンプルコードを用いてその使い方を解説します。

Javaにおけるスレッドの基礎

Javaでスレッドを実装する方法は主に2つあります。1つ目は`Thread`クラスを継承する方法、2つ目は`Runnable`インターフェースを実装する方法です。後者はより柔軟で一般的に推奨されるアプローチです。

Runnableインターフェースを使用したマルチスレッドの例

class MyRunnable implements Runnable {
    private String threadName;

    MyRunnable(String name) {
        threadName = name;
    }

    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println(threadName + " is running: " + i);
            try {
                Thread.sleep(1000); // 1秒ごとに休止
            } catch (InterruptedException e) {
                System.out.println(threadName + " was interrupted.");
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable("Thread 1"));
        Thread thread2 = new Thread(new MyRunnable("Thread 2"));

        thread1.start();
        thread2.start();
    }
}

このコードでは、`MyRunnable`クラスが`Runnable`インターフェースを実装し、`run()`メソッド内で5回のループ処理を行います。`Thread.sleep(1000)`により、各スレッドは1秒ごとに実行されます。結果として、`Thread 1`と`Thread 2`が交互に出力されることが期待されます。

実行結果

Thread 1 is running: 1
Thread 2 is running: 1
Thread 1 is running: 2
Thread 2 is running: 2
Thread 1 is running: 3
Thread 2 is running: 3
Thread 1 is running: 4
Thread 2 is running: 4
Thread 1 is running: 5
Thread 2 is running: 5

スレッドの同期処理

複数のスレッドが同時に共有リソースにアクセスすると、データの整合性に問題が発生する可能性があります。これを防ぐためには、Javaの同期機能を使用します。同期処理では、1つのスレッドがリソースにアクセスしている間は他のスレッドがそのリソースにアクセスできないようにします。このために、`synchronized`キーワードが用いられます。

同期処理の例

class Counter {
    private int count = 0;

    // synchronizedメソッドでスレッド安全性を確保
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

class MyThread extends Thread {
    private Counter counter;

    MyThread(Counter counter) {
        this.counter = counter;
    }

    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Thread thread1 = new MyThread(counter);
        Thread thread2 = new MyThread(counter);

        thread1.start();
        thread2.start();

        try {
            thread1.join(); // スレッドの終了を待機
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + counter.getCount());
    }
}

この例では、`Counter`クラスに`increment()`メソッドを定義し、このメソッドに`synchronized`キーワードを付けることで、複数のスレッドからの同時アクセスを防いでいます。`thread1`と`thread2`は同じ`Counter`オブジェクトに対して1000回ずつインクリメント操作を行いますが、`synchronized`によって正しい最終結果が保証されます。

実行結果

Final count: 2000

同期ブロックの使用

`synchronized`キーワードはメソッド全体に適用することもできますが、特定のブロックだけに適用することも可能です。これを「同期ブロック」と呼び、必要な範囲だけを同期させることで、性能の向上を図ることができます。

同期ブロックの例

class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        balance = initialBalance;
    }

    public void deposit(double amount) {
        synchronized (this) {
            balance += amount;
        }
    }

    public void withdraw(double amount) {
        synchronized (this) {
            if (balance >= amount) {
                balance -= amount;
            } else {
                System.out.println("Insufficient funds.");
            }
        }
    }

    public double getBalance() {
        return balance;
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) { account.deposit(1); } }); Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                account.withdraw(1);
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final balance: " + account.getBalance());
    }
}

このコードでは、`deposit()`と`withdraw()`メソッドに同期ブロックを適用しています。これにより、1つのスレッドが同期ブロックに入っている間は、他のスレッドが同じブロックに入れなくなり、データの整合性が保たれます。`t1`と`t2`はそれぞれ入金と引き出しを行い、最終的な残高が表示されます。

実行結果

Final balance: 1000.0

まとめ

Javaのマルチスレッドと同期処理は、プログラムのパフォーマンス向上とデータの整合性を保つための重要な要素です。スレッドを使用することで、複数の処理を並行して実行でき、同期処理を正しく実装することで競合状態を防ぐことができます。`synchronized`キーワードや同期ブロックを活用し、効率的で安全なスレッド処理を行うことができます。これらの技術を駆使して、スレッドを使用するアプリケーションの設計をさらに最適化してください。

購読
通知
0 Comments
Inline Feedbacks
View all comments