论述题 4.  一个文件中有10000个数,用Java语言实现一个多线程程序,将这10000个数输出到5个不同文件中(不要求输出到每个文件中的数量相同)。要求启动10个线程,两两一组,分为5组。每组两个线程分别将文件中的奇数和偶数输出到该组对应的一个文件中,需要偶数线程每打印10个偶数以后,就将奇数线程打印10个奇数,如此交替进行。同时需要记录输出进度,每完成1000个数就在控制台中打印当前完成数量,并在所有线程结束后,在控制台输出“Done”。
【正确答案】本题考查的是对多线程编程的理解。为了便于理解,首先用随机函数随机生成10000个数放到文件中,以供测试使用。一次把这10000条记录读到内存中,平均分配给5组线程并行处理,因此,本题的难点是如何控制打印偶数的线程和打印奇数的线程轮流运行。
   本题通过Java提供的Condition来实现线程的同步。Condition是在Java 1.5中才出现的,它用来替代传统的Object类的wait()、notify()方法,以实现线程间的协作,相比使用Object类的wait()、nofify()方法,使用Condition的await()、signal()这种方式实现线程间协作,更加安全和高效。它主要有如下特点:
   1)Condition最常用的方法为await()和signal(),其中,await()对应Object类的wait()方法,signal()对应Object类的notify()方法。
   2)Condition依赖于Lock接口,生成一个Condition的代码为lock.newCondition()。
   3)调用Condition的await()和signal()方法必须在lock保护之内。
   对于本题而言,定义两个Condition(oddLock和evenLock),首先打印奇数的线程开始运行,通过调用evenLock.await()来等待打印偶数的线程执行。接着打印偶数的线程开始运行,当输出10个偶数或者没有偶数输出后,调用evenLock.signal()来通知打印奇数的线程开始运行,然后调用oddLock.wait方法来等待打印奇数的线程运行完成。通过这种方法来控制奇数线程与偶数线程的运行顺序,实现代码如下:
   import java.io.*;
   import java.util.Random;
   import java.util.concurrent.locks.Condition;
   import java.util.concurrent.locks.Lock;
   import java.util.concurrent.locks.ReentrantLock;
   public class Test
   {
   private static final int count=10000;
   private static final int threadGruopCount=5;
   private static final String inputFile="testInput.txt";
   public static void generateTestFile()throws IOException
   {
   //用随机数生成10000个测试数据放到文件中
   PrintWriter pw=new PrintWriter(new FileWriter(new File(inputFile)),true);
   Random random=new Random();
   for(inti=0;i<count;i++)
   {
   pw.write(Math.abs(random.nextInt())%count+",");
   }
   pw.flush();
   pw.close();
   }
   public static void main(String[]args)
   {
   try
   {
   generateTestFile();
   BufferedReader reader=new BufferedReader(new FileReader(inputFile));
   String str=reader.readLine();
   reader.close();
   String[]strs=str.split(",");
   int index=0;
   //为了简单,每个文件输出数字的个数相同
   int countForEachFile=count/threadGmopCount;
   for(int i=0;i<threadGmopCount;i++)
   {
   int records[]=new int[countForEachFile];
   for(int j=0;j<countForEachFile;j++)
   {
   records[j]=Integer.parseInt(strs[index]);
   index++;
   }
   PrintGroup group=new PrintGroup(records,i);
   group.startPrint();
   }
   }
   catch(Exception e)
   {
   e.printStackTrace();
   }
   }
   }
   class PrintGroup{
   //这个线程组输出数字的个数
   private static volatile int count=0;
   private Lock lock=new ReentrantLock();
   private Condition oddLock=lock.newCondition();
   private Condition evenLock=lock.newCondition();
   //这个线程组需要输出的数字数组
   private int records[];
   //这个线程组需要把数字输出到同一个文件,因此,共享一个writer
   //由于任意时刻只会有一个线程写文件,因此,不需要同步
   private PrintWriter writer;
   //记录输出奇数所在的数组下标
   private volatile int oddIndex=0;
   //记录输出偶数所在的数组下标
   private volatile im evenlndex=0;
   //输出奇数的线程
   private OddPrintThread oddPrintThread;
   //输出偶数的线程
   private EvenPrintThread evenPrintThread;
   private volatile boolean first=true;
   private int[] result=new int[2000];
   private int index=0;
   public PrintGroup(int[] records, int id) throws Exception {
   this.records=records;
   this.writer=new PrintWriter(new FileWriter(new File("output" + id + ".txt")), true);
   }
   public void startPrint() {
   oddPrintThread=new OddPrintThread();
   evenPrintThread=new EvenPrintThread();
   oddPrintThread.start();
   evenPrintThread.start();
   }
   private class OddPrintThread extends Thread
   {
   @Override
   public void run()
   {
   while (true)
   {
   try{
   lock.lock();
   if(first)//第一次运行时,需要等待打印偶数的线程先执行
   {
   first=false;
   evenLock.await();
   }
   for (int i=0; i<10;)
   if(oddIndex>=records.length&& evenIndex>=records.length)
   {
   writer.flush();
   writer.close();
   return;
   }
   //如果所有事物奇数都打印完了,则不打印奇数,让打印偶数的线程有
   //运行机会
   if (oddlndex>=records.length )
   {
   break;
   }
   if(records[oddIndex]%2==1)
   {
   i++;
   writer.print(records[oddlndex] + " ");
   result[index++]=records[oddlndex];
   writer.flush();
   addCount();
   }
   oddIndex++;
   }
   //打印完10个奇数后,通知打印偶数的线程开始运行
   oddLock.signal();
   //接着等待打印偶数的线程结束
   evenLock.await();
   }
   catch(Exception e)
   {
   e.printStackTrace{};
   }
   finally
   {
   oddLock.signal();
   lock.unlock();
   }
   }
   }
   }
   private class EvenPrintThread extends Thread{
   @Override
   public void rum()
   {
   while(true)
   {
   try{
   //等待打印奇数的线程先运行。如果这个线程先运行调用evenLock.signal();
   //然后打印奇数线程才开始运行,打印奇数线程会通过调用evenLock.await();
   //进入休眠状态,此时打印奇数线程将永远不会被唤醒
   while(first){
   Thread.sleep(1);
   }
   lock.lock();
   for(int i=0;i<10;)
   {
   if(oddIndex>=records.length&&evenIndex>=records.length)
   {
   String s="";
   for(int k=0;k<2000;k++)
   {
   s+=(result[k]+" ");
   }
   writer.flush();
   return;
   }
   if(evenlndex>=records.length)
   {
   break;
   }
   if(records[evenIndex]%2==0)
   {
   i++;
   writer.print(records[everdndex]+" ");
   result[index++]=records[evenIndex];
   writer.flush();
   addCount();
   }
   evenIndex++;
   }
   evenLock.signal();
   oddLock.await();
   }
   catch(Exception e)
   {
   e.printStackTrace();
   }
   finally
   {
   evenLock.signal();
   lock.unlock();
   }
   }
   }
   }
   private synchronized static void addCount()
   {
   count++;
   if(count%1000==0)
   {
   System.out.println("已完成:  "+count);
   if(count==10000)
   {
   System.out.println("Done");
   }
   }
   }
   }
   程序的运行结果为:
   己完成:1000
   己完成:2000
   已完成:3000
   已完成:4000
   已完成:5000
   已完成:6000
   己完成:7000
   已完成:8000
   已完成:9000
   已完成:10000
   Done
【答案解析】