摘要: 推薦兩款純Java的SVN Web Client軟件。其安裝使用均比ViewVC要好! 閱讀全文
摘要: 前幾天好奇,也剛好得了點空閑,然后就想看看ViewVC對Subversion的支持程度,于是就想裝個玩玩。好死不死的,在我的VMWare Workstation上剛好有個Windows Server 2003,心想,就它吧,可就這么一偷懶,折騰了我好幾天,最終還是只能算將就著把它給裝上了。 閱讀全文
摘要: SVN會取代CVS嗎?這個雖然不是我們這種小程序員能決定的大事,但學學總無妨吧,這里有一些我搜集的資料。 閱讀全文
摘要: 你是不是為了高的測試覆蓋度而在為每個函數添加多個測試方法,甚至連getX()和setX()都不放過呢?或者,你一看到覆蓋度達到100%的代碼,景仰之心就開始“有如滔滔江水綿綿不絕,又有如黃河泛濫,一發不可收拾”了呢?那么,你應該讀讀Andrew Glover在最近的developerWorks上發表的這篇文章。 閱讀全文
摘要: 《Java Threads》的第5章“Minimal Synchronization Techniques”,是這本書中到現在我認為最差的一章了,當然主要是我不喜歡JDK 1.5新推出的Atomic Class,而這一章卻花了不少篇章來介紹,且牽強地改造打字程序,又語焉不詳地指出這種改造的困難之處和可能帶來的副作用,但卻又不能從代碼的實際運行中看到這種副作用,很有誤導初學者的嫌疑。不過,我想,沒有哪個初學者會冒風險為了用Atomic Class而將原本簡單明了的算法改造得如此晦澀難懂,并且還有潛在的出錯風險。所以,對于Atomic Class,我建議跳過不讀,絕對沒有什么損失。不過對于其中“5.1.3 Double-Checked Locking”和“5.3 Thread Local Variables”這兩節倒要著重讀一讀,尤其是Thread Local,應該說是Java中一個比較重要的多線程工具。 閱讀全文
摘要: 快來看“洋本山”怎樣忽悠一個只想買一把錘子的人最后買了一個工具工廠的建造工廠的通用建造工廠。很別扭是吧,但如果你是個開發Web應用的Java程序員,你也許已經或者正在被忽悠。 閱讀全文
看到《Java Threads》第5章,介紹了JDK 1.5新加的一些所謂原子類(Atomic Classes),總感覺有點為原子而原子,實際操作中,又有多少人會為了少許的性能提升而刻意去用這些別扭的操作而放棄直觀的synchronize關鍵字或者Lock類呢?不過,這里不是想討論這個,而是當其用Atomic Classes來改造它的打字程序后,解釋用原子類只是保證類似遞增、遞減、賦值等操作的原子性,而不能保證其所在的方法一定是線程安全的,然后說,有可能按鍵事件的處理可能需要等待resetScore()處理完才能執行,而這會導致錯誤的評分(被當成多敲了鍵)。由于前幾章的內容相對比較簡單易懂,所以也沒有很仔細的運行那些例子。這里為了驗證一下,就運行了一下第4章的例子,然后發現,基本上第一次的評分總是錯的。這就引起了我的注意,因為,一般情況下,如果是race condition導致的錯誤是很難重現的,這么明顯的錯誤很可能是程序邏輯上的錯誤。仔細看了一下代碼,發現在start按鈕的事件處理方法里,有下面這樣一段代碼:
然后又運行了第3章的例子,發現基本上沒有這個問題。難道第3章的代碼是正確的?打開源代碼一看,重置成績的方法還是放在最后,那這里為什么又是正確的呢?我想,大約是第3章的例子中,每次點擊start按鈕,都重新創建一個線程對象的原因吧。由于創建對象和初始化線程需要一定的時間,剛好給了主線程重置成績的機會。
不知道作者有意為之呢,還是疏忽,不過,這樣的錯誤不能算是race condition的例子。
startButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
score.resetScore();
}
});
注意重置成績的調用放在了最后,此時,隨機生成字符的線程應該被喚醒并產生了第一個字符,然后,resetScore()將需要輸入的字符又設成了-1,所以,當你第一次輸入字符時,總是被認為是多擊了一次鍵而扣1分:(。既然這樣,那停止然后再啟動也應該會發生這個錯誤啊。而事實上的確是這樣。我想,這不應該看做是race condition吧,有什么樣的同步技術能夠避免這個問題呢?除非另外弄個標志,當成績沒有被重置前,不能產生第一個字符。當然,這是不需要的,只要將score.resetScore()放到第一句就可以了。public void actionPerformed(ActionEvent evt) {
displayCanvas.setDone(false);
producer.setDone(false);
startButton.setEnabled(false);
stopButton.setEnabled(true);
feedbackCanvas.setEnabled(true);
feedbackCanvas.requestFocus();
score.resetScore();
}
});
然后又運行了第3章的例子,發現基本上沒有這個問題。難道第3章的代碼是正確的?打開源代碼一看,重置成績的方法還是放在最后,那這里為什么又是正確的呢?我想,大約是第3章的例子中,每次點擊start按鈕,都重新創建一個線程對象的原因吧。由于創建對象和初始化線程需要一定的時間,剛好給了主線程重置成績的機會。
不知道作者有意為之呢,還是疏忽,不過,這樣的錯誤不能算是race condition的例子。
第3章主要介紹了數據的同步(Data Synchronization),這一章則主要介紹線程之間的同步方法(Thread Notification),同樣包括傳統的wait-and-notify方法和JDK 1.5新推出的Condition Variable。在多線程編程中,數據同步和線程同步是兩個最基本也是最關鍵的部分。
《Java Threads》一書中通過考察打字程序中當按下start和stop按鈕后,每次都創建兩個新的線程的效率問題來引入線程同步的概念,當然不是線程同步的主要用處。不過,教科書歸教科書,實際運用則又是另一回事。所以,通過書本學習語法,通過實踐來獲得運用經驗。
2. 在Java中,就像每個對象有一個鎖之外,任何對象都可以提供等待/喚醒的機制。就像Java中的synchronized總是表示獲得某個具體對象的鎖一樣,wait和notify也總是等待某個具體的對象,并由該對象喚醒;同樣,獲得某個對象上的鎖不一定是該對象需要同步一樣,等待和喚醒的條件也不一定是與之綁定的對象。
3. Java中wait-and-notify的幾個方法:
4. wait()和sleep()的主要區別:
1) sleep()可以在任何地方調用,而wait()需要在同步方法或同步塊中調用;
2) 進入wait()函數時,JVM會自動釋放鎖,而當從wait()返回即被喚醒時,又會自動獲得鎖;而sleep()沒有這個功能,因此如果在wait()的地方用sleep()代替,則會導致相應的nofity()方法在等待時不可能被觸發,因為notify()必須在相應的同步方法或同步塊中,而此時這個鎖卻被sleep()所在的方法占用。也就是說,wait-and-notify不可能與sleep()同時使用。
4.1.1 The Wait-and-Notify Mechanism and Synchronization
1. 這一節詳細的講解了wait-and-notify機制和synchronized的關系,主要是兩點:1)wait-and-notify必須和synchronized同時使用;2)wait()會自動釋放和獲取鎖;
2. 這一節中舉了一個例子用來解釋可能存在當條件被不滿足時也有可能被喚醒的情況:
或者象《Practical Java》中一樣:
3. 調用wait()的線程T可能在以下幾種情況被喚醒:
1) 其它線程調用了notify(),而剛好線程T得到了通知;
2) 其它線程調用了notifyAll();
3) 其它線程中斷了線程T;
4) 由于JVM的原因,導致了spurious wakeup。
4.1.2 wait(), notify(), and notifyAll()
1. 正像多個線程等待同一對象上的鎖,當鎖釋放時,無法確定哪個線程會得到那個鎖一樣;當有多個線程在wait()時,當另外一個線程調用nofity()的時候,也不能確定哪個線程會被喚醒; 2. 因此在《Practical Java》的"實踐53:優先使用notifyAll()而非notify()"建議的一樣,結合實踐54,可以比較好的解決線程喚醒的問題。
4.1.3 Wait-and-Notify Mechanism with Synchronized blocks
再次強調必須在同一個對象的synchronized方法或塊內調用該對象上的wait和notify方法。
2. JDK 1.5提供Condition接口來提供與其它系統幾乎一致的condition variables機制;
3. Condition對象由Lock對象的newCondition()方法生成,從而允許一個鎖產生多個條件變量,可以根據實際情況來等待不同條件;
4. 該書的例子沒有什么特別的實際意義,但JDK 1.5文檔中提供了一個例子,能充分說明使用Condition Variables使得程序更加清晰易讀,也更有效率:
具體的說明請參考JDK 1.5的文檔。
5. 除了用lock和await-and-signal來代替synchronized和wait-and-notify外,其語義和機制基本一樣。await()在進入前也會自動釋放鎖,然后再返回前重新獲得鎖;
6. 使用Condition Variables的原因:
1) 如果使用Lock對象,則必須使用condition variables;
2) 每個Lock對象可以創建多個condition variable.
《Java Threads》一書中通過考察打字程序中當按下start和stop按鈕后,每次都創建兩個新的線程的效率問題來引入線程同步的概念,當然不是線程同步的主要用處。不過,教科書歸教科書,實際運用則又是另一回事。所以,通過書本學習語法,通過實踐來獲得運用經驗。
4.1 Wait and Notify
1. 等待/喚醒類似于Solaris或POSIX中的條件變量(conditon variables),或者Windows中的事件變量(evant variable)或者信號量(signal),用于某個/多個線程暫停等待某個條件的滿足,而該條件將由其它線程來設置的情況。2. 在Java中,就像每個對象有一個鎖之外,任何對象都可以提供等待/喚醒的機制。就像Java中的synchronized總是表示獲得某個具體對象的鎖一樣,wait和notify也總是等待某個具體的對象,并由該對象喚醒;同樣,獲得某個對象上的鎖不一定是該對象需要同步一樣,等待和喚醒的條件也不一定是與之綁定的對象。
3. Java中wait-and-notify的幾個方法:
void wait(): 使當前線程處于等待狀態,直到其它線程調用了nofity()或者notifyAll()方法為止。
void wait(long timeout): 使當前線程處于等待狀態,直到其它線程調用了nofity()或者notifyAll()方法,或者超過了指定的時間(單位為ms)為止
void wait(long timeout, int nanos):與wait(long)一樣,只是在某些JVM中可以精確到奈秒。
void notify(): 喚醒一個正在等待該對象的線程。
void notifyAll(): 喚醒所有正在等待該對象的線程。
注意:任何等待和喚醒方法都必須在與之對應的對象的同步方法或同步塊里調用。即:wait-and-notify必須和與之對應的synchronized關鍵詞一起使用的。void wait(long timeout): 使當前線程處于等待狀態,直到其它線程調用了nofity()或者notifyAll()方法,或者超過了指定的時間(單位為ms)為止
void wait(long timeout, int nanos):與wait(long)一樣,只是在某些JVM中可以精確到奈秒。
void notify(): 喚醒一個正在等待該對象的線程。
void notifyAll(): 喚醒所有正在等待該對象的線程。
4. wait()和sleep()的主要區別:
1) sleep()可以在任何地方調用,而wait()需要在同步方法或同步塊中調用;
2) 進入wait()函數時,JVM會自動釋放鎖,而當從wait()返回即被喚醒時,又會自動獲得鎖;而sleep()沒有這個功能,因此如果在wait()的地方用sleep()代替,則會導致相應的nofity()方法在等待時不可能被觸發,因為notify()必須在相應的同步方法或同步塊中,而此時這個鎖卻被sleep()所在的方法占用。也就是說,wait-and-notify不可能與sleep()同時使用。
4.1.1 The Wait-and-Notify Mechanism and Synchronization
1. 這一節詳細的講解了wait-and-notify機制和synchronized的關系,主要是兩點:1)wait-and-notify必須和synchronized同時使用;2)wait()會自動釋放和獲取鎖;
2. 這一節中舉了一個例子用來解釋可能存在當條件被不滿足時也有可能被喚醒的情況:
1) 線程T1調用一個同步方法;
2) T1檢測狀態變量,發現其不滿足條件;
3) T1調用wait(),并釋放鎖;
4) 線程T2調用另外一個同步方法,獲得鎖;
5) 線程T3調用另外一個同步方法,由于T2獲得了鎖,所以處于等待狀態;
6) T2修改狀態變量,使其滿足條件,并調用notify()方法;
7) T3獲得鎖,然后處理數據,并將狀態變量又設置為不滿足條件的狀態;
8) T3處理完畢返回;
9) T1被喚醒,但實際上此時條件并不滿足。
這個例子剛好印證了《Effective Java》中"Item 50: Never invoke wait outside a loop"和《Practical Java》中"實踐54:針對wait()和notifyAll()使用旋鎖(spin locks)"。即總是用下面這種方式來調用wait():
2) T1檢測狀態變量,發現其不滿足條件;
3) T1調用wait(),并釋放鎖;
4) 線程T2調用另外一個同步方法,獲得鎖;
5) 線程T3調用另外一個同步方法,由于T2獲得了鎖,所以處于等待狀態;
6) T2修改狀態變量,使其滿足條件,并調用notify()方法;
7) T3獲得鎖,然后處理數據,并將狀態變量又設置為不滿足條件的狀態;
8) T3處理完畢返回;
9) T1被喚醒,但實際上此時條件并不滿足。
synchronized(obj) { while(<condition does not hold>) wait();
... // Perform action appropriate to condition }
synchronized(obj) { while(<condition does not hold>) { try { wait(); } catch (InterruptedException e) {} }
... // Perform action appropriate to condition }
1) 其它線程調用了notify(),而剛好線程T得到了通知;
2) 其它線程調用了notifyAll();
3) 其它線程中斷了線程T;
4) 由于JVM的原因,導致了spurious wakeup。
4.1.2 wait(), notify(), and notifyAll()
1. 正像多個線程等待同一對象上的鎖,當鎖釋放時,無法確定哪個線程會得到那個鎖一樣;當有多個線程在wait()時,當另外一個線程調用nofity()的時候,也不能確定哪個線程會被喚醒; 2. 因此在《Practical Java》的"實踐53:優先使用notifyAll()而非notify()"建議的一樣,結合實踐54,可以比較好的解決線程喚醒的問題。
4.1.3 Wait-and-Notify Mechanism with Synchronized blocks
再次強調必須在同一個對象的synchronized方法或塊內調用該對象上的wait和notify方法。
4.2 Condition Variables
1. 就像上面反復強調的一樣,wait-and-notify機制是與特定對象及其上的鎖是綁定在一起的,鎖和喚醒對象不能分開,這在某些情況下不是很方便;2. JDK 1.5提供Condition接口來提供與其它系統幾乎一致的condition variables機制;
3. Condition對象由Lock對象的newCondition()方法生成,從而允許一個鎖產生多個條件變量,可以根據實際情況來等待不同條件;
4. 該書的例子沒有什么特別的實際意義,但JDK 1.5文檔中提供了一個例子,能充分說明使用Condition Variables使得程序更加清晰易讀,也更有效率:
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
5. 除了用lock和await-and-signal來代替synchronized和wait-and-notify外,其語義和機制基本一樣。await()在進入前也會自動釋放鎖,然后再返回前重新獲得鎖;
6. 使用Condition Variables的原因:
1) 如果使用Lock對象,則必須使用condition variables;
2) 每個Lock對象可以創建多個condition variable.
任何一門支持多線程的語言中,多線程是都是一個讓人又愛又恨的東西。Java的多線程相對而言比其它語言要簡單一點,如果不是開發框架類或者系統級的程序,也許很少會碰到要明確碰到Java的多線程API,但事實上不等于你不用注意多線程安全的問題,尤其當你在開發Web程序的時候,在類中使用了靜態屬性(static fields)而不僅僅是對象屬性(instance fields)的時候,如果在壓力測試或者提交給用戶使用的時候,發生了一些不可重現的錯誤或者數據混亂的時候,那往往要查查這些使用了靜態屬性的類是否是多線程安全的了。當然,如果你專注于開發Web應用,并且很少涉及框架或核心模塊的開發,那也就基本上知道synchronized的關鍵字的應用就可以了。這也許就是Java多線程相對其它語言中多線程要簡單一點的原因。
當然,這次我打算比較深入地來了解了解一下Java多線程開發的其它一些內容,那么找一本好的書是一個比 較好的開始。關于Java多線程開發的專著比較有名的大約是《Java Threads, 3rd Edition》和《Java Thread Programming》了,前者基于JDK 1.5(這個版本對多線程進行了很大的改進)進行介紹,并且指出了與以前版本的區別;而后者出版于1999年,是基于JDK 1.2進行講解的。所以呢,基本上采用第一本為主。同時也參考一下《Practical Java》和《Effective Java》的相關條目。這幾本書的封面如下,相關書的介紹可去Amazon查看一下: