1. 程式人生 > >java執行緒資料共享問題總結

java執行緒資料共享問題總結

1.執行緒同步,一個關鍵字:synchronized


為什麼有這個東西呢,假如有一個物件,裡面有成員變數和方法,如果有很多執行緒都想訪問它們,有可能造成使用者想避免的結果。


我也舉那個經典的例子:假如你的銀行賬戶裡面有2000塊錢,有一天你去銀行櫃檯取錢,取1500,正在你辦理的時候,你老婆去了取款機,她也取錢,事先沒商量好誰取,所以她也想取1500。如果兩個人都取走了1500,合起來就3000了,銀行咋辦???


我們把這個銀行賬戶當作一個類來看待,裡面有一個錢的成員變數,有一個對錢的數量進行加減的方法,一個getMoney的方法。


在櫃檯取錢和在取款機取錢分別為2個執行緒。當兩個執行緒同時訪問銀行賬戶這個類的物件的時候。都呼叫了錢的減法運算的方法,並通過getMoney方法拿到了1500塊錢,所有人都這樣幹,於是,銀行破產了。


解決方法,同步。


我在方法宣告的時候前面加一個synchronized關鍵字,public synchronized void method(){ },它代表的意思是在執行這個方法的時候當前物件被鎖定起來。


Java中的每個物件都有一個lock,當訪問某個物件的synchronized方法時,該物件就會被上鎖(注意,是物件,不是方法,假如你在這個類中定義了多個方法,如果你的執行緒訪問到了其中的任意一個synchronized方法,那麼其它的就暫時不能被訪問了,必須等到該物件被解鎖以後,即方法執行結束才行)。解鎖的意思是值執行緒執行該方法完畢,或者說過程中丟擲了異常。


再換一種說法,就是,一個類中有synchronized方法,如果該類的物件的該方法被訪問時,那麼整個該物件都被鎖定了,但是這個意思是其它非synchronized方法和成員變數還是可以被訪問,注意區分這一點。因為synchronized方法會鎖定物件,所以一旦有一個synchronized方法被某個執行緒啟動了,那麼物件已經被獨佔了,其它的synchronized方法就不能再同時獨佔物件了,但是普通方法和成員變數並不獨佔物件,所以仍然可以被呼叫。


需要注意的是,如果同步方法裡面有sleep方法,它仍然是同步方法的一部分,在它被執行的過程中,鎖仍然不會被解開。


其實同步的意思就是上鎖,同步方法,進而達到物件上鎖的目的。假如有一個數據庫,有讀和修改2個方法,你可以允許多個執行緒同時讀,但是你不能讓多個執行緒同時改,所以說改的方法要同步,讀的方法不需要。(其實這裡我更加覺得應該同步的不是方法,而是資料本身,只要有物件訪問物件,物件就應該被鎖定,避免讀的時候有物件要修改,修改的時候有物件要讀,甚至是多個物件同時都想改)。還有,如果2個方法都修改了同一個值的話,那麼2個方法都應該加同步。


執行緒同步我覺得是這樣的,你說概念吧,也還不難理解,我覺得真正難的是實際中的應用,你必須考慮很多相關的問題,哪一個方法要同步,都需要好好琢磨。


在這裡說3個方法,wait,notify,notifyAll。


之前說過一個方法叫做sleep,通常來說你按照自己的經驗和感覺要求執行緒睡眠一定的時間。但是,有時候當你不知道需要執行緒睡眠多久的時候,sleep方法就不行了,必須使用wait。但是記住,wait只能用於同步方法。用法大概可以這樣,比如你可以先進行一個while判斷(不推薦用if,假如有excpetion發生的話,就不再判斷直接執行後面的,這樣可能還是有問題,所以最好用while,即使exception發生了,仍然會進行判斷),如果滿足一定條件就this.wait,然後不滿足了就this.notify。如果有很多同步方法的話,那麼也可以使用notifyAll方法,那麼在這個物件上面等著的執行緒都會被叫醒。


synchronized關鍵字囊括了所有和同步有關的東西。除此之外,還有一個關鍵字volatile,它只能用來同步基本型別的成員變數。資料的寫入通常來說是通過快取寫入記憶體的,使用volatile的原理就是它會繞過快取,直接寫入記憶體。讀取資料的時候同樣也直接從記憶體讀取,這樣就可以有效地避免資料不同步的情況。


同步的幾個準則:
a.首先,儘量使得synchronized塊保持簡短。你鎖的東西越多,越可能造成死鎖。
b.不要在synchronized塊中呼叫那些可能引起阻塞的方法,比如read。
c.如果持有了鎖的話,不要對其它物件呼叫方法。


2.執行緒死鎖
既然可以上鎖,那麼假如有2個執行緒,一個執行緒想先鎖物件1,再鎖物件2,恰好另外有一個執行緒先鎖物件2,再鎖物件1。
在這個過程中,當執行緒1把物件1鎖好以後,就想去鎖物件2,但是不巧,執行緒2已經把物件2鎖上了,也正在嘗試去鎖物件1。
什麼時候結束呢,只有執行緒1把2個物件都鎖上並把方法執行完,並且執行緒2把2個物件也都鎖上並且把方法執行完畢,那麼就結束了,但是,誰都不肯放掉已經鎖上的物件,所以就沒有結果,這種情況就叫做執行緒死鎖。


其中一個解決方法就是加大鎖定的粒度,也就是儘量鎖大的物件,不要鎖得太小,還有儘量不要同時鎖2個或2個以上的物件,但是還有待於進一步研究。


3.wait和notify和notifyAll
主要是用來讓執行緒之間互相通知事件的發生。


1).wait
Object類中的final方法,有InterruptedException。它的作用是導致當前的執行緒等待,直到其它執行緒呼叫此物件的notify方法或者notifyAll方法,wait還有一些重用方法,傳引數,比如說時間長度。
當前的執行緒必須擁有此物件監視器,然後該執行緒釋出對此監視器的所有權並且開始等待,直到其它執行緒通過呼叫notify方法或者notifyAll方法,通知在此物件的監視器上等待的執行緒醒來,然後該執行緒將等到重新獲得對監視器的所有權後才能開始執行。
說說wait和sleep的區別
首先sleep
sleep是Thread裡面的方法,在被執行的時候,鎖並不會被交出去,要直到sleep所在的方法全部被執行完畢以後才交出鎖。
wait是Object裡面的方法,在被執行的時候,鎖被解除,由其它執行緒去爭奪,直到有notify或者notifyAll方法喚醒它。


2).Notify
也是Object類中的方法,用於喚醒在此物件上等待著的某一個執行緒,如果有很多執行緒掛起的話,就隨機地決定哪一個。注意,是隨機的,這時可以用notifyAll來喚醒所有的。一定要注意這個問題,除非你明確地知道你在做什麼,否則最好就是用notifyAll。


注意事項:
wait()和notify()必須包括在synchronized程式碼塊中,等待中的執行緒必須由notify()方法顯式地喚醒,否則它會永遠地等待下去。很多人初級接觸多執行緒時,會習慣把wait()和notify()放在run()方法裡,一定要謹記,這兩個方法屬於某個物件,應在物件所在的類方法中定義它,然後run中去呼叫它。