到这里,基本上线程的并发中的知识点都是学到了,到了最后,还有三道面试题,从面试题中学习更加的加深一下,多线程中的知识点,如何在实际的问题中来解决多线程的问题,可以更好的从实际出发
一、面试题1
面试题题目:
现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:
1 package read; 2 3 public class Test { 4 5 public static void main(String[] args){ 6 7 System.out.println("begin:"+(System.currentTimeMillis()/1000)); 8 /*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。 9 修改程序代码,开四个线程让这16个对象在4秒钟打完。 10 */ 11 for(int i=0;i<16;i++){ //这行代码不能改动 12 final String log = ""+(i+1);//这行代码不能改动 13 { 14 Test.parseLog(log); 15 } 16 } 17 } 18 19 //parseLog方法内部的代码不能改动 20 public static void parseLog(String log){ 21 System.out.println(log+":"+(System.currentTimeMillis()/1000)); 22 23 try { 24 Thread.sleep(1000); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 30 }
看看这个问题是如何解决的?解题思路以及解题方法是什么?这里主要是应用了线程中的队列,将数据存存放到队列中,具体的方法如下:
1 import java.util.concurrent.ArrayBlockingQueue; 2 import java.util.concurrent.BlockingQueue; 3 4 public class Test { 5 6 public static void main(String[] args) { 7 8 BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1); 9 10 for (int i = 0; i < 4; i++) { 11 new Thread(new Runnable() { 12 @Override 13 public void run() { 14 while (true) { 15 try { 16 // 从队列中取出数据 17 String log = queue.take(); 18 Test.parseLog(log); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 } 24 }).start(); 25 } 26 27 System.out.println("begin:" + (System.currentTimeMillis() / 1000)); 28 /* 29 * 模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。 修改程序代码,开四个线程让这16个对象在4秒钟打完。 30 */ 31 for (int i = 0; i < 16; i++) { // 这行代码不能改动 32 final String log = "" + (i + 1);// 这行代码不能改动 33 { 34 // Test.parseLog(log); 35 try { 36 // 将数据存放到队列中 37 queue.put(log); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 } 43 } 44 45 // parseLog方法内部的代码不能改动 46 public static void parseLog(String log) { 47 System.out.println(log + ":" + (System.currentTimeMillis() / 1000)); 48 49 try { 50 Thread.sleep(1000); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 }
二、面试题2
面试题题目:
现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的。原代码如下:
1 package queue; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 7 System.out.println("begin:"+(System.currentTimeMillis()/1000)); 8 for(int i=0;i<10;i++){ //这行不能改动 9 String input = i+""; //这行不能改动 10 String output = TestDo.doSome(input); 11 System.out.println(Thread.currentThread().getName()+ ":" + output); 12 } 13 } 14 } 15 16 //不能改动此TestDo类 17 class TestDo { 18 public static String doSome(String input){ 19 20 try { 21 Thread.sleep(1000); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 String output = input + ":"+ (System.currentTimeMillis() / 1000); 26 return output; 27 } 28 }
解决方案如下:思路就是还是利用队列,并且加上了信号灯类Semaphore,解决的代码如下:
1 import java.util.concurrent.Semaphore; 2 import java.util.concurrent.SynchronousQueue; 3 4 public class Test { 5 6 public static void main(String[] args) { 7 // 使用队列来存取数据 8 SynchronousQueue<String> queue = new SynchronousQueue<String>(); 9 // 控制线程的读取数据 10 Semaphore semaphore = new Semaphore(1); 11 for (int i = 0; i < 10; i++) { 12 new Thread(new Runnable() { 13 @Override 14 public void run() { 15 String input; 16 try { 17 // 拿到信号灯 18 semaphore.acquire(); 19 // 从队列中取数据 20 input = queue.take(); 21 String output = TestDo.doSome(input); 22 System.out.println(Thread.currentThread().getName() + ":" + output); 23 // 释放信号灯 24 semaphore.release(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 }).start(); 30 } 31 32 System.out.println("begin:" + (System.currentTimeMillis() / 1000)); 33 for (int i = 0; i < 10; i++) { // 这行不能改动 34 String input = i + ""; // 这行不能改动 35 try { 36 // 将数据存放到队列中 37 queue.put(input); 38 } catch (InterruptedException e) { 39 e.printStackTrace(); 40 } 41 } 42 } 43 } 44 45 // 不能改动此TestDo类 46 class TestDo { 47 public static String doSome(String input) { 48 49 try { 50 Thread.sleep(1000); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 String output = input + ":" + (System.currentTimeMillis() / 1000); 55 return output; 56 } 57 }
三、面试题3
面试题目:
现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199615
请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚1秒输出结果,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199616
总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:
1 package syn; 2 3 //不能改动此Test类 4 public class Test extends Thread{ 5 6 private TestDo testDo; 7 private String key; 8 private String value; 9 10 public Test(String key,String key2,String value){ 11 this.testDo = TestDo.getInstance(); 12 /*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, 13 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/ 14 this.key = key+key2; 15 this.value = value; 16 } 17 18 19 public static void main(String[] args) throws InterruptedException{ 20 Test a = new Test("1","","1"); 21 Test b = new Test("1","","2"); 22 Test c = new Test("3","","3"); 23 Test d = new Test("4","","4"); 24 System.out.println("begin:"+(System.currentTimeMillis()/1000)); 25 a.start(); 26 b.start(); 27 c.start(); 28 d.start(); 29 30 } 31 32 public void run(){ 33 testDo.doSome(key, value); 34 } 35 } 36 37 class TestDo { 38 39 private TestDo() {} 40 private static TestDo _instance = new TestDo(); 41 public static TestDo getInstance() { 42 return _instance; 43 } 44 45 public void doSome(Object key, String value) { 46 47 // 以大括号内的是需要局部同步的代码,不能改动! 48 { 49 try { 50 Thread.sleep(1000); 51 System.out.println(key+":"+value + ":" 52 + (System.currentTimeMillis() / 1000)); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 } 57 } 58 59 }
解决方案,具体的思路是利用synchronized中参数为同一个对象之间的互斥来实现:
1 import java.util.Iterator; 2 import java.util.concurrent.CopyOnWriteArrayList; 3 4 //不能改动此Test类 5 public class Test extends Thread { 6 7 private TestDo testDo; 8 private String key; 9 private String value; 10 11 public Test(String key, String key2, String value) { 12 this.testDo = TestDo.getInstance(); 13 /* 14 * 常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, 15 * 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果 16 */ 17 this.key = key + key2; 18 19 /* 20 * String a = "1" + ""; 21 * String b = "1" + ""; 22 * 这个 a和b是同一个对象的 编译器会对这个进行优化 最终是 23 * a ="1"; b = "1"; 这个最终是字符串常量 是同一个对象 作为 this.key = key + key2; 24 * 因为key和key2是变量,所以这里this.key 是不同的对象 25 */ 26 27 this.value = value; 28 } 29 30 public static void main(String[] args) throws InterruptedException { 31 Test a = new Test("1", "", "1"); 32 Test b = new Test("1", "", "2"); 33 Test c = new Test("3", "", "3"); 34 Test d = new Test("4", "", "4"); 35 System.out.println("begin:" + (System.currentTimeMillis() / 1000)); 36 a.start(); 37 b.start(); 38 c.start(); 39 d.start(); 40 41 } 42 43 public void run() { 44 testDo.doSome(key, value); 45 } 46 } 47 48 class TestDo { 49 50 private TestDo() { 51 } 52 53 private static TestDo _instance = new TestDo(); 54 55 public static TestDo getInstance() { 56 return _instance; 57 } 58 59 // private List keys = new ArrayList(); 60 private CopyOnWriteArrayList keys = new CopyOnWriteArrayList(); 61 62 public void doSome(Object key, String value) { 63 64 Object o = key; 65 if (!keys.contains(o)) { 66 keys.add(o); 67 } else { 68 for (Iterator iter = keys.iterator(); iter.hasNext();) { 69 Object oo = iter.next(); 70 if (oo.equals(o)) { 71 o = oo; 72 } 73 } 74 } 75 76 // 以大括号内的是需要局部同步的代码,不能改动! 77 synchronized (o) // synchronized中所用的对象,必须保证是同一个对象,才能保证线程之间的互斥, 78 // 所以上面的代码处理来保证题目条件中的key相同的条件下,达到线程之间的互斥 79 { 80 try { 81 Thread.sleep(1000); 82 System.out.println(key + ":" + value + ":" + (System.currentTimeMillis() / 1000)); 83 } catch (InterruptedException e) { 84 e.printStackTrace(); 85 } 86 } 87 } 88 }