在多线程开发中,经常会碰到将多个任务分配给多个线程,每个线程执行他的任务,但是,每个任务又分为好几个
阶段,每个阶段期望各个线程同时达到,意思是,每一步每个线程都要同步,当有一个线程走完第一步的时候,他得等
待其他的线程都完成第一步了才能继续下一步,步调一致能解决很多问题。下面我们使用一个例子,这个例子是模拟遍
历机器上的一些文件,找出以log结尾的文件,并且他的最后修改时间为24小时以内,我们开启3个线程去完成这个任
务。并且使用Phaser来同步各个任务。
package com.bird.concursey.charpet5; import java.io.File; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.Phaser; import java.util.concurrent.TimeUnit; /** * This class implements the operation of searching for files with a determined * extension modified in the last 24 hours in a folder and its subfolders * * @author bird 2014年9月22日 下午10:12:18 */ public class FileSearch implements Runnable { // store the folder in which the search operation will begin. private String initPath; // store the extension of the files we are going to look for. private String end; // store the full path of the files we will find with the desired // characteristics. private List<String> results; private Phaser phaser; public FileSearch(String initPath, String end, Phaser phaser) { this.initPath = initPath; this.end = end; this.phaser = phaser; this.results = new ArrayList<String>(); } @Override public void run() { //The search won't begin until all the threads have been created. phaser.arriveAndAwaitAdvance(); System.out.printf("%s: Starting.\n",Thread.currentThread().getName()); File file = new File(initPath); if(file.isDirectory()) { directoryProcess(file); } if(!checkResults()) { return; } filterResults(); if(!checkResults()) { return; } showInfo(); phaser.arriveAndDeregister(); System.out.printf("%s: Work completed.\n",Thread.currentThread().getName()); } /** * It receives a File object as a parameter and it processes all its files * and subfolders. * * @param file */ private void directoryProcess(File file) { File files[] = file.listFiles(); if (files != null) { for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { directoryProcess(files[i]); } else { fileProcess(files[i]); } } } } /** * checks if its extension is equal to the one we are looking for * * @param file */ private void fileProcess(File file) { if (file.getName().endsWith(end)) { results.add(file.getAbsolutePath()); } } /** * deleting the files that were modified more than 24 hours ago */ private void filterResults() { List<String> newResults = new ArrayList<String>(); long actualDate = new Date().getTime(); for (int i = 0; i < results.size(); i++) { File file = new File(results.get(i)); long fileDate = file.lastModified(); if (actualDate - fileDate < TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS)) { newResults.add(results.get(i)); } } results = newResults; } private boolean checkResults() { if (results.isEmpty()) { System.out.printf("%s: Phase %d: 0 results.\n", Thread.currentThread().getName(), phaser.getPhase()); System.out.printf("%s: Phase %d: End.\n", Thread.currentThread().getName(), phaser.getPhase()); //停止phaser phaser.arriveAndDeregister(); return false; }else{ System.out.printf("%s: Phase %d: %d results.\n",Thread.currentThread().getName(),phaser.getPhase(),results.size()); //this thread has finished the actual //phase and it wants to be blocked until all the participant threads in the phased //operation finish the actual phase. phaser.arriveAndAwaitAdvance(); return true; } } private void showInfo() { for(int i = 0; i < results.size(); i++) { File file = new File(results.get(i)); System.out.printf("%s: %s\n",Thread.currentThread().getName(),file.getAbsolutePath()); } phaser.arriveAndAwaitAdvance(); } public static void main(String[] args) throws Exception { Phaser phaser = new Phaser(3); FileSearch system = new FileSearch("C:\\Windows", "log", phaser); FileSearch apps = new FileSearch("C:\\Program Files", "log", phaser); FileSearch documents = new FileSearch("C:\\ProgramData", "log", phaser); Thread systemThread = new Thread(system, "System"); Thread appsThread = new Thread(apps, "appsThread"); Thread documentsThread = new Thread(documents, "documentsThread"); systemThread.start(); appsThread.start(); documentsThread.start(); systemThread.join(); appsThread.join(); documentsThread.join(); System.out.println("Terminated: "+ phaser.isTerminated()); } }
The program starts creating a Phaser object that will control the synchronization of the threads
at the end of each phase. The constructor of Phaser receives the number of participants as
a parameter. In our case, Phaser has three participants. This number indicates to Phaser
the number of threads that have to execute an arriveAndAwaitAdvance() method before
Phaser changes the phase and wakes up the threads that were sleeping.
Once Phaser has been created, we launch three threads that execute three different
FileSearch objects.
The first instruction in the run() method of this FileSearch object is a call to the
arriveAndAwaitAdvance() method of the Phaser object. As we mentioned earlier, the
Phaser knows the number of threads that we want to synchronize. When a thread calls this
method, Phaser decreases the number of threads that have to finalize the actual phase and
puts this thread to sleep until all the remaining threads finish this phase. Calling this method
at the beginning of the run() method makes none of the FileSearch threads begin their
job until all the threads have been created.
At the end of phase one and phase two, we check if the phase has generated results and the
list with the results has elements, or otherwise the phase hasn‘t generated results and the list
is empty. In the first case, the checkResults() method calls arriveAndAwaitAdvance()
as explained earlier. In the second case, if the list is empty, there‘s no point in the thread
continuing with its execution, so it returns. But you have to notify the phaser that there will be
one less participant. For this, we used arriveAndDeregister(). This notifies the phaser
that this thread has finished the actual phase, but it won‘t participate in the future phases, so
the phaser won‘t have to wait for it to continue.
At the end of the phase three implemented in the showInfo() method, there is a call to the
arriveAndAwaitAdvance() method of the phaser. With this call, we guarantee that all the
threads finish at the same time. When this method ends its execution, there is a call to the
arriveAndDeregister() method of the phaser. With this call, we deregister the threads
of the phaser as we explained before, so when all the threads finish, the phaser will have zero
participants.
Finally, the main() method waits for the completion of the three threads and calls the
isTerminated() method of the phaser. When a phaser has zero participants, it enters the
so called termination state and this method returns true. As we deregister all the threads of
the phaser, it will be in the termination state and this call will print true to the console.