题目描述:
一个文件中有10000个数,用Java语言实现一个多线程程序,将这10000个数输出到5个不同的文件中。要求启动10个线程,两两一组,分为5组。每组两个线程分别将文件中的奇数和偶数输出到改组对应的一个文件中,需要偶数线程每打印10个偶数,就将奇数线程打印10个奇数,如此交替执行。同时需要输出记录输出速度,每完成1000个数就在控制台中打印当前完成数量,并在所有线程结束后,在控制台输出"Done".
题目所涉及知识点:
Condition是java 1.5 中才提出来的,它用来代替传统的Object类的wait()、notify()方法,以实现线程间的协作,相比使用Object类的wait()、notify()方法,使用Condition类的await()、signal()这种方法实现线程间的协作,更加安全和高效。它主要由如下的特点:
1)Condition最常用的方法为await()和signal(),其中,await()对应Object类的wait()方法,signal()对应Object类的notify()方法;
2)Condition依赖于Lock接口,生成一个Condition的代码为lock.newCondition();
3)调用Condition的await()和signal()方法必须在lock保护之内。
下面描述解决问题的思路。
根据题目的要求,
第一步,先生成10000个随机数,保存到文件中,不妨设为"input.txt"。
第二步,读取10000个随机数,按照题目约定我们10000个数分为5组,每一组打印2000个数。这样我们就可以分组来进行处理;
第三步,对于其中一组(2000个数),我们单独来考虑,其他的组用循环遍历解决即可。
a) 这里,我们可以设计成两个线程,分别为打印奇数的线程、打印偶数的线程,通过Condition类来协调完成奇数偶数相间的打印。
b) 按照约定,我们需要让偶数先打印10个,然后接着让奇数线程打印10个,这样协调进行。退出的条件为:该组2000个数全都遍历完成。
c) 另有,题目要求每打印1000个数要控制台输出一下进度,这样我们显然就需要定义一个全局的变量,来累加。我们知道必须要让这个累加的操作是线程安全的,我们可以将这个count的变量使用关键字volatile,然后结合synchronized关键字来保证累加的操作是线程安全的(同理奇数偶数的计数器也是这样的做法)。
最后就得出我们想要的代码:
package thread;
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 DemoTest {
private static final int count = 10000;
private static final int threadGroupCount = 5;
private static final String inputFile = "testInput.txt";
/**
* 生成文件
*
* @throws IOException
*/
public static void generateTestFile() throws IOException {
PrintWriter pw = new PrintWriter(new FileWriter(new File(inputFile)), true);
Random random = new Random();
for (int i = 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 / threadGroupCount;
for (int i = 0; i < threadGroupCount; i++) {
int records[] = new int[countForEachFile];
for (int j = 0; j < countForEachFile; j++) {
records[j] = Integer.parseInt(strs[index]);
index++;
}
// 调用打印线程打印
PrintGroup printGroup = new PrintGroup(records, i);
printGroup.startPrint();
}
} catch (IOException 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 int evenIndex = 0;
// 输出奇数的线程
private OddPrintThread oddPrintThread;
// 输出偶数的线程
private EvenPrintThread evenPrintThread;
private volatile boolean first = true;
private int index = 0;
/**
* 开启打印线程
*/
public void startPrint() {
oddPrintThread = new OddPrintThread();
evenPrintThread = new EvenPrintThread();
oddPrintThread.start();
evenPrintThread.start();
}
/**
* 构造函数
*
* @param records 待输出的数组
* @param id 组号
* @throws IOException
*/
public PrintGroup(int[] records, int id) throws IOException {
this.records = records;
this.writer = new PrintWriter(new FileWriter(new File("output" + id + ".txt")));
}
/**
* 增加count计数
*/
private synchronized static void addCount() {
count++;
if (count % 1000 == 0) {
System.out.println("已完成:" + count);
if (count == 10000) {
System.out.println("Done");
}
}
}
/**
* 奇数线程
*/
private class OddPrintThread extends Thread {
@Override
public void run() {
try {
while (true) {
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 (oddIndex >= records.length) {
break;
}
// 把奇数输出到文件,并计数
if (records[oddIndex] % 2 == 1) {
// i累加,目的是连续打印10个奇数,直到打印完成即可
i++;
writer.print(records[oddIndex] + " ");
writer.flush();
// 累加数
addCount();
}
// 让奇数游标继续往后
oddIndex++;
}
// 打印完10个数字后通知偶数线程继续打印
oddLock.signal();
// 接着等待打印偶数的线程结束
evenLock.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
oddLock.signal();
lock.unlock();
}
}
}
/**
* 偶数线程
*/
private class EvenPrintThread extends Thread {
@Override
public void run() {
while (true) {
try {
// 让奇数线程先执行
while (first) {
Thread.sleep(1);
}
lock.lock();
for (int i = 0; i < 10; ) {
// 如果为退出循环条件
if (oddIndex >= records.length && evenIndex >= records.length) {
writer.flush();
return;
}
// 如果遍历偶数的下标(evenIndex)已经到边际了
if (evenIndex >= records.length) {
break;
}
// 如果找到偶数
if (records[evenIndex] % 2 == 0) {
// 累积
i++;
// 写入L文件中
writer.print(records[evenIndex] + " ");
// 刷新缓存
writer.flush();
// 累积count,如果缝1000的倍数就输出已完成:xxx
addCount();
}
// 偶数游标继续往后
evenIndex++;
}
// 让奇数线程开始执行
evenLock.signal();
// 等待奇数数线程执行结束
oddLock.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
evenLock.signal();
lock.unlock();
}
}
}
}
}
里面涉及Condition的具体的细节需要查阅相关的文档即可。
最后运行的结果如下: