疑似Google多线程面试题的Java实现

来到一个完全陌生的地方,即将一切从新开始,内心兴奋又忐忑。这几天忙着租房,装宽带直到今天才算告一段落,几经折腾,总算能安静的写写东西了。对我而言排解紧张情绪和压力的方式,就是投入一个问题的思考和解决中。之前在网上找相关的多线程问题,想通过演练来加深和熟练对多线程的掌握时,看到一个疑似Google的多线程面试题,感觉可以思考一下,题目如下:
启动4个线程,向4个文件A,B,C,D里写入数据,每个线程只能写一个值。
线程1:只写1
线程2:只写2
线程3:只写3
线程4:只写4
4个文件A,B,C,D。
程序运行起来,4个文件的写入结果如下:
A:12341234...
B:23412341...
C:34123412...
D:41234123...

这个题目主要是要解决线程调度的问题,可能原来是要求C/C++多线程的实现,不过语言不是问题的关键,我尝试用Java浩瀚的API堆了一个实现。先简单的说说思路,分析知有三个对象是必须的:[color=red]线程对象,文件对象以及共享协调对象[/color]。线程对象不用多说了,扩展Thread,为其提供一个唯一的绑定值;文件对象可以使用虚拟文件先代替,反正写入文件和写入对象存储大同小异;最后就是负责调度线程写入文件的共享协调对象,这个对象的职责就是保证线程安全并且提供多线程正确写入数值到文件的方法。我能想到的方式有两种:一个是控制线程顺序依次写入文件,显然这种方式难度较大,毕竟线程之间的执行顺序很难保证;[color=red]另一种就是线程竞争写入权,共享协调对象向获得写入权限的线程提供符合条件的文件写入[/color]。后者不用控制线程的执行顺序,并且各个线程完全相对独立,故而我采用了后面得方案进行实现:
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
* @author: yanxuxin
* @date: 2010-2-18
*/
public class MultiThreadWriter {

public static String[] FILE_NAMES = { "A", "B", "C", "D" };

/** 文件路径 */
public static String FILE_PATH = "D:" + File.separator;

private final List<MockOutput> outList = new ArrayList<MockOutput>(FILE_NAMES.length);

/** 平衡优先级的比较器 */
private final Comparator<MockOutput> comparator = new PriorityComparator();

/** 线程运行开关 */
private volatile boolean working = true;

public static void main(String[] args) {
final MultiThreadWriter writer = new MultiThreadWriter();
final CountDownLatch start = new CountDownLatch(1); // 发令信号
final CountDownLatch end = new CountDownLatch(64); // 终止信号

for (int i = 0; i < FILE_NAMES.length; i++) {
writer.new WriteWorker(writer, i + 1, start, end).start(); // 开启4个线程,每个线程分别只写入单一值
}

start.countDown(); // 熟悉的开闸放狗

try {
end.await(); // 等待其他子线程工作
}
catch (InterruptedException e) {
e.printStackTrace();
}

writer.cleanAndDisplay(); // 计数为0时,打印最后一次调整了存储顺序的StringBuilder的值
}

public MultiThreadWriter() {
try {
init();
}
catch (IOException e) {
e.printStackTrace();
}
}

public boolean isWorking() {
return working;
}

/**
* 供子线程调用并写入线程绑定的数值给文件和StringBuilder
*
* @param num
* @return
*/
public boolean writeToOutput(int num) {
synchronized (outList) {
for (int i = 0; i < FILE_NAMES.length; i++) {
MockOutput output = outList.get(i);

int lastest = output.getTheLatestNum();
if ((lastest % FILE_NAMES.length) + 1 == num) { // 取此output最后一次写入的值,判断是否满足再次写入
output.append(num);
output.setTheLatestNum(num);

adjustPriority(); // 调整outList中各MockOutput实例的顺序

return true;
}
}
}
return false;
}

public void cleanAndDisplay() {
working = false;
synchronized (outList) {
for (int i = 0; i < FILE_NAMES.length; i++) {
MockOutput out = outList.get(i);
out.close();

System.out.println(out.toString());
}
}
}

/**
* 初始化每个MockOutput实例的最后一次写入值,并把其加入到outList中
* @throws IOException
*/
private void init() throws IOException {
for (int i = 0; i < FILE_NAMES.length; i++) {
MockOutput output = new MockOutput(64, FILE_NAMES[i], FILE_PATH);
output.setTheLatestNum(i);
outList.add(output);
}
}

/**
* 使用Comparator的自身实现对outList内的对象进行排序,其作用在于平衡写入优先级.
* 例如:线程Thread-2取得了outList对象的锁执行写入,其绑定的值为3,而outList中
* 有两个MockOutput实例的最后一次写入都为2,均满足再次写入的条件.但是其中一个的长度
* 远远大于另一个,并且其在outList内的位置又在另一个之前,则其将先于后者获得再次写
* 入得机会,使得两者的机会不均衡.
* 此方法的作用就在于把写入机会少的MockOutput调整到最前面从而提高其被写入的机会.
*/
private void adjustPriority() {
Collections.sort(outList, comparator);
}

private class WriteWorker extends Thread {

/** 多个线程共享同一个MultiThreadWriter实例 */
private MultiThreadWriter writer;

/** 线程绑定的写入值,每个线程只反复写入一个固定值 */
private final int num;

private final CountDownLatch start;
private final CountDownLatch end;

public WriteWorker(MultiThreadWriter writer, int num, CountDownLatch start, CountDownLatch end) {
this.writer = writer;
this.num = num;
this.start = start;
this.end = end;
}

public void run() {
try {
start.await();// 等待主线程一声令下,所有的子线程均唤醒开始工作
doWork();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}

/**
* 子线程写入固定值,写入成功则递减计数器
*
* @throws InterruptedException
*/
private void doWork() throws InterruptedException {
while (writer.isWorking()) {
boolean isWrited = writer.writeToOutput(num);
if (isWrited) {
end.countDown();
Thread.sleep(50); // 调整线程竞争,使得每个线程获取outList锁的几率均衡
}
}
}
}

private class MockOutput {

/** 用于终端显示的记录 */
private final StringBuilder stringBuilder;

/** 需要写入的文本输出 */
private final PrintWriter printWriter;

/** 文件名 */
private final String name;

/** 最后一次写入的值 */
private int theLatestNum;

/** 优先级因子,值越大优先级越低 */
private int priorityFactor = 0;

public MockOutput(int capacity, String name, String path) throws IOException {
this.stringBuilder = new StringBuilder(capacity);
this.name = name;
this.printWriter = new PrintWriter(new FileWriter(path + name + ".txt"));
}

public int getTheLatestNum() {
return theLatestNum;
}

public void setTheLatestNum(int theLatestNum) {
this.theLatestNum = theLatestNum;
}

public int getPriorityFactor() {
return priorityFactor;
}

/**
* 递增优先级因子的值,降低写入优先级
*/
private void reducePriority() {
priorityFactor++;
}

/**
* 写入操作
*
* @param i
* @return
*/
public MockOutput append(int i) {
stringBuilder.append(i);
printWriter.print(i);
reducePriority();
return this;
}

/**
* 关闭文本输出
*/
public void close() {
printWriter.close();
}

public String toString() {
return name + ": " + stringBuilder.toString();
}
}

/**
* 用于平衡写入优先级的比较器
*
* @author: yanxuxin
* @date: 2010-2-24
*/
private class PriorityComparator implements Comparator<MockOutput> {

@Override
public int compare(MockOutput o1, MockOutput o2) {
int pf1 = o1.getPriorityFactor();
int pf2 = o2.getPriorityFactor();
return pf1 - pf2;
}

}

}

这个实现中,虚拟文件对象MockOutput的[color=red]theLatestNum[/color]属性记录每个实例最后一次写入的值,由于每个线程仅仅写入一个固定的值,所以一旦得到文件最后一次写入的值就可以判断此文件是否符合线程写入的条件。另外起初我仅仅是使用StringBuilder记录写入的值,PrintWriter是重构时加入的,所以两者都保留了。WriteWorker既是写入线程,其循环检测共享协调对象MultiThreadWriter的working的最新值是否为true,从而调用MultiThreadWriter的writeToOutput(int)去写文件。最初的版本并没有PriorityComparator这个比较器也保证了程序的正常工作。但是输出的结果形如:
A:1234123412341234...
B:23412341234...
C:341234...
D:4123...
之所以出现这样的不协调的结果是因为我使用List存储MockOutput是有序的,所以取值时如果排在前面的和排在后面的均满足写入的话,显然排名靠前的抢了被写入的机会,贫富差距拉大。不过现实虽然我改变不了,程序我还是能主宰的。所以重构时我加入了PriorityComparator这个比较器,在每次写入完毕时调用adjustPriority()使用比较器把机会少的调整到前面均衡一下,这样最终写入的几率基本上能相差无几。

这个实现其实基本上就是API的堆砌,没有什么自我实现的算法,如果以后有好的思路我会再次重构的。如果写程序能不用考虑吃饭,生活该多好啊...新的一年祝愿Developers诸事顺利,我也要好好地准备一下了。为了兴趣,为了生活,Wish I Can...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值