https://me.csdn.net/huo_chai_gun 張孝祥
https://blog.csdn.net/yangyang3_/article/details/80475997? 張孝祥
現(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();??
????????????}??
????????}??
????}??
}?
------------------------------------------------