多線程

https://me.csdn.net/huo_chai_gun 張孝祥

https://blog.csdn.net/yangyang3_/article/details/80475997? 張孝祥

空中網(wǎng)挑選實習(xí)生的面試題

第一題:

現(xiàn)有的程序代碼模擬產(chǎn)生了16個日志對象,并且需要運(yùn)行16秒才能打印完這些日志,請在程序中增加4個線程去調(diào)用parseLog()方法來分頭打印這16個日志對象,程序只需要運(yùn)行4秒即可打印完這些日志對象。原始代碼如下:

[java]view plaincopy

package?cn.itcast.heima;??

public?class?Test?{??

public?static?void?main(String[]?args){??

System.out.println("begin:"+(System.currentTimeMillis()/1000));??

/*模擬處理16行日志,下面的代碼產(chǎn)生了16個日志對象,當(dāng)前代碼需要運(yùn)行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();??

????????}?????????

????}??

}??

分析:線程如何得到產(chǎn)生的數(shù)據(jù)?將數(shù)據(jù)存放在隊列中,然后從隊列中取

步驟:創(chuàng)建四個線程,將產(chǎn)生的數(shù)據(jù)存入集合,線程在集合中取數(shù)據(jù)

[java]view plaincopy

package?cn.itcast.heima;??


import?java.util.concurrent.ArrayBlockingQueue;??

import?java.util.concurrent.BlockingQueue;??


public?class?Test?{??

public?static?void?main(String[]?args){??

final?BlockingQueue<String>?queue?=?new?ArrayBlockingQueue<String>(1);//1個數(shù)據(jù)也可以,??

//final?BlockingQueue<String>?queue?=?new?ArrayBlockingQueue<String>(16);//可以裝16個數(shù)據(jù)??

//創(chuàng)建四個線程??

for(int?i=0;i<4;i++){??

new?Thread(new?Runnable(){??

@Override??

public?void?run()?{??

while(true){??

try?{??

String?log?=?queue.take();//從阻塞隊列中拿數(shù)據(jù)??

????????????????????????????parseLog(log);??

}catch?(InterruptedException?e)?{??

????????????????????????????e.printStackTrace();??

????????????????????????}??

????????????????????}??

????????????????}??

????????????}).start();??

????????}??


System.out.println("begin:"+(System.currentTimeMillis()/1000));??

/*模擬處理16行日志,下面的代碼產(chǎn)生了16個日志對象,當(dāng)前代碼需要運(yùn)行16秒才能打印完這些日志。

????????修改程序代碼,開四個線程讓這16個對象在4秒鐘打完。

????????*/??

for(int?i=0;i<16;i++){??//這行代碼不能改動??

final?String?log?=?""+(i+1);//這行代碼不能改動??

????????????{??

try?{??

queue.put(log);//將數(shù)據(jù)放入隊列中??

}catch?(InterruptedException?e)?{??

????????????????????????e.printStackTrace();??

????????????????????}??

????????????}??

????????}??

????}??


//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)成程序中的Test類中的代碼在不斷地產(chǎn)生數(shù)據(jù),然后交給TestDo.doSome()方法去處理,就好像生產(chǎn)者在不斷地產(chǎn)生數(shù)據(jù),消費(fèi)者在不斷消費(fèi)數(shù)據(jù)。請將程序改造成有10個線程來消費(fèi)生成者產(chǎn)生的數(shù)據(jù),這些消費(fèi)者都調(diào)用TestDo.doSome()方法去進(jìn)行處理,故每個消費(fèi)者都需要一秒才能處理完,程序應(yīng)保證這些消費(fèi)者線程依次有序地消費(fèi)數(shù)據(jù),只有上一個消費(fèi)者消費(fèi)完后,下一個消費(fèi)者才能消費(fèi)數(shù)據(jù),下一個消費(fèi)者是誰都可以,但要保證這些消費(fèi)者線程拿到的數(shù)據(jù)是有順序的。原始代碼如下:

[java]view plaincopy

package?queue;??

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;??

????}??

}??

分析:

線程如何得到產(chǎn)生的數(shù)據(jù)?通過隊列

這里使用同步隊列SynchronousQueue,它是一個阻塞隊列,每一個插入操作對應(yīng)一個取出操作,只有線程一旦來取了,才會插入數(shù)據(jù)。所以它不指定大小

使用lock或者semaphore實現(xiàn)線程互斥,一個一個執(zhí)行

[java]view plaincopy

package?queue;??


import?java.util.concurrent.Semaphore;??

import?java.util.concurrent.SynchronousQueue;??


public?class?Test?{??

public?static?void?main(String[]?args)?{??

final?Semaphore?semaphore?=?new?Semaphore(1);??

final?SynchronousQueue<String>?queue?=?new?SynchronousQueue<String>();??

for(int?i=0;i<10;i++){??

new?Thread(new?Runnable(){??

public?void?run()?{???

try?{??

semaphore.acquire();//當(dāng)前線程獲取一個信號燈,保證線程一個一個取,而不是10個線程同時取數(shù)據(jù)(這里用鎖也可以)??

????????????????????????String?input?=?queue.take();??

????????????????????????String?output?=?TestDo.doSome(input);??

System.out.println(Thread.currentThread().getName()+":"?+?output);??

????????????????????????semaphore.release();??

}catch?(InterruptedException?e)?{??

????????????????????????e.printStackTrace();??

????????????????????}?????

????????????????}??

????????????}).start();??

????????}??


System.out.println("begin:"+(System.currentTimeMillis()/1000));??

for(int?i=0;i<10;i++){??//這行不能改動??

String?input?=?i+"";??//這行不能改動??

try?{??

queue.put(input);//將數(shù)據(jù)放在隊列中??

}catch?(InterruptedException?e)?{??

????????????????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)方法時,傳遞進(jìn)去的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í)行(相互之間不互斥)。原始代碼如下:

[java]view plaincopy

package?syn;??


//不能改動此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();??

????????????}??

????????}??

????}??

}??

分析:

需要同步,同步的鎖就是傳入的key,但是鎖字符串內(nèi)容相同,但不一定是同一對象,所以需要進(jìn)行判斷。

需要用到同步集合。

[java]view plaincopy

package?syn;??


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;???

/*??????a?=?"1"+"";

????????b?=?"1"+"";//這里a和b都是常量相加,編譯器會自動優(yōu)化為同一對象

*/??

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();//這里不能使用ArrayList集合。因為會發(fā)生ConcurrentModificationException的問題,此例中會出現(xiàn)一個線程迭代集合的同時另一個線程在往集合中添加元素。比如此例中a線程進(jìn)入往集合中添加了一個元素,b線程進(jìn)入因為元素相同就進(jìn)行迭代操作,這時c線程也是同時執(zhí)行的,進(jìn)入后會執(zhí)行添加操作。出現(xiàn)該錯誤必須是這種情況出現(xiàn),否則程序運(yùn)行不會報錯!!??

private?CopyOnWriteArrayList?keys?=?new?CopyOnWriteArrayList();??

public?void?doSome(Object?key,?String?value)?{??

????????Object?o?=?key;??

if(!keys.contains(o)){??

keys.add(o);//如果集合中沒有該元素(實際上判斷有無元素與之相等),就存入該對象??

}else{??

//如果有該元素,就直接遍歷集合,找到該元素,并用該元素作為同步的鎖??

for(Iterator?iter=keys.iterator();iter.hasNext();){??

try?{??

Thread.sleep(20);//減慢迭代的速度(等著別的線程進(jìn)行加入元素操作),增加出現(xiàn)并發(fā)修改問題的概率??

}catch?(InterruptedException?e)?{??

????????????????????e.printStackTrace();??

????????????????}??

????????????????Object?oo?=?iter.next();??

if(oo.equals(o)){??

????????????????????o?=?oo;??

break;??

????????????????}??

????????????}??

????????}??

synchronized(o)??

//?以大括號內(nèi)的是需要局部同步的代碼,不能改動!??

????????{??

try?{??

Thread.sleep(1000);??

System.out.println(key+":"+value?+?":"??

+?(System.currentTimeMillis()/1000));//這里將毫秒數(shù)除以1000,所以看到的時間會一樣??

}catch?(InterruptedException?e)?{??

????????????????e.printStackTrace();??

????????????}??

????????}??

????}??

}?

------------------------------------------------

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容