`
ftj20003
  • 浏览: 132348 次
  • 性别: Icon_minigender_1
  • 来自: ...
社区版块
存档分类
最新评论

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

    博客分类:
  • 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堆了一个实现。先简单的说说思路,分析知有三个对象是必须的:线程对象,文件对象以及共享协调对象。线程对象不用多说了,扩展Thread,为其提供一个唯一的绑定值;文件对象可以使用虚拟文件先代替,反正写入文件和写入对象存储大同小异;最后就是负责调度线程写入文件的共享协调对象,这个对象的职责就是保证线程安全并且提供多线程正确写入数值到文件的方法。我能想到的方式有两种:一个是控制线程顺序依次写入文件,显然这种方式难度较大,毕竟线程之间的执行顺序很难保证;另一种就是线程竞争写入权,共享协调对象向获得写入权限的线程提供符合条件的文件写入。后者不用控制线程的执行顺序,并且各个线程完全相对独立,故而我采用了后面得方案进行实现:
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的theLatestNum属性记录每个实例最后一次写入的值,由于每个线程仅仅写入一个固定的值,所以一旦得到文件最后一次写入的值就可以判断此文件是否符合线程写入的条件。另外起初我仅仅是使用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...
3
0
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    多线程面试题

    以上是对“多线程面试题”这一主题的简要概述,实际面试中可能会涉及到更深层次的问题,如并发模型、线程池的优化策略、线程安全的实现原理等。深入理解和熟练运用这些知识点,将有助于你在面试中脱颖而出,解决实际...

    java经典多线程面试题

    在Java中,多线程是一种非常重要的编程概念,...这些面试题涵盖了Java多线程编程的基础知识、同步机制、线程间通信以及并发集合类等多个方面。在准备面试时,对这些问题进行深入理解和准备,能够有效提升面试的成功率。

    2022java面试题、JVM面试题、多线程面试题、并发编程、Redis面试题、MySQL面试题、Java2022面试题

    2022java面试题、JVM面试题、多线程面试题、并发编程、Redis面试题、MySQL面试题、Java2022面试题、Netty面试题、Elasticsearch面试题、Tomcat面试题、Dubbo面试题、Kafka面试题、Linux面试题、2021面试题、java面试...

    史上最全 Java 多线程面试题及答案

    了解这些核心概念后,开发者可以更好地应对Java多线程面试中可能出现的问题,同时也能在实际项目中灵活运用多线程技术,提升程序性能。多线程编程虽然复杂,但掌握好相关的工具和原理,就能有效地解决并发问题,编写...

    java多线程面试题和答案

    以下是一些关于Java多线程的面试题及其答案,涵盖了基础概念、并发控制、线程安全以及性能优化等方面。 1. **什么是Java多线程?** 多线程是指在单个程序中同时执行多个线程,这样可以提高应用程序的效率和响应...

    C#面试题 包括 ADO.net 多线程等

    C#面试题 包括 ADO.net 多线程等 C#面试题 包括 ADO.net 多线程等 C#面试题 包括 ADO.net 多线程等 C#面试题 包括 ADO.net 多线程等 C#面试题 包括 ADO.net 多线程等

    10万字总结java面试题和答案(八股文之一)Java面试题指南

    多线程&并发面试题 JVM面试题 Mysql面试题 Redis面试题 Memcached面试题 MongoDB面试题 Spring面试题 Spring Boot面试题 Spring Cloud面试题 RabbitMQ面试题 Dubbo 面试题 MyBatis 面试题 ZooKeeper 面试题 数据结构...

    Java面试题、JVM面试题、多线程面试题、并发编程、设计模式面试题

    Java面试题、JVM面试题、多线程面试题、并发编程、设计模式面试题、SpringBoot面试题、SpringCloud面试题、MyBatis面试题、Mysql面试题、VUE面试题、算法面试题、运维面试题。 收集汇总各行业笔试or编程题解题思路 ...

    java面试题_多线程(68题).pdf

    Java中的多线程是面试中常见的话题,涵盖了操作系统的基础概念以及Java并发库的高级特性。...了解这些概念有助于深入理解Java多线程编程,它们在面试中常常作为考察点,对于开发高效、可靠的并发应用程序至关重要。

    java多线程面试题59题集合

    以下是对Java多线程面试题59题集合中可能涉及的一些关键知识点的详细解析。 1. **线程的创建方式** - 继承Thread类:创建一个新的类,该类继承自Thread类,并重写其run()方法。 - 实现Runnable接口:创建一个实现...

    Java面试题、JVM面试题、多线程面试题

    标题提到的是"Java面试题、JVM面试题、多线程面试题",而描述和标签却提及"python编程"。不过,既然您希望聚焦于"Java面试题、JVM面试题、多线程面试题",我将为您详细介绍这些主题。 **Java面试题** 1. **Java是...

    java多线程面试题

    以上知识点涵盖了多线程编程在Java中的基础理论和实际操作,包括线程的创建、运行、异常处理以及线程安全等问题,这些都是在进行Java多线程面试时常见的问题,对于理解和掌握Java多线程编程至关重要。

    Java线程面试题Top50[参照].pdf

    Java 线程面试题 Top 50 Java 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。 一、什么是...

    多线程面试59题(含答案).pdf

    多线程面试59题(含答案)是关于多线程编程的知识点总结,涵盖了多线程的基本概念、优点、线程和进程的区别、Java 实现多线程的方式、启动线程方法的区别、终止线程的方式、线程的生命周期、wait()和 sleep()方法的...

    热门Java面试多线程面试题问答Top50共17页.pdf

    【标题】"热门Java面试多线程面试题问答Top50共17页.pdf" 提供了一份关于Java多线程面试的重要资源,涵盖了面试中可能会遇到的50个关键问题和答案,共计17页。这表明该文档深入探讨了Java编程中的并发处理和线程管理...

    最新各大公司企业真实面试题-Java面试题

    "Java 面试题及其答案.doc"和"JAVA面试题.doc"提供了大量的面试题及解答,涵盖了从基础知识到高级特性的广泛范围,包括反射、注解、设计模式、Spring框架、数据库操作等。通过这些题目,求职者可以自我评估,了解...

    java面试题之多线程.pdf

    Java中的多线程是面试中常见的话题,因为它在并发编程中扮演着重要角色。线程允许应用程序同时执行多个任务,从而提高系统效率和响应性。理解线程的概念、创建方式以及状态转换对于Java开发者至关重要。 1. **线程...

    java面试题,java框架面试题

    Java 面试题是 Java 开发人员面试的必备知识,涵盖了 Java 基础知识、Java 框架、Java 集合框架、Java 多线程、Java 网络编程等方面的知识点。在本文中,我们将对 Java 面试题进行总结和分析,帮助读者快速掌握 Java...

Global site tag (gtag.js) - Google Analytics