<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html><head> <link rel="stylesheet" href="css/stdlayout.css" type="text/css"> <link rel="stylesheet" href="css/print.css" type="text/css"> <meta content="text/html; charset=Big5" http-equiv="content-type"> <title>執行緒的同步化</title></head><body><h3><a href="http://caterpillar.onlyfun.net/Gossip/index.html">FromGossip@caterpillar</a></h3><h1><a href="JavaGossip2.htm">Java Gossip: 執行緒的同步化</a></h1><table style="text-align: left; width: 946px; height: 32px;" border="0" cellpadding="0" cellspacing="0"> <tbody> <tr> <td style="width: 676px; vertical-align: top;"> <small>如果您的程式只是一個單執行緒,單一流程的程式,那麼通常您只要注意到程式邏輯的正確,您的程式通常就可以正確的執行您想要的功能,但當您的程式是多執行緒程式,多流程同時執行時,那麼您就要注意到更多的細節,例如在多執行緒共用同一物件的資料時。<br> <br>如果一個物件所持有的資料可以被多執行緒同時共享存取時,您必須考慮到<span style="font-weight: bold;">「資料同步」</span>的問題,所謂資料同步指的是兩份資料的整體性一致,例如物件A有name與id兩個屬性,而有一份A1資料有name與id的資料要更新物件A的屬性,如果A1的name與id設定給A物件完成,則稱A1與A同步,如果A1資料在更新了物件的name屬性時,突然插入了一份A2資料更新了A物件的id屬性,則顯然的A1資料與A就不同步,A2資料與A也不同步。<br> <br>資料在多執行緒下共享時,就容易因為同時多個執行緒可能更新同一個物件的資訊,而造成物件資料的不同步,因為資料的不同步而可能引發的錯誤通常不易察覺,而且可能是在您程式執行了幾千幾萬次之後,才會發生錯誤,而這通常會發生在您的產品已經上線之後,甚至是程式已經執行了幾年之後。<br> <br>這邊舉個簡單的例子,考慮您設計這麼一個類別:</small> <br> <ul> <li> PersonalInfo.java </li> </ul> <pre>package onlyfun.caterpillar;<br> <br>public class PersonalInfo {<br> private String name; <br> private String id; <br> private int count; <br> <br> public PersonalInfo() { <br> name = "nobody"; <br> id = "N/A"; <br> } <br> <br> public void setNameAndID(String name, String id) { <br> this.name = name; <br> this.id = id; <br> if(!checkNameAndIDEqual()) {<br> System.out.println(count + <br> ") illegal name or ID.....");<br> } <br> count++; <br> } <br> <br> private boolean checkNameAndIDEqual() { <br> return (name.charAt(0) == id.charAt(0)) ? <br> true : false; <br> } <br>} <br></pre> <br> <small>在這個類別中,您可以設定使用者的名稱與縮寫id,並簡單檢查一下名稱與id的第一個字是否相同,單就這個類別本身而言,它並沒有任何的錯誤,但如果它被用於多執行緒的程式中,而且同一個物件被多個執行存取時,就會"有可能"發生錯誤,來寫個簡單的測試程式: </small><br> <ul> <li> SynchronizedDemo.java </li> </ul> <pre>package onlyfun.caterpillar;<br> <br>public class SynchronizedDemo {<br> public static void main(String[] args) {<br> final PersonalInfo person = new PersonalInfo(); <br> Thread thread1 = new Thread(new Runnable() { <br> public void run() { <br> while(true) <br> person.setNameAndID("Justin Lin", "J.L"); <br> } <br> }); <br> <br> Thread thread2 = new Thread(new Runnable() { <br> public void run() { <br> while(true) <br> person.setNameAndID("Shang Hwang", "S.H"); <br> } <br> }); <br> <br> System.out.println("Start testing....."); <br> <br> thread1.start(); <br> thread2.start();<br> }<br>} <br></pre> <br> <small>來看一下執行時的一個例子:</small> <table style="text-align: left; width: 100%;" border="0" cellpadding="2" cellspacing="2"> <tbody> <tr> <td style="background-color: rgb(0, 0, 0);"><small><span style="color: rgb(255, 255, 255);">Start testing..... <br>822949) illegal name or ID..... <br>1443074) illegal name or ID..... <br>1750512) illegal name or ID..... <br>2587632) illegal name or ID..... <br>2805877) illegal name or ID..... <br>3705555) illegal name or ID..... <br>4000077) illegal name or ID..... </span></small><span style="color: rgb(255, 255, 255);"><br> </span></td> </tr> </tbody> </table> <br> <small>看到了嗎?如果以單執行緒的觀點來看,上面的訊息在測試中根本不可能出現,然而在這個程式中卻出現了錯誤,而且重點是,第一次錯誤是發生在第822949 次的設定(您的電腦上可能是不同的數字),如果您在程式完成並開始應用之後,這個時間點可能是幾個月甚至幾年之後。<br> <br>問題出現哪?在於這邊:<br> </small> <div style="margin-left: 40px;"><small><span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> public void setNameAndID(String name, String id) { </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> this.name = name; </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> this.id = id; </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> if(!checkNameAndIDEqual()) {</span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> System.out.println(count + </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> ") illegal name or ID.....");</span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> } </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> count++; </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> } </span><br> </small></div> <small> <br> <br>雖然您設定給它的參數並沒有問題,在某個時間點時,thread1設定了"Justin Lin","J.L"給name與id,在進行測試的前一刻,thread2可能此時剛好呼叫setNameAndID("Shang Hwang","S.H"),在name被設定為"Shang Hwang"時,checkNameAndIDEqual()開始執行,此時name等於"ShangHwang",而id還是"J.L",所以checkNameAndIDEqual()就會傳回false,結果就顯示了錯誤訊息。<br> <br>您必須同步資料對物件的更新,也就是在有一個執行緒正在設定person物件的資料時,不可以又被另一個執行緒同時進行設定,您可以使用<span style="font-weight: bold;">"synchronized"關鍵字</span>來進行這個動作。<br> <br>"synchronized"的一個使用方式是用於方法上,讓方法作用範圍內都成為被同步化區域,例如:<br> </small> <div style="margin-left: 40px;"><small><span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> public synchronized void setNameAndID(String name, </span><br style="font-weight: bold; font-family: Courier New,Courier,monospace;"> <span style="font-weight: bold; font-family: Courier New,Courier,monospace;"> &nbs