多线程经典面试题
现有程序同时启动了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 //不能改动此Test类 2 public class Test extends Thread{ 3 4 private TestDo testDo; 5 private String key; 6 private String value; 7 8 public Test(String key,String key2,String value){ 9 this.testDo = TestDo.getInstance(); 10 /*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, 11 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/ 12 this.key = key+key2; 13 this.value = value; 14 } 15 16 17 public static void main(String[] args) throws InterruptedException{ 18 Test a = new Test("1","","1"); 19 Test b = new Test("1","","2"); 20 Test c = new Test("3","","3"); 21 Test d = new Test("4","","4"); 22 System.out.println("begin:"+(System.currentTimeMillis()/1000)); 23 a.start(); 24 b.start(); 25 c.start(); 26 d.start(); 27 28 } 29 30 public void run(){ 31 testDo.doSome(key, value); 32 } 33 } 34 35 class TestDo { 36 37 private TestDo() {} 38 private static TestDo _instance = new TestDo(); 39 public static TestDo getInstance() { 40 return _instance; 41 } 42 43 public void doSome(Object key, String value) { 44 45 // 以大括号内的是需要局部同步的代码,不能改动! 46 { 47 try { 48 Thread.sleep(1000); 49 System.out.println(key+":"+value + ":" 50 + (System.currentTimeMillis() / 1000)); 51 } catch (InterruptedException e) { 52 e.printStackTrace(); 53 } 54 } 55 } 56 57 }
对于源代码中关于实现值相同而对象不同的效果进行解释:
对于:
a = “1”+”“;
b = “1”+””
编译器自动优化,所以a和b是同一个对象
而对于:key = key+key2; 由于是变量,编译器无法识别,这时a和b把“1”和“”赋值给key和key2时会得到两个不同的对象
思想:将集合中的对象作为同步代码块的锁,即this锁,每次将对象存入集合中的时候,就判断是否原集合中已经存在一个与将要存入集合的对象值相同的对象,即用equals比较,如果有,那么就获取原来的这个对象,把这个对象作为将要存入对象的锁,这样它们持有的就是同一把锁,即可实现互斥,这样就可以实现值相同的对象在不同的时刻打印的效果
代码中出现的问题:在遍历ArrayList集合查找与要存入值相同元素的时候,进行了添加的动作,所以会出现并发修改异常,因此使用并发的CopyOnWriteArrayList
1 import java.util.Iterator; 2 import java.util.concurrent.CopyOnWriteArrayList; 3 4 //不能改动此Test类 5 public class Test extends Thread { 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 /* 13 * 常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, 14 * 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果 15 */ 16 this.key = key + key2; 17 /* 18 * a = "1"+""; b = "1"+"" 19 */ 20 this.value = value; 21 } 22 23 public static void main(String[] args) throws InterruptedException { 24 Test a = new Test("1", "", "1"); 25 Test b = new Test("1", "", "2"); 26 Test c = new Test("3", "", "3"); 27 Test d = new Test("4", "", "4"); 28 System.out.println("begin:" + (System.currentTimeMillis() / 1000)); 29 a.start(); 30 b.start(); 31 c.start(); 32 d.start(); 33 34 } 35 36 public void run() { 37 testDo.doSome(key, value); 38 } 39 } 40 41 class TestDo { 42 43 private TestDo() { 44 } 45 46 private static TestDo _instance = new TestDo(); 47 48 public static TestDo getInstance() { 49 return _instance; 50 } 51 52 // private ArrayList keys = new ArrayList();用同步的ArrayList,迭代时可以添加数据 53 private CopyOnWriteArrayList keys = new CopyOnWriteArrayList(); 54 55 public void doSome(Object key, String value) { 56 Object o = key; 57 58 if (!keys.contains(o)) {// 两个线程可能同时走到这里,加上同步锁,避免两个“1”都被加入到keys集合中 59 synchronized (TestDo.class) { 60 if (!keys.contains(o)) { 61 keys.add(o); 62 } else { 63 for (Iterator iter = keys.iterator(); iter.hasNext();) {//for循环迭代 64 Object oo = iter.next(); 65 if (oo.equals(o)) { 66 o = oo;//使得key相同的两个线程使用同一把锁 67 break; 68 } 69 } 70 } 71 } 72 } 73 else { 74 for (Iterator iter = keys.iterator(); iter.hasNext();) { 75 Object oo = iter.next(); 76 if (oo.equals(o)) { 77 o = oo; 78 break; 79 } 80 } 81 } 82 synchronized (o) 83 // 以大括号内的是需要局部同步的代码,不能改动! 84 { 85 try { 86 Thread.sleep(1000); 87 System.out.println(key + ":" + value + ":" + (System.currentTimeMillis() / 1000)); 88 } catch (InterruptedException e) { 89 e.printStackTrace(); 90 } 91 } 92 } 93 94 }