マルチスレッド環境におけるスレッドセーフの実現方法

映画館のチケット販売システムを例に、スレッドセーフな処理の3つの方法を解説します。100席ある映画のチケット販売をシミュレートします。

  1. 基本的な実装と問題点 まずはスレッドセーフではない基本的な実装です:
public class TicketSales implements Runnable {
    private int remainingSeats = 100;
    
    public void run() {
        while(true) {
            if(remainingSeats > 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "が" + remainingSeats-- + "番目の席を販売");
            }
        }
    }
}

public class MainApp {
    public static void main(String[] args) {
        TicketSales sales = new TicketSales();
        new Thread(sales).start();
        new Thread(sales).start();
        new Thread(sales).start();
    }
}

この実装では、重複販売や0枚目のチケットが表示される問題が発生します。

  1. 同期ブロックによる解決 synchronizedブロックを使用した解決方法:
public class SynchronizedTicketSales implements Runnable {
    private int seats = 100;
    private final Object lock = new Object();
    
    public void run() {
        while(true) {
            synchronized(lock) {
                if(seats > 0) {
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "が" + seats-- + "番目の席を確保");
                }
            }
        }
    }
}
  1. 同期メソッドによる解決 synchronizedメソッドを使用した方法:
public class MethodSyncTicketSales implements Runnable {
    private int availableSeats = 100;
    
    public void run() {
        while(true) {
            processSale();
        }
    }
    
    private synchronized void processSale() {
        if(availableSeats > 0) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "が" + availableSeats-- + "番目を予約");
        }
    }
}
  1. Lockインターフェースを使用した解決 ReentrantLockを使用したより柔軟な方法:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockTicketSales implements Runnable {
    private int totalSeats = 100;
    private final Lock seatLock = new ReentrantLock();
    
    public void run() {
        while(true) {
            seatLock.lock();
            try {
                if(totalSeats > 0) {
                    Thread.sleep(300);
                    System.out.println(Thread.currentThread().getName() + "が" + totalSeats-- + "番を購入");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                seatLock.unlock();
            }
        }
    }
}

これらの方法はいずれも、チケットの重複販売や不正な値の問題を解決できます。

タグ: Java マルチスレッド スレッドセーフ synchronized ReentrantLock

7月21日 01:34 投稿