2 つのCountDownLatchesと 1 つのSemaphoreを使用できます。最初のカウントダウン ラッチは、スレッドの開始を同期して、すべてのスレッドを同時に開始できるようにします。2 番目のカウントダウン ラッチは、スレッドの 1 つが終了したときに通知します。セマフォは、勝者のスレッドのみが完了することを許可し、どのスレッドが勝者であるかを尋ねている間に他のスレッドが終了する可能性がある競合状態を防ぎます。また、Bot クラスにある種の完了フラグを追加して、メイン スレッドがどちらが最初に完了したかを判断できるようにする必要がありますisAlive()
。
同時に開始するスレッドは、スレッドスケジューラに依存することに注意してください。サンプルコードは次のとおりです。
スレッドを作成して開始するスレッドコントローラー
public void threadController() throws Exception
{
int numWorkers = 20;
List<Worker> workerList = new ArrayList<Worker>(numWorkers);
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(1);
//Semaphore prevents only one thread from completing
//before they are counted
Semaphore pauseForCheck = new Semaphore(1);
for(int i=0; i<numWorkers; i++)
{
Worker worker = new Worker(i, startSignal, doneSignal, pauseForCheck);
Thread thread = new Thread(worker);
//worker has started, but will block on await();
thread.start();
workerList.add(worker);
}
//tell workers they can start
startSignal.countDown();
//wait for one thread to complete.
doneSignal.await();
//Look at all workers and find which one is done
for (int i=0; i< numWorkers; i++)
{
if(workerList.get(i).isCompleted())
{
System.out.printf("Thread %d finished first\n", i);
}
}
//add permits to semaphore so all losing threads can finish
pauseForCheck.release(numWorkers - 1);
}
実際に仕事をするワーカークラス
class Worker implements Runnable
{
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
private final Semaphore pauseForCheck;
private final int id;
private boolean completed = false;
public Worker(int id, CountDownLatch startSignal, CountDownLatch doneSignal, Semaphore pauseForCheck )
{
this.id = id;
this.startSignal = startSignal;
this.doneSignal = doneSignal;
this.pauseForCheck = pauseForCheck;
}
public boolean isCompleted()
{
return completed;
}
public void run()
{
try
{
//block until controller counts down the latch
startSignal.await();
//simulate real work
Thread.sleep((long) (Math.random() * 1000));
//try to get the semaphore. Since there is only
//one permit, the first worker to finish gets it,
//and the rest will block.
pauseForCheck.acquire();
}
catch (InterruptedException e)
{
//don't care about this
}
//Use a completed flag instead of Thread.isAlive because
//even though countDown is the last thing in the run method,
//the run method may not have before the time the
//controlling thread can check isAlive status
completed = true;
//tell controller we are finished
doneSignal.countDown();
}