1.線程同步秃励,一個關(guān)鍵字:synchronized
為什么有這個東西呢傍念,假如有一個對象,里面有成員變量和方法股冗,如果有很多線程都想訪問它們霹陡,有可能造成用戶想避免的結(jié)果。
我也舉那個經(jīng)典的例子:假如你的銀行賬戶里面有2000塊錢止状,有一天你去銀行柜臺取錢烹棉,取1500,正在你辦理的時候怯疤,你老婆去了取款機浆洗,她也取錢,事先沒商量好誰取集峦,所以她也想取1500伏社。如果兩個人都取走了1500抠刺,合起來就3000了,銀行咋辦洛口?矫付??
我們把這個銀行賬戶當(dāng)作一個類來看待第焰,里面有一個錢的成員變量买优,有一個對錢的數(shù)量進行加減的方法,一個getMoney的方法挺举。
在柜臺取錢和在取款機取錢分別為2個線程杀赢。當(dāng)兩個線程同時訪問銀行賬戶這個類的對象的時候。都調(diào)用了錢的減法運算的方法湘纵,并通過getMoney方法拿到了1500塊錢脂崔,所有人都這樣干,于是梧喷,銀行破產(chǎn)了砌左。
解決方法,同步铺敌。
我在方法聲明的時候前面加一個synchronized關(guān)鍵字汇歹,public synchronized void method(){ },它代表的意思是在執(zhí)行這個方法的時候當(dāng)前對象被鎖定起來。
Java中的每個對象都有一個lock偿凭,當(dāng)訪問某個對象的synchronized方法時产弹,該對象就 會被上鎖(注意,是對象弯囊,不是方法痰哨,假如你在這個類中定義了多個方法,如果你的線程訪問到了其中的任意一個synchronized方法匾嘱,那么其它的就暫 時不能被訪問了斤斧,必須等到該對象被解鎖以后,即方法執(zhí)行結(jié)束才行)霎烙。解鎖的意思是值線程執(zhí)行該方法完畢折欠,或者說過程中拋出了異常。
再換一種說法吼过,就是,一個類中有synchronized方法咪奖,如果該類的對象的該方法被訪問時盗忱,那么整個該對象都被鎖定了,但是這個意思是其它非synchronized方法和成員變量還 是可以被訪問羊赵,注意區(qū)分這一點趟佃。因為synchronized方法會鎖定對象扇谣,所以一旦有一個synchronized方法被某個線程啟動了,那么對象已 經(jīng)被獨占了闲昭,其它的synchronized方法就不能再同時獨占對象了罐寨,但是普通方法和成員變量并不獨占對象,所以仍然可以被調(diào)用序矩。
需要注意的是鸯绿,如果同步方法里面有sleep方法,它仍然是同步方法的一部分簸淀,在它被執(zhí)行的過程中瓶蝴,鎖仍然不會被解開。
其實同步的意思就是上鎖租幕,同步方法舷手,進而達到對象上鎖的目的。假如有一個數(shù)據(jù)庫劲绪,有讀和修改2個方法男窟,你可以允許多個線程同時讀,但是你不能讓多個線程同時改贾富,所以說改的方法要同步歉眷,讀的方法不需要。(其實這里我更加覺得應(yīng)該同步的不是方法祷安,而是數(shù)據(jù)本身姥芥,只要有對象訪問對象,對象就應(yīng)該被鎖定汇鞭,避免讀的時候有對象要修改凉唐,修改的時候有對象要讀,甚至是多個對象同時都想改)霍骄。還有台囱,如果2個方法都修改了同一個值的話,那么2個方法都應(yīng)該加同步读整。
線程同步我覺得是這樣的簿训,你說概念吧,也還不難理解米间,我覺得真正難的是實際中的應(yīng)用强品,你必須考慮很多相關(guān)的問題,哪一個方法要同步屈糊,都需要好好琢磨的榛。
在這里說3個方法,wait逻锐,notify夫晌,notifyAll雕薪。
之前說過一個方法叫做sleep,通常來說你按照自己的經(jīng)驗和感覺要求線程睡眠一定的時間晓淀。但是所袁,有時候當(dāng)你不知道需要線程睡眠多久的時候,sleep方 法就不行了凶掰,必須使用wait燥爷。但是記住,wait只能用于同步方法锄俄。用法大概可以這樣局劲,比如你可以先進行一個while判斷(不推薦用if,假如有exception發(fā)生的話奶赠,就不再判斷直接執(zhí)行后面的鱼填,這樣可能還是有問題,所以最好用while毅戈,即使exception發(fā)生了苹丸,仍然會進行判斷),如果滿足一定條件就this.wait苇经,然后不滿足了就this.notify赘理。如果有很多同步方法的話,那么也可以使用notifyAll方法扇单,那么在這個對象上面等著的線程都會被叫醒商模。
synchronized關(guān)鍵字囊括了所有和同步有關(guān)的東西。除此之外蜘澜,還有一個關(guān)鍵字volatile施流,它只能用來同步基本類型的成員變量。數(shù)據(jù)的寫入 通常來說是通過緩存寫入內(nèi)存的鄙信,使用volatile的原理就是它會繞過緩存瞪醋,直接寫入內(nèi)存。讀取數(shù)據(jù)的時候同樣也直接從內(nèi)存讀取装诡,這樣就可以有效地避免 數(shù)據(jù)不同步的情況银受。
同步的幾個準則:
a.首先,盡量使得synchronized塊保持簡短鸦采。你鎖的東西越多宾巍,越可能造成死鎖。
b.不要在synchronized塊中調(diào)用那些可能引起阻塞的方法渔伯,比如read顶霞。
c.如果持有了鎖的話,不要對其它對象調(diào)用方法咱旱。
2.線程死鎖
既然可以上鎖确丢,那么假如有2個線程,一個線程想先鎖對象1吐限,再鎖對象2鲜侥,恰好另外有一個線程先鎖對象2,再鎖對象1诸典。
在這個過程中描函,當(dāng)線程1把對象1鎖好以后,就想去鎖對象2狐粱,但是不巧舀寓,線程2已經(jīng)把對象2鎖上了,也正在嘗試去鎖對象1肌蜻。
什么時候結(jié)束呢互墓,只有線程1把2個對象都鎖上并把方法執(zhí)行完,并且線程2把2個對象也都鎖上并且把方法執(zhí)行完畢蒋搜,那么就結(jié)束了篡撵,但是,誰都不肯放掉已經(jīng)鎖上的對象豆挽,所以就沒有結(jié)果育谬,這種情況就叫做線程死鎖。
其中一個解決方法就是加大鎖定的粒度帮哈,也就是盡量鎖大的對象膛檀,不要鎖得太小,還有盡量不要同時鎖2個或2個以上的對象娘侍,但是還有待于進一步研究咖刃。
3.wait和notify和notifyAll
主要是用來讓線程之間互相通知事件的發(fā)生。
1).wait
Object類中的final方法私蕾,有InterruptedException僵缺。它的作用是導(dǎo)致當(dāng)前的線程等待,直到其它線程調(diào)用此對象的notify方法或者notifyAll方法踩叭,wait還有一些重用方法磕潮,傳參數(shù),比如說時間長度容贝。
當(dāng)前的線程必須擁有此對象監(jiān)視器自脯,然后該線程發(fā)布對此監(jiān)視器的所有權(quán)并且開始等待,直到其它線程通過調(diào)用notify方法或者notifyAll方法斤富,通知在此對象的監(jiān)視器上等待的線程醒來膏潮,然后該線程將等到重新獲得對監(jiān)視器的所有權(quán)后才能開始執(zhí)行。
說說wait和sleep的區(qū)別
首先sleep
sleep是Thread里面的方法满力,在被執(zhí)行的時候焕参,鎖并不會被交出去轻纪,要直到sleep所在的方法全部被執(zhí)行完畢以后才交出鎖。
wait是Object里面的方法叠纷,在被執(zhí)行的時候刻帚,鎖被解除,由其它線程去爭奪涩嚣,直到有notify或者notifyAll方法喚醒它崇众。
2).Notify
也是Object類中的方法,用于喚醒在此對象上等待著的某一個線程航厚,如果有很多線程掛起的話顷歌,就隨機地決定哪一個。注意幔睬,是隨機的眯漩,這時可以用notifyAll來喚醒所有的。一定要注意這個問題溪窒,除非你明確地知道你在做什么坤塞,否則最好就是用notifyAll。
注意事項:
wait()和notify()必須包括在synchronized代碼塊中澈蚌,等待中的線程必須由notify()方法顯 式地喚醒摹芙,否則它會永遠地等待下去。很多人初級接觸多線程時宛瞄,會習(xí)慣把wait()和notify()放在run()方法里浮禾,一定要謹記,這兩個方法屬于 某個對象份汗,應(yīng)在對象所在的類方法中定義它盈电,然后run中去調(diào)用它。