Java高并发编程:使用JDK5中同步技术的3个面试题
第一題:
現(xiàn)有的程序代碼模擬產(chǎn)生了16個日志對象,并且需要運行16秒才能打印完這些日志,請在程序中增加4個線程去調(diào)用parseLog()方法來分頭打印這16個日志對象,程序只需要運行4秒即可打印完這些日志對象。
public class Test { public static void main(String[] args){ System.out.println("begin:"+(System.currentTimeMillis()/1000)); /*模擬處理16行日志,下面的代碼產(chǎn)生了16個日志對象,當(dāng)前代碼需要運行16秒才能打印完這些日志。 修改程序代碼,開四個線程讓這16個對象在4秒鐘打完。 */ for(int i=0;i<16;i++){ //這行代碼不能改動 final String log = ""+(i+1); //這行代碼不能改動 { Test.parseLog(log); } } } //parseLog方法內(nèi)部的代碼不能改動 public static void parseLog(String log){ System.out.println(log+":"+(System.currentTimeMillis()/1000)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }實現(xiàn):通過阻塞隊列實現(xiàn)線程間的通信
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; //BlockingQueue public class Test { public static void main(String[] args){ //創(chuàng)建一個空間大小為16的阻塞隊列,空間大小可以任意,因為每次打印都要1秒,在此期間, //4個線程足以不斷去從隊列中取數(shù)據(jù),然后打印,即在1秒內(nèi)打印4條日志信息 final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(16); //開啟4個線程打印 for(int i=0;i<4;i++){ new Thread(new Runnable(){ @Override public void run() { while(true){ try { String log = queue.take(); //開始沒有數(shù)據(jù),阻塞,一旦有其中一個線程就去取 //數(shù)據(jù),即不再阻塞,就開始打印 parseLog(log); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } //打印秒數(shù) System.out.println("begin:"+(System.currentTimeMillis()/1000)); for(int i=0;i<16;i++){ //這行代碼不能改動 final String log = ""+(i+1);//這行代碼不能改動 { try { queue.put(log); //向隊列中存儲數(shù)據(jù) } catch (InterruptedException e) { e.printStackTrace(); } //Test.parseLog(log); } } } //parseLog方法內(nèi)部的代碼不能改動 public static void parseLog(String log){ System.out.println(log+":"+(System.currentTimeMillis()/1000)); try { Thread.sleep(1000); //模擬每條日志打印需要1秒 } catch (InterruptedException e) { e.printStackTrace(); } } }第二題:
現(xiàn)成程序中的Test類中的代碼在不斷地產(chǎn)生數(shù)據(jù),然后交給TestDo.doSome()方法去處理,就好像生產(chǎn)者在不斷地產(chǎn)生數(shù)據(jù),消費者在不斷消費數(shù)據(jù)。
請將程序改造成有10個線程來消費生成者產(chǎn)生的數(shù)據(jù),這些消費者都調(diào)用TestDo.doSome()方法去進行處理,故每個消費者都需要一秒才能處理完,程序應(yīng)保證這些消費者線程依次有序地消費數(shù)據(jù),只有上一個消費者消費完后,下一個消費者才能消費數(shù)據(jù),下一個消費者是誰都可以,但要保證這些消費者線程拿到的數(shù)據(jù)是有順序的。
public class Test { public static void main(String[] args) { System.out.println("begin:"+(System.currentTimeMillis()/1000)); for(int i=0;i<10;i++){ //這行不能改動 String input = i+""; //這行不能改動 String output = TestDo.doSome(input); System.out.println(Thread.currentThread().getName()+ ":" + output); } } } //不能改動此TestDo類 class TestDo { public static String doSome(String input){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } String output = input + ":"+ (System.currentTimeMillis() / 1000); return output; } }在實現(xiàn)之前先介紹一個阻塞隊列:SynchronousQuene,一種阻塞隊列,其中每個插入操作必須等待另一個線程的對應(yīng)移除操作 ,反之亦然。同步隊列沒有任何內(nèi)部容量,甚至連一個隊列的容量都沒有。除非另一個線程試圖移除某個元素,否則也不能(使用任何方法)插入元素;也不能迭代隊列,因為其中沒有元素可用于迭代。
應(yīng)用:它非常適合于傳遞性設(shè)計,在這種設(shè)計中,在一個線程中運行的對象要將某些信息、事件或任務(wù)傳遞給在另一個線程中運行的對象,它就必須與該對象同步。
import java.util.concurrent.SynchronousQueue; /*Semaphore與SynchronousQueue的混合使用。 由于Semaphore只有1個許可權(quán),所以誰先拿到誰執(zhí)行,然后釋放,保證依次執(zhí)行, 用鎖也行,只要保證一個線程執(zhí)行即可 SynchronousQueue是必須有其他線程取的動作,這樣一一對應(yīng) */ public class Test { public static void main(String[] args) { //定義一個許可權(quán)為1的信號燈 final Semaphore semaphore = new Semaphore(1); //產(chǎn)生的結(jié)果無序 final SynchronousQueue<String> queue = new SynchronousQueue<String>(); //產(chǎn)生10個線程 for(int i=0;i<10;i++){ new Thread(new Runnable(){ @Override public void run() { try { semaphore.acquire(); //獲取許可 String input = queue.take(); //獲取并移除此隊列的頭 String output = TestDo.doSome(input); System.out.println(Thread.currentThread().getName()+ ":" + output); semaphore.release(); //釋放許可 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start(); } System.out.println("begin:"+(System.currentTimeMillis()/1000)); for(int i=0;i<10;i++){ //這行不能改動 String input = i+""; //這行不能改動 try { queue.put(input); //將指定元素添加到此隊列 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } //不能改動此TestDo類 class TestDo { public static String doSome(String input){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } String output = input + ":"+ (System.currentTimeMillis() / 1000); return output; } }第三題
現(xiàn)有程序同時啟動了4個線程去調(diào)用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法內(nèi)的代碼是先暫停1秒,然后再輸出以秒為單位的當(dāng)前時間值,所以,會打印出4個相同的時間值,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199615
請修改代碼,如果有幾個線程調(diào)用TestDo.doSome(key, value)方法時,傳遞進去的key相等(equals比較為true),則這幾個線程應(yīng)互斥排隊輸出結(jié)果,即當(dāng)有兩個線程的key都是”1”時,它們中的一個要比另外其他線程晚1秒輸出結(jié)果,如下所示:
4:4:1258199615
1:1:1258199615
3:3:1258199615
1:2:1258199616
總之,當(dāng)每個線程中指定的key相等時,這些相等key的線程應(yīng)每隔一秒依次輸出時間值(要用互斥),如果key不同,則并行執(zhí)行(相互之間不互斥)。
//不能改動此Test類 public class Test extends Thread{ private TestDo testDo; private String key; private String value; public Test(String key,String key2,String value){ this.testDo = TestDo.getInstance(); /*常量"1"和"1"是同一個對象,下面這行代碼就是要用"1"+""的方式產(chǎn)生新的對象, 以實現(xiàn)內(nèi)容沒有改變,仍然相等(都還為"1"),但對象卻不再是同一個的效果*/ this.key = key+key2; this.value = value; } public static void main(String[] args) throws InterruptedException{ Test a = new Test("1","","1"); Test b = new Test("1","","2"); Test c = new Test("3","","3"); Test d = new Test("4","","4"); System.out.println("begin:"+(System.currentTimeMillis()/1000)); a.start(); b.start(); c.start(); d.start(); } public void run(){ testDo.doSome(key, value); } } class TestDo { private TestDo() {} private static TestDo _instance = new TestDo(); public static TestDo getInstance() { return _instance; } public void doSome(Object key, String value) { // 以大括號內(nèi)的是需要局部同步的代碼,不能改動! { try { Thread.sleep(1000); System.out.println(key+":"+value + ":" + (System.currentTimeMillis() / 1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } }對于源代碼中關(guān)于實現(xiàn)值相同而對象不同的效果進行解釋:
對于:
a = “1”+”“;
b = “1”+””
編譯器自動優(yōu)化,所以a和b是同一個對象
而對于:key = key+key2; 由于是變量,編譯器無法識別,這時a和b把“1”和“”賦值給key和key2時會得到兩個不同的對象
思想:將集合中的對象作為同步代碼塊的鎖,即this鎖,每次將對象存入集合中的時候,就判斷是否原集合中已經(jīng)存在一個與將要存入集合的對象值相同的對象,即用equals比較,如果有,那么就獲取原來的這個對象,把這個對象作為將要存入對象的鎖,這樣它們持有的就是同一把鎖,即可實現(xiàn)互斥,這樣就可以實現(xiàn)值相同的對象在不同的時刻打印的效果
代碼中出現(xiàn)的問題:在遍歷ArrayList集合查找與要存入值相同元素的時候,進行了添加的動作,所以會出現(xiàn)并發(fā)修改異常,因此使用并發(fā)的CopyOnWriteArrayList
import java.util.ArrayList; import java.util.Iterator; import java.util.concurrent.CopyOnWriteArrayList; //不能改動此Test類 public class Test extends Thread{ private TestDo testDo; private String key; private String value; public Test(String key,String key2,String value){ this.testDo = TestDo.getInstance(); /*常量"1"和"1"是同一個對象,下面這行代碼就是要用"1"+""的方式產(chǎn)生新的對象, 以實現(xiàn)內(nèi)容沒有改變,仍然相等(都還為"1"),但對象卻不再是同一個的效果*/ this.key = key+key2; //這里是變量,所以不會優(yōu)化 /* a = "1"+""; b = "1"+"" 編譯器自動優(yōu)化,所以a和b是同一個對象 */ this.value = value; } public static void main(String[] args) throws InterruptedException{ Test a = new Test("1","","1"); Test b = new Test("1","","2"); Test c = new Test("3","","3"); Test d = new Test("4","","4"); System.out.println("begin:"+(System.currentTimeMillis()/1000)); a.start(); b.start(); c.start(); d.start(); } public void run(){ testDo.doSome(key, value); } } class TestDo { private TestDo() {} private static TestDo _instance = new TestDo(); public static TestDo getInstance() { return _instance; } //private ArrayList keys = new ArrayList(); //迭代的時候不能修改數(shù)據(jù),所以使用同步的ArrayList private CopyOnWriteArrayList keys = new CopyOnWriteArrayList(); public void doSome(Object key, String value) { Object o = key; if(!keys.contains(o)){ //比較是否已經(jīng)存入了一個相同值的對象 keys.add(o); }else{ //迭代,找出原集合里和傳進來的值相同的對象 for(Iterator iter=keys.iterator();iter.hasNext();){ try { Thread.sleep(20); //迭代的時候休息一會,在ArrayList下演示并發(fā)修改異常 } catch (InterruptedException e) { e.printStackTrace(); } Object oo = iter.next(); if(oo.equals(o)){ //如果兩個對象的值相同 o = oo; //就讓原集合中的那個相等值的對象作為鎖對象,由于原對象之前做的就是鎖 //這樣兩個鎖就相同了,就可以實現(xiàn)互斥 break; } } } synchronized(o) // 以大括號內(nèi)的是需要局部同步的代碼,不能改動! { try { Thread.sleep(1000); System.out.println(key+":"+value + ":" + (System.currentTimeMillis() / 1000)); } catch (InterruptedException e) { e.printStackTrace(); } } } }總結(jié)
以上是生活随笔為你收集整理的Java高并发编程:使用JDK5中同步技术的3个面试题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图片加载小框架
- 下一篇: 自定义控件:旋转菜单