1.?
如果一個對象所持有的數據可以被多線程同時共享存取,必須考慮到數據同步的問題。所謂數據同步指的是兩份數據的整體性和一致性。數據在多線程下共享時容易由于同時多個線程可能更新同一個對象的信息,而造成對象數據的不同步,因為數據的不同步可能引發的錯誤通常不易察覺,而且可能是在程序執行了幾千幾萬次之后,才會發生錯誤。這通常會發生在產品已經上線之后,甚至是程序已經執行了幾年之后。
2. 舉個簡單的例子,設計了一個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
;
??
-
????} ??
-
}??
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;
}
}
?單就這個類本身而言,它并沒有任何的錯誤,但如果它被用于多線程的程序中,而且同一個對象被多個線程存取時,就會有可能發生錯誤。下面是一個簡單的測試程序,看看PersonalInfo類在多線程共享數據下會發生什么問題。
-
package
?ysu.hxy;
??
-
??
-
public
?
class
?PersonalInfoTest? ??
-
{ ??
-
????
public
?
static
?
void
?main(String[]?args)? ??
-
????{ ??
-
????????
final
?PersonalInfo?person?=?
new
?PersonalInfo(); ??
-
??
-
????????
??
-
????????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(); ??
-
????} ??
-
}??
package ysu.hxy;
public class PersonalInfoTest
{
public static void main(String[] args)
{
final PersonalInfo person = new PersonalInfo();
//假設會能兩個線程可能更新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();
}
}
?
執行結果:
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...
這個程序出現了錯誤,在23466451次的setNameAndID()執行時就開始了。如果程序完成并開始應用于實際場合之后,這個時間點可能是幾個月甚至是幾年之后。問題出在這里:
-
public
?
void
?setNameAndID(String?name,String?id)
??
-
????{ ??
-
????????
this
.name?=?name;
??
-
????????
this
.id?=?id;
??
-
????????
if
(!checkNameAndIDEqual()) ??
-
????????{ ??
-
?????????????System.out.println(count?+?
")?illegal?name?or?ID..."
); ??
-
????????} ??
-
????????count?++; ??
-
????}??
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設定了Justin
Lin、J.L給name和id,在進行if測試的前一刻,thread2可能此時剛好調用setNameAndID("Shang
Hwang","S.H")。在name被設定為Shang HWang時,checkNameAndIDEqual()開始執行,此時name等于Shang
HWang,而id還是J.L。所以,checkNameAndIDEqual()就會返回false,結果就顯示了錯誤信息。
??????
必須同步數據對對象的更新,方法在有一個線程正在設定person對象的數據時,不可以被另一個線程同時進行設定。可以使用synchronized關鍵詞來進行這個動作。
-
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?++; ??
-
????}??
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關鍵詞的一個使用方式,用于方法上讓方法的范圍內都成為被同步化區域。被同步化區域在有一個線程占據時就像一個禁區,不允許其他線程進入。由于同時間只能有一個線程在被同步化區域,所以更新共享數據時,就像單線程程序在更新數據一樣,以保證對象中的數據會與給定的數據同步。
? sychronized的設定不只可用于方法上,也可以用于限定某個程序區塊上被同步化區域。例如:?
-
public
?
void
?setNameAndID(String?name,String?id)
??
-
??{?
??
-
???????
synchronized
(
this
) ??
-
???{ ??
-
???????
this
.name?=?name;
??
-
???????
this
.id?=?id;
??
-
???????
if
(!checkNameAndIDEqual()) ??
-
??????????{ ??
-
???????????????System.out.println(count+
")?illegal?name?or?ID..."
); ??
-
??????????} ??
-
???} ??
-
}??
public void setNameAndID(String name,String id)
{ //同步某個程序區塊
synchronized(this)
{
this.name = name;
this.id = id;
if(!checkNameAndIDEqual())
{
System.out.println(count+") illegal name or ID...");
}
}
}
??
這個程序片段的意思是,在線程執行到synchronized設定的被同步化區塊時鎖定當前對象,這樣就沒有其他線程可以來執行這個被同步化區塊。這個方式可以應用于您不想鎖定整個方法區塊,而只是想在更新共享數據時再確保對象與數據的同步化。由于只鎖定方法中的某個區塊,在執行完區塊后即釋放對對象的鎖定,以便讓其他線程能有機會對對象進行操作,相對于鎖定整個方法區塊效率較高。
?? 也可以標示某個對象要求同步化。例如在多線程中存取同一個ArrayList對象時,由于ArrayList并沒有實現數據存取時的同步化,所以當它使用多線程環境時,必須注意多個線程存取同一個ArrayList時,有可能發生兩個以上的線程將數據存入ArrayList的同一個位置,造成數據的相互覆蓋。為了確保數據存入時的正確性,可以在存取ArrayList對象時要求同步化。例如:
-
??
-
synchronized
(arraylist)
??
-
{ ??
-
?????arrayList.add(
new
?SomeClass()); ??
-
}??
//arraylist參考至一個ArrayList的一個實例
synchronized(arraylist)
{
arrayList.add(new SomeClass());
}
?同步化確保數據的同步,但所犧牲的就是在于一個線程占據同步化區塊,而其他線程等待它釋放區塊執行權時的延遲。這在線程少時可能看不出來,但在線程多的環境中必然造成一定的效率問題(例如大型網站的多人聯機時)。