I學霸官方免費教程四十 :Java基礎教程之線程同步

來源:互聯網
上載者:User

標籤:java線程同步   同步方法   同步塊   wait方法   notifyall方法   notify方法   

線程的同步

指當多個線程使用同一對象中被同步的資源時,要根據“先來後到”的順序使用。
舉個例子:現在只有一台電腦,現在有兩個人A和B想玩遊戲,一個人C想寫代碼,一個人D想聽音樂。此時A、B、C三個人要搶這台電腦,誰先搶到誰用,用完了後面兩個人在接著搶,誰搶到誰用。而D則不用,在另外三個人中任意一個人正在使用的時候,都可以播放音樂給他聽;由此可以看出玩遊戲和寫代碼的功能(方法)是要有“先來後到”的順序的,而聽音樂這個功能不需要。所以玩遊戲和寫代碼的方法就是需要被同步的,而聽音樂就不需要同步
同步使用關鍵字synchronized實現
同步方法:[存取修飾詞]  synchronized  傳回值類型  方法名(){}
同步塊:synchronized(對象) { //代碼塊 }
同步塊和前面所提到的代碼塊一樣,只不過這塊代碼是被同步的。被同步的部分也按照“先來後到”的順序執行
在開發的過程中,應當盡量縮小同步代碼的範圍,因為多個線程執行時要有順序的執行,這樣會大大降低程式的運行效率


以下執行個體代碼中建立了一個Computer類中含有一個非同步的方法listenMusic,一個含有同步塊的非同步方法printer和兩個同步方法playGame和coding。
七個線程類ThreadA、ThreadB、ThreadC、ThreadD、ThreadE、ThreadF、ThreadG;其中ThreadA和ThreadB中都執行了非同步的方法listenMusic;ThreadC和ThreadD中都執行了同步方法playGame;ThreadE中執行了同步方法coding;ThreadF中ThreadG都執行了含有同步塊的非同步方法printer;
五個測試類別TestAB、TestBC、TestCD、TestDE、TestEF、TestFG;根據測試類別的類名最後兩個字母,測試對象的兩個線程;例如TestAB測試ThreadA和ThreadB

執行個體:package thread.synchronize;/** * 建立Computer類 * 其中包含同步方法,非同步方法和含有同步塊的非同步方法 * @author 學霸聯盟 - 趙燦 */public class Computer {/** * 非同步方法;功能:聽音樂 * @param threadTag:線程標記,用於標識哪個線程正在執行這個方法 */public void listenMusic(String threadTag) {System.out.println(threadTag + "-聽音樂開始");try {//此處休眠1秒是為了類比編寫代碼消耗的時間Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadTag + "-聽音樂結束");}/** * 含有同步塊的非同步方法;功能:列印材料 * @param threadTag:線程標記,用於標識哪個線程正在執行這個方法 */public void printer(String threadTag) {System.out.println(threadTag + "-準備材料開始");try {//此處休眠1秒是為了類比準備材料消耗的時間Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadTag + "-準備材料結束");//同步塊:獲得當前對象的鎖synchronized(this){System.out.println(threadTag + "-列印材料開始");try {//此處休眠1秒是為了類比列印材料消耗的時間Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadTag + "-列印材料結束");}}/** * 同步方法;功能:玩遊戲 * @param threadTag:線程標記,用於標識哪個線程正在執行這個方法 */public synchronized void playGame(String threadTag) {System.out.println(threadTag + "-玩遊戲開始");try {//此處休眠3秒是為了類比玩遊戲消耗的時間Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadTag + "-玩遊戲結束");}/** * 同步方法;功能:編寫代碼 * @param threadTag:線程標記,用於標識哪個線程正在執行這個方法 */public synchronized void coding(String threadTag) {System.out.println(threadTag + "-編寫代碼開始");try {//此處休眠3秒是為了類比編寫代碼消耗的時間Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(threadTag + "-編寫代碼結束");}}package thread.synchronize;/** * 建立ThreadA類 * 用於執行Computer對象中的非同步方法listenMusic * @author 學霸聯盟 - 趙燦 */public class ThreadA extends Thread {/* * 聲明成員變數pc * 目的是因為在run方法中需要使用外部傳進來的Computer對象 * 但run方法中無法使用構造方法中的局部變數localPC * 所以此處聲明一個成員變數pc用於接收外部傳入的對象 * 這樣以來run方法中,便可以通過成員變數pc可以使用外部傳入的對象了 */private Computer pc;/* * 此處的localPC會接收到外部傳入的Computer類型的對象 * 並將其傳遞給成員變數pc */public ThreadA(Computer localPC) {pc = localPC;}@Overridepublic void run() {//使用pc完成聽音樂的更能,即使用pc調用listenMusic方法pc.listenMusic("ThreadA");}}package thread.synchronize;/** * 建立ThreadB類:作用和代碼同ThreadA * @author 學霸聯盟 - 趙燦 */public class ThreadB extends Thread {private Computer pc;public ThreadB(Computer localPC) {pc = localPC;}@Overridepublic void run() {pc.listenMusic("ThreadB");}}package thread.synchronize;/** * 建立測試類別TestAB * 用於測試多個線程同時調用同一對象的同一個非同步方法 * 結果將是多個線程同時執行非同步方法 * @author 學霸聯盟 - 趙燦 */public  class  TestAB {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadA和ThreadB中都執行了pc的非同步方法listenMusicThreadA  ta = new  ThreadA(pc);ThreadB  tb = new  ThreadB(pc);//啟動線程ta和tbta.start();tb.start();}}運行結果:ThreadA-聽音樂開始ThreadB-聽音樂開始ThreadB-聽音樂結束ThreadA-聽音樂結束
package thread.synchronize;/** * 建立ThreadC類:代碼基本和ThreadA相同 * @author 學霸聯盟 - 趙燦 */public class ThreadC extends Thread {private Computer pc;public ThreadC(Computer localPC) {pc = localPC;}@Overridepublic void run() {//調用pc的同步方法playGamepc.playGame("ThreadC");}}package thread.synchronize;/** * 建立測試類別TestBC * 用於測試兩個線程同時調用同一對象的非同步方法和同步方法 * 結果是執行非同步方法和同步方法的兩個線程可以同時執行,互不影響 * @author 學霸聯盟 - 趙燦 */public  class  TestBC {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadB執行了pc的非同步方法listenMusicThreadB  tb = new  ThreadB(pc);//ThreadC執行了pc的同步方法playGameThreadC  tc = new  ThreadC(pc);tb.start();tc.start();}}運行結果:ThreadC-玩遊戲開始ThreadB-聽音樂開始ThreadB-聽音樂結束ThreadC-玩遊戲結束
package thread.synchronize;/** * 建立ThreadD類:代碼和功能同ThreadC * @author 學霸聯盟 - 趙燦 */public class ThreadD extends Thread {private Computer pc;public ThreadD(Computer localPC) {pc = localPC;}@Overridepublic void run() {//調用pc的同步方法playGamepc.playGame("ThreadD");}}package thread.synchronize;/** * 建立測試類別TestCD * 用於測試多個線程同時調用同一對象的同一個同步方法 * 結果是某一線程先獲得對象的鎖的執行完後,第二個線程才能執行 * @author 學霸聯盟 - 趙燦 */public  class  TestCD {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadC和ThreadD中都執行了pc的同步方法playGameThreadC  tc = new  ThreadC(pc);ThreadD  td = new  ThreadD(pc);//啟動線程tc和tdtc.start();td.start();}}運行結果:ThreadC-玩遊戲開始ThreadC-玩遊戲結束ThreadD-玩遊戲開始ThreadD-玩遊戲結束
package thread.synchronize;/** * 建立ThreadE類 * @author 學霸聯盟 - 趙燦 */public class ThreadE extends Thread {private Computer pc;public ThreadE(Computer localPC) {pc = localPC;}@Overridepublic void run() {//調用pc的同步方法codingpc.coding("ThreadE");}}package thread.synchronize;/** * 建立測試類別TestDE * 用於測試多個線程同時調用同一對象的不同的同步方法 * 結果也是某一線程先獲得對象的鎖的執行完後,第二個線程才能執行 * @author 學霸聯盟 - 趙燦 */public  class  TestDE {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadD執行了pc的同步方法playGameThreadD  td = new  ThreadD(pc);//ThreadE中執行了pc的同步方法codingThreadE  te = new  ThreadE(pc);td.start();te.start();}}運行結果:ThreadD-玩遊戲開始ThreadD-玩遊戲結束ThreadE-編寫代碼開始ThreadE-編寫代碼結束
package thread.synchronize;/** * 建立ThreadF類 * @author 學霸聯盟 - 趙燦 */public class ThreadF extends Thread {private Computer pc;public ThreadF(Computer localPC) {pc = localPC;}@Overridepublic void run() {//調用pc的含同步塊的方法printerpc.printer("ThreadF");}}package thread.synchronize;/** * 建立測試類別TestEF * 用於測試多個線程同時調用同一對象的同步方法和含有同步塊的非同步方法 * 結果將是被同步的部分有先後,非同步部分代碼同時執行 * @author 學霸聯盟 - 趙燦 */public  class  TestEF {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadE中執行了pc的同步方法codingThreadE  te = new  ThreadE(pc);//ThreadF執行了pc的含同步塊的非同步方法printerThreadF  tf = new  ThreadF(pc);te.start();tf.start();}}運行結果:ThreadF-準備材料開始ThreadE-編寫代碼開始ThreadF-準備材料結束ThreadE-編寫代碼結束ThreadF-列印材料開始ThreadF-列印材料結束
package thread.synchronize;/** * 建立ThreadG類 * @author 學霸聯盟 - 趙燦 */public class ThreadG extends Thread {private Computer pc;public ThreadG(Computer localPC) {pc = localPC;}@Overridepublic void run() {//調用pc的含同步塊的方法printerpc.printer("ThreadG");}}package thread.synchronize;/** * 建立測試類別TestFG * 用於測試多個線程同時調用同一對象中同一個含有同步塊的非同步方法 * 結果將是被同步的部分有先後,非同步部分代碼同時執行 * @author 學霸聯盟 - 趙燦 */public  class  TestFG {public  static  void  main(String[] args) {/** * 為了保證只有一台電腦(即同一個對象)供多個線程訪問 * 所以只在此處建立一個Computer對象作為實參傳遞給各個線程 */Computer  pc = new  Computer();//ThreadF和ThreadG都執行了pc的含同步塊的非同步方法printerThreadF  tf = new  ThreadF(pc);ThreadG  tg = new  ThreadG(pc);tf.start();tg.start();}}運行結果:ThreadF-準備材料開始ThreadG-準備材料開始ThreadG-準備材料結束ThreadG-列印材料開始ThreadF-準備材料結束ThreadG-列印材料結束ThreadF-列印材料開始ThreadF-列印材料結束


wait方法和notify方法/notifyAll方法

wait([int][,int]); Object類中的方法,線上程A中使用對象o調用wait方法;會阻塞線程A;
如果沒有傳入時間參數,直到在其他地方調用對象o的notify/notifyAll時才會恢複;
如果傳入long類型的時間參數,指定時間結束後自動回復
而且線程A必須先獲得對象o的鎖,否則會出現異常。

執行個體:package thread.wait;/*** 建立WaitDemo類* 用於測試wait、notify、notifyAll方法* @author 學霸聯盟 - 趙燦*/public class WaitDemo {public static void main(String[] args) {/* * 在這裡建立一個唯一的Object對象onlyObject * 用於傳遞給以下三個線程使用,是為了保證三個線程使用的是同一對象 */Object onlyObject = new Object();/* * 建立三個線程對象ta、tb、tc * onlyObject會被賦值給每個線程的構造方法中的變數localObject * 相當於localObject = onlyObject; */ThreadA ta = new ThreadA(onlyObject);ThreadB tb = new ThreadB(onlyObject);ThreadC tc = new ThreadC(onlyObject);//先啟動線程ta和tbta.start();tb.start();try {/* * 此處執行Thread.sleep(100);會是主線程休眠100毫秒 * 100毫秒後才會執行tc.start();啟動線程tc * 目的是為了保證線程ta和tb中的wait方法先執行,阻塞住線程ta和tb * 然後在執行線程tc中的notify()方法是喚醒操作 * 否則如果先執行了喚醒操作,而後執行阻塞操作,將看不到喚醒的效果 */Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//啟動線程tctc.start();}}/*** 建立ThreadA類* 用於執行wait方法;阻塞操作* @author 學霸聯盟 - 趙燦*/class ThreadA extends Thread {/* * 聲明成員變數obj * 目的是因為在run方法中需要使用外部傳進來的Object對象 * 但run方法中無法使用構造方法中的局部變數localObject * 所以此處聲明一個成員變數obj用於接收外部傳入的對象 * 這樣以來run方法中,便可以通過成員變數obj可以使用外部傳入的對象了 */private Object obj;/* * 此處的localObject會接收到外部傳入的Object類型的對象 * 並將其傳遞給成員變數obj */public ThreadA(Object localObject) {//賦值給成員變數objthis.obj = localObject;}@Overridepublic void run() {System.out.println("ThreadA--執行開始");/* * 調用wait方法時,必須使用同步的方式擷取調用wait方法對象的鎖 * 否則會出現IllegalMonitorStateException異常 * 本例中,此處使用的是obj調用wait(obj.wait()) * 所以同步的也應該是obj(synchronized(obj) ) */synchronized (obj) {try {System.out.println("ThreadA--被阻塞");/* * 調用對象obj的wait方法,阻塞的將是執行這句代碼的線程 * 本例中是執行此處代碼的是線程ta,所以被阻塞的就是線程ta */obj.wait();System.out.println("ThreadA--被喚醒");} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("ThreadA--執行結束");}}/*** 建立ThreadB類* 用於執行wait方法;阻塞操作* 其中的代碼和ThreadA相同,就不再一一寫注釋了* @author 學霸聯盟 - 趙燦*/class ThreadB extends Thread {private Object obj;public ThreadB(Object localObject) {this.obj = localObject;}@Overridepublic void run() {synchronized (obj) {System.out.println("ThreadB--執行開始");synchronized (obj) {try {System.out.println("ThreadB--被阻塞");/* * 執行obj.wait(),阻塞的將是執行這句代碼的線程 * 本例中是執行此處代碼的是線程tb,所以被阻塞就是線程tb */obj.wait();System.out.println("ThreadB--被喚醒");} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("ThreadB--執行結束");}}}/*** 建立ThreadC類* 用於執行notify方法;喚醒操作* @author 學霸聯盟 - 趙燦*/class ThreadC extends Thread {private Object obj;public ThreadC(Object localObject) {this.obj = localObject;}@Overridepublic void run() {/* * 調用notify方法時,必須使用同步的方式擷取調用notify方法對象的鎖 * 否則會出現IllegalMonitorStateException異常 * 本例中,此處使用的是obj調用notify(obj.notify()) * 所以同步的也應該是obj(synchronized(obj) ) */synchronized (obj) {System.out.println("ThreadC--喚醒最先被obj對象阻塞的線程");/* * 執行obj.notify();將喚醒第一個被obj阻塞的線程 * 如果執行obj.notifyAll();所有被obj阻塞的線程都會被喚醒 */obj.notify();}}}運行結果:ThreadA--執行開始ThreadB--執行開始ThreadB--被阻塞ThreadA--被阻塞ThreadC--喚醒最先被obj對象阻塞的線程ThreadB--被喚醒ThreadB--執行結束


總結:線程同步是為了在多線程的情況下保護資料在操作過程中的安全性,但是這樣做極大的影響了軟體的執行效率,所以在使用同步的過程中,應儘可能的縮小同步範圍,從而減少同步對效能的影響。

本文出自 “學霸聯盟教育官方部落格” 部落格,轉載請與作者聯絡!

I學霸官方免費教程四十 :Java基礎教程之線程同步

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.