15. java5的CyclicBarrier同步工具
例如:組織人員(線程)郊遊,約定一個時間地點(路障),人員陸續到達地點,等所有人員全部到達,開始到公園各玩各的,再到約定時間去食堂吃飯,等所有人到齊開飯……
java.util.concurrent.CyclicBarrier
一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因爲該 barrier 在釋放等待線程後可以重用,所以稱它爲循環的 barrier。
CyclicBarrier 支持一個可選的 Runnable 命令,在一組線程中的最後一個線程到達之後(但在釋放所有線程之前),該命令只在每個屏障點運行一次。若在繼續所有參與線程之前更新共享狀態,此屏障操作很有用。
構造方法摘要 |
|
CyclicBarrier(int parties) 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)處於等待狀態時啓動,但它不會在啓動 barrier 時執行預定義的操作。 |
|
CyclicBarrier(int parties, Runnable barrierAction) 創建一個新的 CyclicBarrier,它將在給定數量的參與者(線程)處於等待狀態時啓動,並在啓動 barrier 時執行給定的屏障操作,該操作由最後一個進入 barrier 的線程執行。 |
|
方法摘要 |
|
int |
|
int |
await(long timeout, TimeUnit unit) 在所有參與者都已經在此屏障上調用 await 方法之前將一直等待,或者超出了指定的等待時間。 |
int |
getNumberWaiting() 返回當前在屏障處等待的參與者數目。 |
int |
getParties() 返回要求啓動此 barrier 的參與者數目。 |
boolean |
isBroken() 查詢此屏障是否處於損壞狀態。 |
void |
reset() 將屏障重置爲其初始狀態。 |
例:
ExecutorService service =Executors.newCachedThreadPool();
final CyclicBarrier cb = newCyclicBarrier(3); 約定3個人
for (int i=0; i<3; i++)產生3個人
{ 每個人的任務
Runnablerunnable = newRunnable()
{
publicvoid run()
{ 開始出發到目的地
Thread.sleep((long)Math.random()*1000);
SOP(ThreadName即將到達集合點1,
當前已有cb.getNumberWaiting()+1個
+ (cb.getNumberWaiting()==2?"都到齊了,繼續走啊":"正在等候"))
cb.await();到了其他人沒來就等
人到齊了再繼續進行
Thread.sleep((long)Math.random()*1000);
SOP(ThreadName即將到達集合點2)
cb.await();到了其他人沒來就等
}
}
service.execute(runnable);
}
16. java5的CountDownLatch同步工具
好像倒計時計數器,調用CountDownLatch對象的countDown方法就將計數器減1,當到達0時,所有等待者就開始執行。
舉例:多個運動員等待裁判命令: 裁判等所有運動員到齊後發佈結果
代碼示例:
ExecutorService service =Executors.newCachedThreadPool();
裁判發佈命令的計數器,計數器爲0,運動員就跑
final CountDownLatch cdOrder = newCountDownLatch(1);
運動員跑到終點的計數器,爲0裁判宣佈結果
final CountDownLatch cdAnswer = newCountDownLatch(3);
產生3個運動員
for (int i=0; i<3; i++)
{ 運動員的任務
Runnablerunnable = new Runnable(){
public void run()
{
SOP(ThreadName準備接受命令)
等待發布命令
cdOrder.await(); 計數器爲0繼續向下執行
SOP(ThreadName已接受命令) order計數器爲0了
Thread.sleep(Random);開始跑步
cdAnswer.countDown();跑到終點了,計數器減1
}
};
service.execute(runnable);運動員開始任務
}
Thread.sleep(1000)裁判休息一會 再發布命令
SOP(即將發佈命令)
cdOrder.countDown();命令計數器置爲0,發佈命令
SOP(命令已經發布,等待結果)
cdAnswer.await(); 等待所有運動員,計數器爲0 所有運動員到位
SOP(宣佈結果)
java.util.concurrent.CountDownLatch
一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。用給定的計數 初始化 CountDownLatch。由於調用了 countDown()方法,所以在當前計數到達零之前,await方法會一直受阻塞。之後,會釋放所有等待的線程,await的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。
CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過調用 countDown()的線程打開入口前,所有調用 await的線程都一直在入口處等待。用 N 初始化的CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。
構造方法摘要 |
|
CountDownLatch(int count) 構造一個用給定計數初始化的 CountDownLatch。 |
|
方法摘要 |
|
void |
|
boolean |
await(long timeout, TimeUnit unit) 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。 |
void |
countDown() 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。 |
long |
getCount() 返回當前計數。 |
toString() 返回標識此鎖存器及其狀態的字符串。 |
17. java5的Exchanger同步工具
用於實現兩個人之間的數據交換,每個人在完成一定的事務後想與對方交換數據,第一個先拿出數據的人會一直等待第二個人,直到第二個人拿着數據到來時,才能彼此交換數據。
舉例:毒品交易 雙方並不是同時到達,有先有後,只有都到達了,瞬間交換數據,各自飛
代碼演示:
ExecutorService service =Executors.newCachedThreadPool();
final Exchanger exchanger = newExchanger();
毒販:
service.execute(new Runnable()
{ 毒販做的事
public void run()
{
String(毒品) data1 = 毒品
SOP(毒販正在將data1換出去)
Thread.sleep(Random)換的過程
毒販到位了,拿着毒品等待毒人接頭,接頭後就能換到錢了
String data2 = (String)exchanger.exchange(data1);
SOP(毒販換到了錢:data2)
}
});
毒人:
service.execute(new Runnable()
{ 吸毒人做的事
public void run()
{
String(錢) data1 = 錢
SOP(毒人正在將data1換出去)
Thread.sleep(Random)換的過程
吸毒人到位了,拿着錢等待毒販接頭,接頭後就能換到毒品了
String data2 =(String)exchanger.exchange(data1);
SOP(毒人換到了毒品:data2)
}
});
java.util.concurrent.Exchanger<V> V -可以交換的對象類型
可以在對中對元素進行配對和交換的線程的同步點。每個線程將條目上的某個方法呈現給exchange方法,與夥伴線程進行匹配,並且在返回時接收其夥伴的對象。Exchanger 可能被視爲 SynchronousQueue 的雙向形式。Exchanger 可能在應用程序(比如遺傳算法和管道設計)中很有用。
用法示例:以下是重點介紹的一個類,該類使用Exchanger 在線程間交換緩衝區,因此,在需要時,填充緩衝區的線程獲取一個新騰空的緩衝區,並將填滿的緩衝區傳遞給騰空緩衝區的線程。
class FillAndEmpty {
Exchanger<DataBuffer> exchanger = newExchanger<DataBuffer>();
DataBuffer initialEmptyBuffer = ... a made-up type
DataBuffer initialFullBuffer = ...
classFillingLoop implements Runnable {
public void run() {
DataBuffer currentBuffer = initialEmptyBuffer;
try {
while (currentBuffer != null) {
addToBuffer(currentBuffer);
if (currentBuffer.isFull())
currentBuffer =exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ... }
}
}
class EmptyingLoop implements Runnable {
public void run() {
DataBuffer currentBuffer = initialFullBuffer;
try {
while (currentBuffer != null) {
takeFromBuffer(currentBuffer);
if (currentBuffer.isEmpty())
currentBuffer = exchanger.exchange(currentBuffer);
}
} catch (InterruptedException ex) { ... handle ...}
}
}
void start() {
new Thread(new FillingLoop()).start();
new Thread(new EmptyingLoop()).start();
}
}
構造方法摘要 |
|
Exchanger() 創建一個新的 Exchanger。 |
|
方法摘要 |
|
exchange(V x) 等待另一個線程到達此交換點(除非當前線程被中斷),然後將給定的對象傳送給該線程,並接收該線程的對象。 |
|
exchange(V x, long timeout, TimeUnit unit) 等待另一個線程到達此交換點(除非當前線程被中斷,或者超出了指定的等待時間),然後將給定的對象傳送給該線程,同時接收該線程的對象。 |
18. java5阻塞隊列的應用
隊列包含固定長度的隊列和不固定長度的隊列,先進先出
固定長度的隊列往裏放數據,如果放滿了還要放,阻塞式隊列就會等待,直到有數據取出,空出位置後才繼續放;非阻塞式隊列不能等待就只能報錯了。
講Condition時提到了阻塞隊列的原理,Java中已經實現了阻塞隊列ArrayBlockingQueue
BlockingQueue<E> public interfaceBlockingQueue<E>extends Queue<E>
支持兩個附加操作的Queue,這兩個操作是:獲取元素時等待隊列變爲非空,以及存儲元素時等待空間變得可用。
BlockingQueue 方法以四種形式出現,對於不能立即滿足但可能在將來某一時刻可以滿足的操作,這四種形式的處理方式不同:第一種是拋出一個異常,第二種是返回一個特殊值(null 或 false,具體取決於操作),第三種是在操作可以成功前,無限期地阻塞當前線程,第四種是在放棄前只在給定的最大時間限制內阻塞。下表中總結了這些方法:
|
拋出異常 |
特殊值 |
阻塞 |
超時 |
插入 |
||||
移除 |
||||
檢查 |
不可用 |
不可用 |
BlockingQueue 不接受 null 元素。試圖 add、put 或 offer 一個 null 元素時,某些實現會拋出 NullPointerException。null 被用作指示 poll 操作失敗的警戒值。
BlockingQueue 可以是限定容量的。它在任意給定時間都可以有一個 remainingCapacity,超出此容量,便無法無阻塞地 put 附加元素。沒有任何內部容量約束的 BlockingQueue 總是報告 Integer.MAX_VALUE 的剩餘容量。
BlockingQueue 實現主要用於生產者-使用者隊列,但它另外還支持 Collection 接口。因此,舉例來說,使用remove(x) 從隊列中移除任意一個元素是有可能的。然而,這種操作通常不會有效執行,只能有計劃地偶爾使用,比如在取消排隊信息時。
BlockingQueue 實現是線程安全的。所有排隊方法都可以使用內部鎖或其他形式的併發控制來自動達到它們的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)沒有 必要自動執行,除非在實現中特別說明。因此,舉例來說,在只添加了c 中的一些元素後,addAll(c) 有可能失敗(拋出一個異常)。
java.util.concurrent.ArrayBlockingQueue<E> E - 在此 collection 中保持的元素類型
extends AbstractQueue<E>implements BlockingQueue<E>, Serializable
一個由數組支持的有界阻塞隊列。此隊列按 FIFO(先進先出)原則對元素進行排序。隊列的頭部 是在隊列中存在時間最長的元素。隊列的尾部是在隊列中存在時間最短的元素。新元素插入到隊列的尾部,隊列獲取操作則是從隊列頭部開始獲得元素。
這是一個典型的“有界緩存區”,固定大小的數組在其中保持生產者插入的元素和使用者提取的元素。一旦創建了這樣的緩存區,就不能再增加其容量。試圖向已滿隊列中放入元素會導致操作受阻塞;試圖從空隊列中提取元素將導致類似阻塞。
此類支持對等待的生產者線程和使用者線程進行排序的可選公平策略。默認情況下,不保證是這種排序。然而,通過將公平性 (fairness) 設置爲 true 而構造的隊列允許按照FIFO 順序訪問線程。公平性通常會降低吞吐量,但也減少了可變性和避免了“不平衡性”。
此類及其迭代器實現了Collection 和Iterator 接口的所有可選方法。
此類是JavaCollections Framework 的成員。
構造方法摘要 |
|
ArrayBlockingQueue(int capacity) 創建一個帶有給定的(固定)容量和默認訪問策略的 ArrayBlockingQueue。 |
|
ArrayBlockingQueue(int capacity, boolean fair) 創建一個具有給定的(固定)容量和指定訪問策略的 ArrayBlockingQueue。 |
|
ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) 創建一個具有給定的(固定)容量和指定訪問策略的 ArrayBlockingQueue,它最初包含給定 collection 的元素,並以 collection 迭代器的遍歷順序添加元素。 |
|
方法摘要 |
|
boolean |
add(E e) 將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true,如果此隊列已滿,則拋出 IllegalStateException。 |
void |
clear() 自動移除此隊列中的所有元素。 |
boolean |
|
int |
drainTo(Collection<? super E> c) 移除此隊列中所有可用的元素,並將它們添加到給定 collection 中。 |
int |
drainTo(Collection<? super E> c, int maxElements) 最多從此隊列中移除給定數量的可用元素,並將這些元素添加到給定 collection 中。 |
iterator() 返回在此隊列中的元素上按適當順序進行迭代的迭代器。 |
|
boolean |
offer(E e) 將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true,如果此隊列已滿,則返回 false。 |
boolean |
offer(E e, long timeout, TimeUnit unit) 將指定的元素插入此隊列的尾部,如果該隊列已滿,則在到達指定的等待時間之前等待可用的空間。 |
peek() 獲取但不移除此隊列的頭;如果此隊列爲空,則返回 null。 |
|
poll() 獲取並移除此隊列的頭,如果此隊列爲空,則返回 null。 |
|
poll(long timeout, TimeUnit unit) 獲取並移除此隊列的頭部,在指定的等待時間前等待可用的元素(如果有必要)。 |
|
void |
|
int |
remainingCapacity() 返回在無阻塞的理想情況下(不存在內存或資源約束)此隊列能接受的其他元素數量。 |
boolean |
|
int |
size() 返回此隊列中元素的數量。 |
take() 獲取並移除此隊列的頭部,在元素變得可用之前一直等待(如果有必要)。 |
|
Object[] |
toArray() 返回一個按適當順序包含此隊列中所有元素的數組。 |
<T> T[] |
toArray(T[] a) 返回一個按適當順序包含此隊列中所有元素的數組;返回數組的運行時類型是指定數組的運行時類型。 |
toString() 返回此 collection 的字符串表示形式。 |
阻塞隊列的實現原理(Condition鎖中有提到await signal)
19. java5同步集合類的應用
傳統集合實現同步的問題
舉了一個例子:Map集合線程不同步導致的問題。
解決辦法:使用同步的Map集合 使用集合工具類中的方法將不同步的集合轉爲同步的Collections.synchronizedMap(newMap())這個方法返回一個同步的集合
publicstatic <K, V> Map<K, V> synchronizedMap(Map<K, V> m)
{return newSynchronizedMap<K, V>(m);}
SynchronizedMap類相當於一個代理類,通過查看源代碼發現:該類中的所有方法都是直接返回:原Map集合方法調用後的結果,只是將返回結果的代碼放在了同步代碼塊中以實現同步,構造是將同步鎖默認置爲當前對象。
HashSet與HashMap的關係與區別:
HashSet是單列的,HashMap是雙列的(鍵值對)
關係:HashSet內部使用的是HashMap中的鍵,不考慮值。
查看HashSet的源代碼發現其內部就是用HashMap實現的,只是沒有使用HashMap的V,只使用了它的K。
JDK1.5中提供了併發 Collection:提供了設計用於多線程上下文中的 Collection 實現:
ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet。當期望許多線程訪問一個給定 collection 時,ConcurrentHashMap 通常優於同步的 HashMap,ConcurrentSkipListMap 通常優於同步的 TreeMap。當期望的讀數和遍歷遠遠大於列表的更新數時,CopyOnWriteArrayList優於同步的ArrayList。
ConcurrentSkipListMap<K,V>映射可以根據鍵的自然順序進行排序,也可以根據創建映射時所提供的 Comparator 進行排序,具體取決於使用的構造方法。
ConcurrentSkipListSet<E>一個基於 ConcurrentSkipListMap 的可縮放併發 NavigableSet 實現。set 的元素可以根據它們的自然順序進行排序,也可以根據創建 set 時所提供的 Comparator 進行排序,具體取決於使用的構造方法
CopyOnWriteArrayList<E>ArrayList 的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的複製來實現的。
這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更有效。在不能或不想進行同步遍歷,但又需要從併發線程中排除衝突時,它也很有用。“
CopyOnWriteArraySet<E>對其所有操作使用內部 CopyOnWriteArrayList 的 Set。因此,它共享以下相同的基本屬性:
它最適合於具有以下特徵的應用程序:set 大小通常保持很小,只讀操作遠多於可變操作,需要在遍歷期間防止線程間的衝突。它是線程安全的。 因爲通常需要複製整個基礎數組,所以可變操作(add、set 和 remove 等等)的開銷很大。 迭代器不支持可變 remove 操作。 使用迭代器進行遍歷的速度很快,並且不會與其他線程發生衝突。在構造迭代器時,迭代器依賴於不變的數組快照。
傳統集合中存在的其它問題:對集合迭代時,不能對集合中的元素進行修改(添加、刪除……),Java5中提供的併發集合就解決了這個問題。
20. 空中網挑選實習生的面試題1
現有的程序代碼模擬產生了16個日誌對象,並且需要運行16秒才能打印完這些日誌,請在程序中增加4個線程去調用parseLog()方法來分頭打印這16個日誌對象,程序只需要運行4秒即可打印完這些日誌對象。原始代碼如下:
public class Test1
{
publicstatic void main(String[] args)
{
SOP(begin:+sys.currentTimeMillis()/1000);
//模擬處理16行日誌,下面的代碼產生16個日誌對象,需運行16秒才能打印完
//修改程序代碼,開4個線程讓這16個日誌在4秒鐘打完
for (iint i=0; i<16; i++) //這行代碼不能改動
{
final String log = “”+(i+1); //這行代碼不能改動
{
Test1.parseLog(log);
}
}
}
//parseLog方法內部代碼不能改動
public staticvoid parseLog(String log)
{
SOP(log+”:”+(sys.currentTimeMillis()/1000));
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
剛看到題目還想着很簡單;直接在Test.parseLog(log)的地方new4個線程,都執行打印任務即可,仔細一看不行,在這裏new4個線程的話就是16*4個線程了,所以要將線程在for循環外邊創建出來,for內部將產生的日誌對象裝在一個共享變量裏,在線程內部從共享變量中取數據打印。要考慮線程同步互斥問題,這個共享變量要具備同步功能,可以使用ArrayBlockingQueue這種阻塞式隊列來存儲日誌對象。也可以使用普通集合,但拿數據要考慮同步問題,可能會浪費時間。
在for循環外部創建線程,定義共享變量
finalBlockingQueue<String> queue = new ArrayBlockingQueue<String>(16);
for(int i=0; i<4; i++) 創建4個線程
{
new Thread(new Runnable()
{
public void run()
{
while (true)
{ 先從集合中取到日誌對象,再打印
String log =queue.take(); 要處理異常
parseLog(log);
}
}
}).start();
}
在16次for循環內部將產生的日誌對象裝入共享變量中
queue.put(log); 要處理異常
這道題只要用到同步集合來共享數據就可以了 List集合的Vector也可以
21. 空中網挑選實習生的面試題2
現成程序中的Test類中的代碼在不斷地產生數據,然後交給TestDo.doSome()方法去處理。就好像生產者不斷地產生數據,消費者不斷地消費數據。請將程序改造成有10個線程來消費生產者產生的數據,這些消費者都調用TestDo.doSome()方法去進行處理,故每個消費者都需要一秒才能處理完,程序應保證這些消費者線程依次有序地消費數據,只有上一個消費者消費完後,下一個消費者才能消費數據,下一個消費者是誰都可以,但要保證這些消費者線程拿到的數據是有序的。原始代碼如下:
public class Test2
{
publicstatic void main(String[] args)
{
SOP(begin+sys.currentTimeMillis()/1000);
for (int i=0; i<10; i++) //這行不能改動
{
String input = i+””; //這行不能改動
String output =TeatDo.doSome(input);
SOP(ThreadName+output);
}
}
}
//不能改動此TestDo類
class TestDo
{
publicstatic String doSome(String input)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
String output = input + “:” + (Sys.currentTimeMillis());
return output;
}
}
看這題和上一題差不多,也是數據共享問題了,弄一個同步集合存起來。
用同樣的方法一樣解決 new ArrayBlockingQueue()
張老師又講了另一個同步隊列:SynchronousQueue一種阻塞隊列,其中每個插入操作必須等待另一個線程的對應移除操作,反之亦然。同步隊列沒有任何內部容量,甚至連一個隊列的容量都沒有。不能在同步隊列上進行 peek,因爲僅在試圖要移除元素時,該元素才存在;除非另一個線程試圖移除某個元素,否則也不能(使用任何方法)插入元素;也不能迭代隊列,因爲其中沒有元素可用於迭代。
SynchronousQueue是一個特殊隊列,即便是空的也不能插入元素,也讀不到元素,要往裏邊插入的時候如果沒有讀取操作,插入操作就會阻塞,等到有讀取操作出現時,插入操作檢測到了讀取操作,才能把數據插入進去,而讀取操作正好可以拿到剛剛插入進去的數據。就好比毒品買賣,我拿着毒品給誰呢,只有買毒品的人來了,才能立馬給他,他也拿到了。與Exchanger類似,不過Exchanger是單對單的交換,SynchronousQueue可以多個搶數據,我拿着毒品等人來買,一下來了3個人買,誰搶到了就是誰的;或者我拿3包毒品,3個人同時每人一份。
這道題用synchronousQueue的話會一下子將10個數據全打印出來,因爲10次循環一次放一個並沒有人來取,所以沒有放進去,後來一下10個線程來取數據,就一下放進去拿走了。我測試的時候沒有這種情況,都是間隔一秒一秒的。測試後發現,將doSome處理後的結果存進去,就會有間隔,而直接存進去,取數據後再處理的話就是一下一片了。分析後知道:put時沒有take,10個數據都在等待存入,如果存入的數據是doSome(input)的話,開始取數據時纔開始執行doSome所以就會有間隔了。直接存數據,取出後在doSome就是一下拿到10個數據了。
要解決這個問題,可以使用廁所搶坑的方式解決,使用Semaphore來獲取許可,每取一次數據釋放一次即可。
final Semaphorex = new Semaphore(1); 一次一個
finalSynchronousQueue queue = new SynchronousQueue();
每次取數據前都要先獲取許可
x.acquire();
取完後釋放許可
x.release();
這種方式與使用Lock方式一樣
22. 空中網挑選實習生的面試題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不同,則並行執行(相互之間不互斥)。原始代碼如下:
//不能改動此Test類
public class Test3 extends Thread
{
privateTestDo testDo;
privateString key;
privateString value;
publicTest3(String key, String key2, String value)
{
this.testDo = TestDo.getInstance();
/*常量“1”和 “1”是同一個對象,下面這行代碼就是要用“1”+“”的
方式產生新的對象,以實現內容沒有改變,仍然相等(都還爲“1”),
但對象卻不再是同一個的效果
*/
this.key = key + key2;
this.value = value;
}
publicstatic void main(String[] args) throws InterruptedException
{
Test3 a = new Test3(“1”, “”, “1”);
Test3 b = new Test3(“1”, “”, “2”);
Test3 c = new Test3(“3”, “”, “3”);
Test3 d = new Test3(“4”, “”, “4”);
SOP(begin+:+sys.currentTimeMillis()/1000);
a.start();
b.start();
c.start();
d.start();
}
publicvoid run()
{
testDo.doSome(key, value);
}
}
class TestDo
{
privateTestDo(){}
privatestatic TestDo _instance = new TestDo();
public staticTestDo getInstance()
{
return _instance;
}
public voiddoSome(Object key, String value)
{
//此大括號內的代碼是需要局部同步的代碼,不能改動!
{
try
{
Thread.sleep(1000);
SOP(key+”:”+value+”:”+sys.currentTimeMillis()/1000);
}
catch (InterruptedExceptione)
{
e.printStackTrace();
}
}
}
}
看完這道題第一個想法是在標記位置加上同步代碼塊,但是鎖不好弄了,因爲每次都新建了一個key對象來接受實際key,沒法獲取到實際key對象。
想到了Lock對象,所以建一個Lock對象,判斷key的值是否和指定值“1“相同,如果相同就鎖上,不同不管,finally裏在解鎖前進行判斷,避免沒上鎖還要解鎖發生問題。
Lock lock = newReentrantLock();
publicvoid doSome(Object key, String value)
{
if(key.equals("1"))
lock.lock();
//System.out.println("OKOKOOK");
//synchronized("1")
try
//此大括號內的代碼是需要局部同步的代碼,不能改動!
{
try
{
Thread.sleep(1000);
System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}finally
{
if(key.equals("1"))
lock.unlock();
}
}
但上面的方式寫死了,如果換2呢 還要改代碼,現在要不管是什麼只要相同都互斥,將這些加進來的key放到一個集合ArrayList中,每次傳進來一個key,先把傳進來的key作爲鎖對象,再判斷這個對象有沒有存在鎖集合中,如果沒有,就把它存進去,同時就用這個key做鎖;如果已經存在了,就是說這個key已經做過鎖對象了,就需要將以前做鎖的那個對象拿出來,再讓它來當鎖,與傳進來的key對象一樣,這樣就產生互斥效果了。
需要注意:拿原來的鎖對象時要迭代鎖集合,因爲有多個線程在運行,所以迭代時有可能出現其他線程的key沒有做過鎖,需要將它加到鎖集合中,可是這時候這個線程還在迭代過程中,迭代時不能操作集合中的數據,就會發生異常。要解決這個問題,就需要用到同步集合了。CopyOnWriteArrayList
使用ArrayList時就經常出異常,換CopyOnWriteArrayList後沒有異常了
//將所有傳過來的key都存起來
//privateList<Object> keys = new ArrayList<Object>();
privateCopyOnWriteArrayList<Object> keys = newCopyOnWriteArrayList<Object>();
public voiddoSome(Object key, String value)
{
//先用這個key當鎖,用過一次就存到集合中
Objecto = key;
//判斷這個鎖用過沒有
if (!keys.contains(o))
{
//如果這個key沒有用過,就用它當鎖,把它存到鎖集合中
keys.add(o);
}
else //鎖集合中已經有了這個key
{
//這個key已經當過鎖了,就把它拿出來,還用它做鎖,就和現在的key互斥了
//因爲不知道原來key的位置,所有需要進行遍歷
for(Iterator<Object> it = keys.iterator();it.hasNext();)
{
//當前遍歷到的對象
Objectoo = it.next();
//如果找到了,就讓它做鎖
if(oo.equals(o))
{
o= oo;
break; //找到了,不用再循環了
}
}
//o = keys.get(keys.indexOf(o)); //key和o不是同一個對象,拿不到
}
synchronized(o)
//此大括號內的代碼是需要局部同步的代碼,不能改動!
{
try
{
Thread.sleep(1000);
System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
a = “1”+””;
b = “1”+””;
a和b是同一個對象,常量相加equals爲真 ==爲假
Object o1 = new String("1");
Object o2 = new String("1");
System.out.println(o1==o2); //false
System.out.println(o1.equals(o2)); //true
System.out.println(o1); //1
System.out.println(o2); //1
Object o3 = "1"+"";
Object o4 = "1"+"";
System.out.println(o3==o4); //true
System.out.println(o3.equals(o4)); //true
Object o5 = "2"+"";
Object o6 = get("2","");
System.out.println(o5==o6); //false
System.out.println(o5.equals(o6)); //true
System.out.println(o5+"__"+o6); //2__2
public static Object get(String a,String b)
{
return a+b;
}