另外,采用utf-8編碼之后,用WinMerge比較文件時,就會出來Information lost to encoding errors:之類的提示,心想,這個指定個編碼就可以了吧。在編輯=>選項中,果然看到代碼頁的指定,如是選擇指定代碼頁,輸入utf-8,點擊確定,被提示請輸入一個整數,估計這時候大多數人和我一樣傻眼,utf-8的代碼頁是多少?好在有Google,調整不同的關鍵字,終于找到清炒苦瓜的一篇文章中提到utf-8的代碼頁是65001,并且也是為了解決WinMerge的亂碼問題,可是改完之后,并沒有起作用,這次沒有去懷疑這個代碼頁是不是正確,再仔細看一下那篇文章,嗯,人家明明說用的是2.6.8嘛,檢查一下自己的版本,2.6.0,是不是版本問題呢?來到WinMerge的網站,發(fā)現竟然已經是2.6.12了,于是下載最新版本,然后將代碼頁改成65001,嗯,這下所有的中文注釋都乖乖出來了。
2006年3月5日 #
另外,采用utf-8編碼之后,用WinMerge比較文件時,就會出來Information lost to encoding errors:之類的提示,心想,這個指定個編碼就可以了吧。在編輯=>選項中,果然看到代碼頁的指定,如是選擇指定代碼頁,輸入utf-8,點擊確定,被提示請輸入一個整數,估計這時候大多數人和我一樣傻眼,utf-8的代碼頁是多少?好在有Google,調整不同的關鍵字,終于找到清炒苦瓜的一篇文章中提到utf-8的代碼頁是65001,并且也是為了解決WinMerge的亂碼問題,可是改完之后,并沒有起作用,這次沒有去懷疑這個代碼頁是不是正確,再仔細看一下那篇文章,嗯,人家明明說用的是2.6.8嘛,檢查一下自己的版本,2.6.0,是不是版本問題呢?來到WinMerge的網站,發(fā)現竟然已經是2.6.12了,于是下載最新版本,然后將代碼頁改成65001,嗯,這下所有的中文注釋都乖乖出來了。
1. 為什么老是提示“Your credentials are not correct. Please check them and try again. ”?
這個多半是svn初學者常碰到的問題,尤其在使用HTTP協議的時候。一開始大家都用svnserve來做svn的服務器,自然配置的是conf中的passwd。但如果采用HTTP協議的話,就得使用Basci HTTP Authentication了,需要用Apache提供的htpasswd來管理用戶和口令。這個的配置在svn自帶的幫助文件中第6章“httpd, the Apache HTTP Server”一節(jié)中有比較詳細的解釋。但如果你沒有通篇看完,在Apache中沒有加上Require valid-user指令的話,那是允許匿名操作的。我想,你不愿意留下這樣的安全漏洞吧。
2. 怎樣配置多個repository
這個也是實際中需要的,當然,在它的readme中其實是說得很清楚的。但我們有些同志就是喜歡拿來就試,尤其是在有些類似于我這種其實語焉不詳的文章時,更是就喜歡照葫蘆畫瓢,而不去看最權威最原始的英文文檔了。要配置多個repository,以HTTP協議為例,在web.xml中要刪掉RepositoryUrl、Username、Password這3個參數的設置,然后加上ParentRepositoryDirectory參數,值自然是指向svn倉庫的父目錄了,比如http://localhost/svn/,這個東西又是哪里來的呢?自然需要在Apache中配置,用SVNParentPath來指定svn倉庫的父目錄,Apache會自動解析其下所有的倉庫的。這里要注意一下AuthzSVNAccessFile授權文件的寫法,這里將配置所有倉庫的存取權限,對于每個倉庫,需要用[倉庫名:/module]的方式來配置。
3. 怎樣使用svn協議
前面我一直用http協議做例子,實在是因為我在其2.5.0下沒有配置出來過svn協議:(。這次去其網站下載了個最新的nightly版本,發(fā)現其已經能夠支持http, svn, svn+ssh, ssl和proxy等6種協議了。看看其代碼結構,好象也發(fā)生了很大的變化,估計應該有比較大的改進。于是,用這個版本試了一把,呵呵,輕而易舉就把svn協議給連通了,包括多倉庫的情況。并且其還改進了原來設置父目錄地址時一定要在最后添加/的要求,估計原來在這個地方卡殼的朋友也不少吧:)。不過,新版本還是不支持中文文件名,看我以前的帖子自己改吧。
Important: 由于svn webclient采用的javasvn(現已更名為svnkit)版本較低,用svn協議在提交老的文件時會失敗,但添加新的文件時沒有問題,所以,大家就不要再嘗試svn協議了。如果不采用SVN協議,則其官方發(fā)布的版本就沒什么問題了,已經有網友重新打包了一個解決了中文文件名的版本,到這里下載。(Updated: 2007.1.20)
...
不要憂慮“不要為明天憂慮,天上的飛鳥,不耕種也不收獲,上天尚且要養(yǎng)活它,田野里的百合花,從不憂慮它能不能開花,是不是可以開得和其它一樣美,但是它就自然的開花了,開得比所羅門皇冠上的珍珠還美。你呢,憂慮什么呢? 人比飛鳥和百合花貴重多了,上帝會棄你不顧嗎?” 閱讀全文
“我雖然行過死蔭的幽谷,
也不怕遭害;
因為你與我同在;
你的杖,你的竿,都安慰我。”
--《詩篇第二十三篇第四節(jié)》
當然,一開始我不知道這是《舊約全書·詩篇》中的內容,于是就Google了一把,找到下面這篇文章《因為你與我同在》:
“
我去一個鄉(xiāng)村教會講道,在接待的弟兄家休息。看見弟兄的兒子學習很用功,很是喜愛。我請小朋友將他的語文課本借給我看。這是人教版小學四年級語文課本。當中有一篇文章吸引了我——
1989 年美國洛杉磯大地震,30萬人在不到4分鐘時間里受到不同程度的傷害。這其間一個小學的慘況讓人心酸。多少孩子的父母因在地震中痛失愛子而哀聲聞天,面對 地震后的學校廢墟只能絕望離去。但一個父親卻在廢墟中不斷地挖掘尋找自己那才七歲的叫阿曼達的兒子。救火隊長擋住他,“太危險了,隨時可能發(fā)生大爆炸,請 你離開。”別的學生家長也勸說,“太晚了,沒有希望了。”可這位父親不顧這些勸阻,在廢墟中不斷地尋找。就在他挖了整整八個小時后,父親聽見瓦礫堆底下傳 出他十分熟悉的聲音,“爸爸,是你嗎?”這時他發(fā)現有14個孩子在當中,因為這是在教室墻角的位置,當房頂塌下時架成大三角形,孩子們才沒被砸著。“我告 訴同學們不要害怕,說只要我爸爸活著就一定會來救我,也能救大家。因為你說過,不論發(fā)生什么,你總會和我在一起!”孩子對爸爸說。“爸爸,先讓我的同學出 去吧,我知道你會跟我在一起,我不怕。不論發(fā)生了什么,我知道你總會跟我在一起。”
14個孩子獲救了!
我們不禁會問,如果因為大爆炸的危險而絕望地放棄,如果這個父親和其他的家長一樣絕望地離開,如果阿曼達沒有“因為你與我同在”的信念,那結果又將如何?
我想說的是,絕望讓生命失去,希望使生命存留。
詩人說,“因為你與我同在。”(詩篇23:3)這樣的信心使詩人在人生的黑夜里依然有生命的曙光,在人生的冬天里可憑信宣告說,冬天來了,上帝的春天也不再遙遠。
“因為我與你同在,要拯救你。這是耶和華說的。”(耶利米書1:8)
阿曼達對父親單純的信念應一如我們對天上的父親執(zhí)著的信仰。
是啊,“絕望讓生命失去,希望使生命存留”,不過呢,對于我這個不信教的人來說,大可抱著象故事中的小阿曼達相信他父親只要活著就一定會來救他的希望一樣,也不一定非要抱著耶和華會來救我們的希望的。不過,不管信什么教,基督也好,佛陀也好,其實所有經典中的智慧都是值得學習的;當然,如果你在這個浮躁的物質社會中實在找不到可以依賴的東西時,信個把教也未嘗不可。
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章的例子,發(fā)現基本上沒有這個問題。難道第3章的代碼是正確的?打開源代碼一看,重置成績的方法還是放在最后,那這里為什么又是正確的呢?我想,大約是第3章的例子中,每次點擊start按鈕,都重新創(chuàng)建一個線程對象的原因吧。由于創(chuàng)建對象和初始化線程需要一定的時間,剛好給了主線程重置成績的機會。
不知道作者有意為之呢,還是疏忽,不過,這樣的錯誤不能算是race condition的例子。
《Java Threads》一書中通過考察打字程序中當按下start和stop按鈕后,每次都創(chuàng)建兩個新的線程的效率問題來引入線程同步的概念,當然不是線程同步的主要用處。不過,教科書歸教科書,實際運用則又是另一回事。所以,通過書本學習語法,通過實踐來獲得運用經驗。
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(long timeout): 使當前線程處于等待狀態(tài),直到其它線程調用了nofity()或者notifyAll()方法,或者超過了指定的時間(單位為ms)為止
void wait(long timeout, int nanos):與wait(long)一樣,只是在某些JVM中可以精確到奈秒。
void notify(): 喚醒一個正在等待該對象的線程。
void notifyAll(): 喚醒所有正在等待該對象的線程。
4. wait()和sleep()的主要區(qū)別:
1) sleep()可以在任何地方調用,而wait()需要在同步方法或同步塊中調用;
2) 進入wait()函數時,JVM會自動釋放鎖,而當從wait()返回即被喚醒時,又會自動獲得鎖;而sleep()沒有這個功能,因此如果在wait()的地方用sleep()代替,則會導致相應的nofity()方法在等待時不可能被觸發(fā),因為notify()必須在相應的同步方法或同步塊中,而此時這個鎖卻被sleep()所在的方法占用。也就是說,wait-and-notify不可能與sleep()同時使用。
4.1.1 The Wait-and-Notify Mechanism and Synchronization
1. 這一節(jié)詳細的講解了wait-and-notify機制和synchronized的關系,主要是兩點:1)wait-and-notify必須和synchronized同時使用;2)wait()會自動釋放和獲取鎖;
2. 這一節(jié)中舉了一個例子用來解釋可能存在當條件被不滿足時也有可能被喚醒的情況:
2) T1檢測狀態(tài)變量,發(fā)現其不滿足條件;
3) T1調用wait(),并釋放鎖;
4) 線程T2調用另外一個同步方法,獲得鎖;
5) 線程T3調用另外一個同步方法,由于T2獲得了鎖,所以處于等待狀態(tài);
6) T2修改狀態(tài)變量,使其滿足條件,并調用notify()方法;
7) T3獲得鎖,然后處理數據,并將狀態(tài)變量又設置為不滿足條件的狀態(tài);
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:優(yōu)先使用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對象可以創(chuàng)建多個condition variable.
任何一門支持多線程的語言中,多線程是都是一個讓人又愛又恨的東西。Java的多線程相對而言比其它語言要簡單一點,如果不是開發(fā)框架類或者系統級的程序,也許很少會碰到要明確碰到Java的多線程API,但事實上不等于你不用注意多線程安全的問題,尤其當你在開發(fā)Web程序的時候,在類中使用了靜態(tài)屬性(static fields)而不僅僅是對象屬性(instance fields)的時候,如果在壓力測試或者提交給用戶使用的時候,發(fā)生了一些不可重現的錯誤或者數據混亂的時候,那往往要查查這些使用了靜態(tài)屬性的類是否是多線程安全的了。當然,如果你專注于開發(fā)Web應用,并且很少涉及框架或核心模塊的開發(fā),那也就基本上知道synchronized的關鍵字的應用就可以了。這也許就是Java多線程相對其它語言中多線程要簡單一點的原因。
當然,這次我打算比較深入地來了解了解一下Java多線程開發(fā)的其它一些內容,那么找一本好的書是一個比 較好的開始。關于Java多線程開發(fā)的專著比較有名的大約是《Java Threads, 3rd Edition》和《Java Thread Programming》了,前者基于JDK 1.5(這個版本對多線程進行了很大的改進)進行介紹,并且指出了與以前版本的區(qū)別;而后者出版于1999年,是基于JDK 1.2進行講解的。所以呢,基本上采用第一本為主。同時也參考一下《Practical Java》和《Effective Java》的相關條目。這幾本書的封面如下,相關書的介紹可去Amazon查看一下:
Atomic variables: A set of classes that provide threadsafe operations without synchronization
Explicit locks: Synchronization locks that can be acquired and released programmatically
Condition variables: Variables that can be the subject of a targeted notification when certain conditions exist
Queues: Collection classes that are thread-aware Synchronization primitives: New classes that perform complex types of synchronization
Thread pools: Classes that can manage a pool of threads to run certain tasks
Thread schedulers: Classes that can execute tasks at a particular point in time
在《Java Threads》一書中將其歸納為三類:
1. 對現有功能的新實現;
2. 提供了重要的多線程工具,如線程池(pool)和計劃(schedule);
3. 最小化同步工具(Minimal synchronization utilities)。
這些功能的妙處我現在自然是無法體會得到,但對于JDK 5.0中提供的這些多線程工具,會不會也遭遇JDK 1.4提供的Log API的命運,因敵不過第三方工具而成為擺設呢(至少目前我還在用Log4J,且其也沒有停止開發(fā)的跡象)?
Chapter2: Thread Creation and Management
2.1 What Is a Thread?
介紹了什么是線程,以及線程(thread, multithread)與進程(process, mutltitask)的區(qū)別。其中的一個重要區(qū)別是對共享數據的訪問。進程可以共享操作系統級別的某些數據區(qū)域,如剪貼板;而線程是對程序自有的數據進行共享。進程之間對共享數據的存取方法是特殊的,因此自然能夠得到程序員的注意;而線程之間對共享數據的存取與對線程自己的數據的存取方法是一樣的,所以常常比較隱蔽,而被程序員忽略。其實,這也是多線程開發(fā)比較難的地方。所以,這一節(jié)最后說:"A thread, then, is a discrete task that operates on data shared with other threads.(線程就是一個在與其它線程共享的數據上進行操作的單獨任務。)"
2.2 Creating a Thread
1. 有兩種方法創(chuàng)建線程:使用Thread類或者Runnable接口
2. 示例程序竟然是編一個打字游戲,所以,這本書的門檻還是有點高的:(。當然可以從該書的站點直接下載代碼。
package java.lang;
public class Thread implements Runnable {
public Thread( );
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(String name);
public Thread(ThreadGroup group, String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize);
public void start( );
public void run( );
}
Runnable target:可運行的目標。一個可運行的對象就是一串可由線程執(zhí)行的指令。缺省就是在run()方法中編寫的內容。
Thread group:線程組。對于大多數應用來說沒什么意義,缺省在同一組。
Stack size:線程堆棧大小,與平臺相關。為了使程序更具可移植性,并不推薦使用。
2.3 The Lifecycle of a Thread
1. Creating a Thread:創(chuàng)建一個線程對象,并沒有開始執(zhí)行一個線程
2. Starting a Thread:在調用線程對象的start()方法前,一直處于等待狀態(tài)。可以通過調用isAlive()方法來獲取線程是否正在運行;
3. Terminating a Thread:線程在以下情況下結束
1)執(zhí)行完run()中的語句;
2)拋出一個異常,包括其沒有捕獲的異常;
3)其所在的程序(或者容器)調用System.exit()。
注意:一個線程結束后,就不能再次啟動,該線程就變成一個普通的對象,如果試圖再次調用start()方法,將拋出java.lang.IllegalThreadStateException異常。如果想多次運行,要么創(chuàng)建新的對象;要么就是不要結束該線程。
4. Java并沒有提供可靠的方法來暫停、掛起或者重啟一個線程的方法,線程本身能通過sleep()函數來暫停執(zhí)行。目前依然只能確保若干毫秒級別的精度。
5. Thread Cleanup:線程終止后,線程對象依然存在。可以通過join()方法以阻塞方式(blocked)來等待某個線程終止。
2.4 Two Approches to Stop a Thread
1. Setting a Flag:在線程的執(zhí)行過程中判斷其它線程是否將標志置位。其缺點是有時間延遲,尤其是當進入了某些被阻塞的函數如:sleep(), wait()等;
2. 調用線程的interrupt()方法,線程的執(zhí)行過程中改為判斷isInterrupted()的返回值。可以解決線程進入阻塞導致的延遲,但依然解決不了超長時間計算而導致的延遲。interrupt()有兩個作用:1)終止sleep()和wait(),拋出InterruptedException;2)使isInterrupted()返回true。下面這段代碼將演示interrupt()的作用:
public class TestInterrupted {
public static void main(String args[]) throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread.currentThread().sleep(100); //注釋掉這句有什么影響呢?
System.out.println("before interrupt");
foo.interrupt();
System.out.println("after interrupt");
} }
class Foo extends Thread {
public void run() {
try{
while(!isInterrupted()) {
System.out.println("start calculating...");
double pi = 3.1415926;
for(int i=0; i<5; i++) {
for(long j=0; j<1000000; j++) {
pi *= pi;
}
System.out.println("i="+i);
}
System.out.println("before sleep");
sleep(5000); //注釋掉這句及相關的try...catch語句,又怎樣呢?
System.out.println("after sleep");
}
} catch(InterruptedException ex) {
ex.printStackTrace(System.out);
}
}
}
2.5 The Runnable Interface
為什么有了Thread類,還要有Runnable接口呢?最主要的原因是為了解決Java不能多重繼承的問題。線程繼承自Thread,還是僅僅實現Runnable接口,取決于這個線程的作用。不過,如果僅僅實現Runnable接口,則在線程里不能使用Thread類的方法,比如interrupt()和isInterrupted()。當然,這個還是要取決于實際情況。
2.6 Threads and Objects
主要講解線程與對象的關系。線程根本上還是個對象,其可以存取任何對象包括其它線程對象,當然也能被其它對象存取,只要沒有因為爭奪公共資源而被鎖定,線程對象中的任何屬性和方法都有可能同時執(zhí)行。并且Java中的鎖只針對對象本身,并不包括對象下的屬性;而對方法同步,則等同于對對象同步。更進一步的說明還可以參考《Practical Java》中的"實踐46:面對instance函數,synchronized鎖定的是對象(object)而非函數(methods)或代碼(code)"。下面用兩段代碼來說明一下:
代碼1:
public class TestLock {
public static void main(String args[])
throws InterruptedException{
Foo foo = new Foo();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo extends Thread{
protected int arrI[] = new int[10];
public void run() {
try {
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
} catch(InterruptedException ex) {}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public void setInt2(String from, int pos, int val)
throws InterruptedException {
synchronized(arrI){
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
}
Foo:arrI[0]=0
Main:arrI[0]=0
Main:arrI[1]=21
Main:arrI[2]=22
Foo:arrI[1]=21
Main:arrI[3]=23
Main:arrI[4]=24
Main:arrI[5]=25
Foo:arrI[2]=2
Main:arrI[6]=26
Main:arrI[7]=27
Foo:arrI[3]=3
Foo:arrI[4]=4
Main:arrI[8]=28
Main:arrI[9]=29
Foo:arrI[5]=5
Foo:arrI[6]=6
Foo:arrI[7]=7
Foo:arrI[8]=8
Foo:arrI[9]=9
代碼2:
public class TestLock1 {
public static void main(String args[])
throws InterruptedException{
Foo1 foo = new Foo1();
foo.start();
Thread t = Thread.currentThread();
for(int i=0; i<10; i++) {
foo.setInt2("Main", i, i+20);
}
}
}
class Foo1 extends Thread{
protected int arrI[] = new int[10];
public void run() {
try{
for(int i=0; i<10; i++) {
setInt("Foo", i, i);
}
}catch(InterruptedException ex){}
}
public synchronized void setInt(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
public synchronized void setInt2(String from, int pos, int val)
throws InterruptedException{
arrI[pos] = val;
sleep((long)(Math.random()*5000));
System.out.println(from+":arrI["+pos+"]="+arrI[pos]);
}
}
Chapter 3. Data synchronization
在第二章中介紹了如何創(chuàng)建線程對象、啟動和終止線程。但多線程編程的關鍵在于多個線程之間數據的共享和同步,從這一章開始,將詳細介紹線程之間數據的共享和同步的各種方法。3.1 The Synchronized Keywor
1. synchronized是Java中最基本也最常用的用來編寫多線程安全代碼的關鍵字,用以保護對多線程共享的數據的操作總是完整的;
2. Atomic: 當一個操作被定義成原子操作時,意味著該操作在執(zhí)行過程中不會被打斷;原子操作可以由硬件保證或者通過軟件來模擬;
3. Mutex Lock: 在很多多線程系統,通過互斥鎖來保護的共享數據。Java中的任何一個對象都有一個與之相關的鎖,當一個方法被聲明成synchronized時,就表示當線程進入該方法前,必須獲得相應對象的鎖,在執(zhí)行完畢再釋放這個鎖。從而保證同一時刻只有一個線程調用該對象上的被聲明為synchronized的方法。注意:Java的互斥鎖只能加在對象級上,獲得某個對象的鎖,并不能保證該對象的屬性和其它非synchronized的方法是線程安全的;也不能保證受保護的方法里調用的其它對象是多線程安全的,除非任何調用這些沒有被保護的方法或者對象只通過受保護的方法進行調用。所以,編寫線程安全的代碼關鍵就在于規(guī)劃方法和對象之間的調用關系,并盡量采用相同對象的鎖來進行同步控制。
3.2 The Volatile Keyword
1. Scope of a Lock: 鎖的作用范圍即獲得和釋放鎖之間的那段時間。
2. Java標準雖然聲明存取一個非long和double變量的操作是原子操作,但由于不同虛擬機實現的差異,在多線程環(huán)境下每個線程可能會保留自己的工作拷貝,而導致變量的值產生沖突。為了避免這種情況的發(fā)生,可以有兩種方法:
1) 為變量創(chuàng)建聲明為synchronized的setter和getter方法,然后任何調用(包括在類類部)該變量的地方都通過setter和getter方法;
2) 采用volatile聲明,確保每次存取這些屬性時都從主內存中讀入或者寫入主內存中;
3) volatile僅僅用于解決Java內存模式導致的問題,只能運用在對該變量只做一個單一裝載或寫入操作且該方法的其它操作并不依賴該變量的變化。如:在一個循環(huán)體中作為遞增或遞減變量時就不能使用volatile來解決線程同步的問題。
3. volatile的使用是有限的,一般而言,僅僅將其作為強制虛擬機總是從主內存讀寫變量的一個手段,或者某些需要其參數聲明為volatile的函數。
3.3 More on Race Conditions
本節(jié)以打字游戲中顯示成績的例子來解釋了可能存在的race condition。關鍵要注意以下幾點:
1. 操作系統會隨機的切換多個線程的運行,因此當多個線程調用同一個方法時,可能在任何地方被暫停而將控制權交給另外的線程;
2. 為了減少被誤用的可能,總是假設方法有可能被多個線程調用;
3. 鎖僅僅與某個特定實例相關,而與任何方法和類都無關,這一點當需要存取類屬性或方法時要特別注意;
4. 任何時候只有一個線程能夠運行某個類中一個被聲明為synchronized的靜態(tài)方法;一個線程只能運行某個特定實例中一個被聲明為synchronized的非靜態(tài)方法。
3.4 Explicit Locking
1. 學過Win32下編寫多線程的朋友剛開始可能會被Java的Synchronized關鍵詞搞糊涂。因為Java中的任何一個對象都有一個與之相關的鎖,而不象在Win32下要先定義一個互斥量,然后再調用一個函數進入或者離開互斥區(qū)域。在JDK 1.5以后也開始提供這種顯示聲明的鎖。JDK 1.5中定義了一個Lock接口和一些類,允許程序員顯示的使用鎖對象。
2. 在Lock接口里有兩個方法:lock()和unlock()用來獲取和釋放鎖對象,從而保證受保護的代碼區(qū)域是線程安全的。
3. 使用鎖對象的一個好處在于可以被保存、傳遞和拋棄,以在比較復雜的多線程應用中使用統一的鎖。
在使用鎖對象時,總是將lock()和unlock()調用包含在try/finally塊中,以防止運行時異常的拋出而導致死鎖的情況。
3.5 Lock Scope
利用lock()和unlock()方法,我們可以在任何地方使用它們,從一行代碼到跨越多個方法和對象,這樣就能根據程序設計需要來定義鎖的作用(scope of lock)范圍,而不是象以前局限在對象的層次上。
3.5.1 Synchronized Blocks
1. 利用synchronized關鍵字也能建立同步塊,而不一定非得同步整個方法。
2. 同步一段代碼時,需要明確的指定獲取哪個對象的鎖(這就類似于鎖對象了),這樣,就可以在多個方法或對象中共享這個鎖對象。
3.6 Choosing a Locking Mechanism
使用鎖對象還是同步關鍵字(synchronized),這個取決于開發(fā)員自己。使用synchronized比較簡單,但相對而言,比較隱晦,在比較復雜的情況下(如要同時同步靜態(tài)和非靜態(tài)方法時),沒有鎖對象來得直觀和統一。一般而言,synchronized簡單,易于使用;但同時相對而言效率較低,且不能跨越多個方法。
3.6.1 The Lock Interface
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
void unlock();
Condition newCondition();
}
3.7 Nested Locks
1. 在一個同步方法內調用同一個對象上的另外的同步方法的情況,稱之為嵌套鎖(nested locking)。系統將自動執(zhí)行嵌套的同步方法,而無須等待鎖的釋放。因此,有的時候,即使一些私有方法僅僅被已同步的方法調用,我們也給其加上synchronized關鍵字,以減少后續(xù)維護時可能產生的誤導。
2. ReenterantLock類也支持嵌套鎖。在ReenterantLock類維持一個計數器,只有當這個計數器為0時,才會釋放鎖。注意:這個特性是ReenterantLock類的特性,而不是所有實現Lock接口的類的特性。
3. 需要支持嵌套鎖的一個原因是方法之間交叉調用(cross-calling)。設想對象a的方法M1調用對象b的方法N1,然后N1再調用對象a的方法M2,而M1和M2都是同步方法,如果不支持嵌套鎖,則N1將在調用M2時等待M1釋放鎖,而M1則由于N1沒有返回永遠也不會釋放鎖,這樣就產生了死鎖。
4. synchronized和Lock接口并沒有提供鎖對象被嵌套獲取的次數,但ReentrantLock則提供了這樣一種機制:
public class ReentrantLock implements Lock {
public int getHoldCount();
public boolean isLocked();
public boolean isHeldByCurrentThread();
public int getQueueLength();
...
}
1) getHoldCount()返回當前線程的獲取次數,返回0并不表示該鎖是可獲取的,有可能沒有被當前線程獲得;
2) isLocked()判斷該鎖對象是否被任何線程獲得;
3) isHeldByCurrentThread()判斷是否由當前線程獲得;
4) getQueueLength()用來估計當前有多少線程在等待獲取這個鎖對象。
3.8 Deadlock
介紹了死鎖的概念,并修改例子代碼來演示deadlock的產生。死鎖一般產生在多個線程在多個鎖上同步時產生的;當然,多個線程在判斷多個條件的時候,也有可能產生死鎖。
3.9 Lock Fairness
1. Java里鎖的獲取機制依賴于底層多線程系統的實現,并不保證一個特定的順序;
2. ReentrantLock類提供一個先進先出(first-in-first-out)的獲取鎖的選項(在創(chuàng)建鎖對象時傳入true值)。
文章來源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616068.aspx

等著你回來
看那桃花開
把那花兒采
桃花朵朵開
暖暖的春風迎面吹
枝頭鳥兒成雙對
情人心花兒開
啊喲啊喲
你比花還美妙
叫我忘不了
秋又去春又來
記得我的愛
給你把花戴
嘗嘗家鄉(xiāng)菜
團圓樂開懷
暖暖的春風迎面收
別把我忘懷
把那花兒揀
文章來源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616108.aspx
一個月過去了,女孩仍然昏睡,而男孩早已憔悴不堪了,看他仍苦苦支撐。上帝被他感動了,決定給這個執(zhí)著的男孩一個例外,上帝問他:“你愿意用你自己的生命作為交換嗎?”男孩毫不猶豫的回答“我愿意”上帝說:“那好吧,我可以讓你的戀人很快醒來,看你要答應我 化做三年的蜻蜓。”
天亮了,南海變成了一只蜻蜓,他匆匆地飛到了醫(yī)院,女孩真的醒了,她還在跟身旁的一名醫(yī)生在交談。
女孩出院了,但是她并不快樂。她四處打聽男孩的下落,女孩整天不停地尋找,早已化成蜻蜓的男孩卻無時無刻不圍繞在的身邊,只是他不會呼喊,不會擁抱,他只能默默地承受著她視而不見。夏天過去了,蜻蜓不得不離開這里,他最后一次飛落在女孩的肩上。他想用細小的嘴來親親她的額頭,然而他弱小的身體不足以被她發(fā)現。春天來了,蜻蜓迫不及待地飛回來,然而,她那熟悉的身影旁站著高大而英俊的男人,那一剎那,蜻蜓幾乎快從半空中跌落下來。蜻蜓傷心極了,他常常會看到那個男人帶著自己的戀人在海邊看日出,晚上又在海邊看日落,而他自己除了偶然能停落在的肩上之外,什么也做不了。第三年夏天,蜻蜓已不再常常去看望自己的戀人了。她的肩被男醫(yī)生擁著,臉被男醫(yī)生輕輕地吻著,根本沒時間去留意一只傷心的蜻蜓,更沒有心情去懷念過去。三年的期限很快就要到了。最后一天,蜻蜓昔日的戀人跟那個男醫(yī)生舉行了婚禮。
蜻蜓悄悄地飛進了教堂,落在上帝的肩膀上,他聽到下面的戀人對上帝發(fā)誓說“我愿意”他看著男醫(yī)生把戒指戴到昔日戀人的手上,然后他看見他們甜蜜地親吻。蜻蜓流下了傷心的眼淚。
上帝嘆息著:“你后悔了嗎?”蜻蜓擦干了眼淚:“沒有!”上帝又帶著一絲愉悅說:“明天你就可以變回你自己了。”蜻蜓搖搖頭說:“就讓我一輩子做蜻蜓吧……”
有些緣分是注定要失去的,愛一個人不一定要擁有,但是擁有一個人就一定要好好去愛他。你的肩上有蜻蜓嗎?

飄飄:前面漆黑一片,什么也看不到。
伊天仇:可是等到天亮,景色就會很美麗了。
這個對話大概就是這樣子,是啊,如果你現在眼前一片漆黑,那倒真用不著怎樣害怕,天也許就快亮了。怕的就是,眼前白茫茫一片,分不清到底是快天亮了呢,還是天要黑了。
接下來,飄飄就和伊天仇一夜風流,處于人生最低谷的伊天仇不知道這是愛情呢,還是僅僅一夜情,于是翻箱倒柜,將能找到的所有錢、手表之類的全部放在了飄飄的衣服上,繼續(xù)裝睡。而飄飄呢,也以為伊天仇僅僅是將自己當作風塵女子,也只能瀟灑地走開。然而,當伊天仇追出來說:“我養(yǎng)你”的時候,我覺得應該是全劇最精彩的情節(jié)之一了。兩個處于社會底層且又在自己最失意的時候,終于能夠找到相互欣賞和珍惜的愛人,的確是比較感人的。難怪飄飄在出租車里拿出伊天仇一直看的那本怎樣當喜劇演員的書時會淚流滿面了。
唉,現在看片子,有的時候連這樣一句兩句令人欣賞的臺詞也沒有了。
文章來源:http://blog.csdn.net/evanwhj/archive/2006/03/05/616120.aspx