Java線程之同步化(Synchronized)
1.? 如果一個對象所持有的數(shù)據(jù)可以被多線程同時共享存取,必須考慮到數(shù)據(jù)同步的問題。所謂數(shù)據(jù)同步指的是兩份數(shù)據(jù)的整體性和一致性。數(shù)據(jù)在多線程下共享時容易由于同時多個線程可能更新同一個對象的信息,而造成對象數(shù)據(jù)的不同步,因為數(shù)據(jù)的不同步可能引發(fā)的錯誤通常不易察覺,而且可能是在程序執(zhí)行了幾千幾萬次之后,才會發(fā)生錯誤。這通常會發(fā)生在產(chǎn)品已經(jīng)上線之后,甚至是程序已經(jīng)執(zhí)行了幾年之后。
2. 舉個簡單的例子,設(shè)計了一個PersonalInfo類:???
- package ?ysu.hxy; ??
- ??
- public ? class ?PersonalInfo? ??
- { ??
- ???? private ?String?name; ??
- ???? private ?String?id; ??
- ???? private ? int ?count; ??
- ??
- ???? public ?PersonalInfo() ??
- ????{ ??
- ????????name?=? "nobody" ; ??
- ????????id?=? "N/A" ; ??
- ????} ??
- ??
- ???? public ? void ?setNameAndID(String?name,String?id) ??
- ????{ ??
- ???????? this .name?=?name; ??
- ???????? this .id?=?id; ??
- ???????? if (!checkNameAndIDEqual()) ??
- ????????{ ??
- ?????????????System.out.println(count?+? ")?illegal?name?or?ID..." ); ??
- ????????} ??
- ????????count?++; ??
- ????} ??
- ??
- ???? private ? boolean ?checkNameAndIDEqual(){ ??
- ???????? return ?(name.charAt( 0 )?==?id.charAt( 0 ))??? true : false ; ??
- ????} ??
- }??
?單就這個類本身而言,它并沒有任何的錯誤,但如果它被用于多線程的程序中,而且同一個對象被多個線程存取時,就會有可能發(fā)生錯誤。下面是一個簡單的測試程序,看看PersonalInfo類在多線程共享數(shù)據(jù)下會發(fā)生什么問題。
- package ?ysu.hxy; ??
- ??
- public ? class ?PersonalInfoTest? ??
- { ??
- ???? public ? static ? void ?main(String[]?args)? ??
- ????{ ??
- ???????? final ?PersonalInfo?person?=? new ?PersonalInfo(); ??
- ??
- ???????? //假設(shè)會能兩個線程可能更新person對象? ??
- ????????Thread?thread1?=? new ?Thread( new ?Runnable()?{ ??
- ???????????? public ? void ?run()?{ ??
- ???????????????? while ( true ){ ??
- ????????????????????person.setNameAndID( "Justin?Lin" , "J.L" ); ??
- ????????????????} ??
- ????????????} ??
- ????????}); ??
- ??
- ????????Thread?thread2?=? new ?Thread( new ?Runnable()?{ ??
- ???????????? public ? void ?run()?{ ??
- ???????????????? while ( true ){ ??
- ????????????????????person.setNameAndID( "Shang?Hwang?Lin" , "S.H" ); ??
- ????????????????} ??
- ????????????} ??
- ????????}); ??
- ??
- ????????System.out.println(); ??
- ??
- ????????thread1.start(); ??
- ????????thread2.start(); ??
- ????} ??
- }??
?
執(zhí)行結(jié)果:
D:\hxy>java ysu.hxy.PersonalInfoTest
開始測試...
23466451) illegal name
or ID...
78044494) illegal name or ID...
101630476) illegal name or
ID...
106496643) illegal name or ID...
145330181) illegal name or
ID...
169674022) illegal name or ID...
174072203) illegal name or
ID...
214717201) illegal name or ID...
219668799) illegal name or
ID...
240921750) illegal name or ID...
265875722) illegal name or
ID...
270920923) illegal name or ID...
281256783) illegal name or
ID...
這個程序出現(xiàn)了錯誤,在23466451次的setNameAndID()執(zhí)行時就開始了。如果程序完成并開始應(yīng)用于實際場合之后,這個時間點可能是幾個月甚至是幾年之后。問題出在這里:
- public ? void ?setNameAndID(String?name,String?id) ??
- ????{ ??
- ???????? this .name?=?name; ??
- ???????? this .id?=?id; ??
- ???????? if (!checkNameAndIDEqual()) ??
- ????????{ ??
- ?????????????System.out.println(count?+? ")?illegal?name?or?ID..." ); ??
- ????????} ??
- ????????count?++; ??
- ????}??
????? 雖然傳遞給setNameAndID()的變量并沒有問題,在某個時間點時,thread1設(shè)定了Justin Lin、J.L給name和id,在進行if測試的前一刻,thread2可能此時剛好調(diào)用setNameAndID("Shang Hwang","S.H")。在name被設(shè)定為Shang HWang時,checkNameAndIDEqual()開始執(zhí)行,此時name等于Shang HWang,而id還是J.L。所以,checkNameAndIDEqual()就會返回false,結(jié)果就顯示了錯誤信息。
?????? 必須同步數(shù)據(jù)對對象的更新,方法在有一個線程正在設(shè)定person對象的數(shù)據(jù)時,不可以被另一個線程同時進行設(shè)定。可以使用synchronized關(guān)鍵詞來進行這個動作。
- public ? synchronized ? void ?setNameAndID(String?name,String?id) ??
- ????{ ??
- ???????? this .name?=?name; ??
- ???????? this .id?=?id; ??
- ???????? if (!checkNameAndIDEqual()) ??
- ????????{ ??
- ?????????????System.out.println(count?+? ")?illegal?name?or?ID..." ); ??
- ????????} ??
- ????????count?++; ??
- ????}??
?這是synchronized關(guān)鍵詞的一個使用方式,用于方法上讓方法的范圍內(nèi)都成為被同步化區(qū)域。被同步化區(qū)域在有一個線程占據(jù)時就像一個禁區(qū),不允許其他線程進入。由于同時間只能有一個線程在被同步化區(qū)域,所以更新共享數(shù)據(jù)時,就像單線程程序在更新數(shù)據(jù)一樣,以保證對象中的數(shù)據(jù)會與給定的數(shù)據(jù)同步。
? sychronized的設(shè)定不只可用于方法上,也可以用于限定某個程序區(qū)塊上被同步化區(qū)域。例如:?
- public ? void ?setNameAndID(String?name,String?id) ??
- ??{? //同步某個程序區(qū)塊 ??
- ??????? synchronized ( this ) ??
- ???{ ??
- ??????? this .name?=?name; ??
- ??????? this .id?=?id; ??
- ??????? if (!checkNameAndIDEqual()) ??
- ??????????{ ??
- ???????????????System.out.println(count+ ")?illegal?name?or?ID..." ); ??
- ??????????} ??
- ???} ??
- }??
?? 這個程序片段的意思是,在線程執(zhí)行到synchronized設(shè)定的被同步化區(qū)塊時鎖定當(dāng)前對象,這樣就沒有其他線程可以來執(zhí)行這個被同步化區(qū)塊。這個方式可以應(yīng)用于您不想鎖定整個方法區(qū)塊,而只是想在更新共享數(shù)據(jù)時再確保對象與數(shù)據(jù)的同步化。由于只鎖定方法中的某個區(qū)塊,在執(zhí)行完區(qū)塊后即釋放對對象的鎖定,以便讓其他線程能有機會對對象進行操作,相對于鎖定整個方法區(qū)塊效率較高。
?? 也可以標(biāo)示某個對象要求同步化。例如在多線程中存取同一個ArrayList對象時,由于ArrayList并沒有實現(xiàn)數(shù)據(jù)存取時的同步化,所以當(dāng)它使用多線程環(huán)境時,必須注意多個線程存取同一個ArrayList時,有可能發(fā)生兩個以上的線程將數(shù)據(jù)存入ArrayList的同一個位置,造成數(shù)據(jù)的相互覆蓋。為了確保數(shù)據(jù)存入時的正確性,可以在存取ArrayList對象時要求同步化。例如:
- //arraylist參考至一個ArrayList的一個實例 ??
- synchronized (arraylist) ??
- { ??
- ?????arrayList.add( new ?SomeClass()); ??
- }??
?同步化確保數(shù)據(jù)的同步,但所犧牲的就是在于一個線程占據(jù)同步化區(qū)塊,而其他線程等待它釋放區(qū)塊執(zhí)行權(quán)時的延遲。這在線程少時可能看不出來,但在線程多的環(huán)境中必然造成一定的效率問題(例如大型網(wǎng)站的多人聯(lián)機時)。
posted on 2009-10-14 11:52 李云澤 閱讀(452) 評論(0) 編輯 收藏 所屬分類: 面試筆試相關(guān)的