Java线程详解(8)-线程的同步
Java線程:線程的同步-同步方法
? ? ? ? 線程的同步是保證多線程安全訪問競爭資源的一種手段。
????????線程的同步是Java多線程編程的難點,往往開發者搞不清楚什么是競爭資源、什么時候需要考慮同步,怎么同步等等問題,當然,這些問題沒有很明確的答案,但有些原則問題需要考慮,是否有競爭資源被同時改動的問題?
????????在本部分之前,請參閱《Java線程:線程的同步與鎖》部分,本部分是在此基礎上所寫的。
????????對于同步,在具體的Java代碼中需要完成一下兩個操作:
????????把競爭訪問的資源標識為private;
????????同步哪些修改變量的代碼,使用synchronized關鍵字同步方法或代碼。
????????當然這不是唯一控制并發安全的途徑。
????????synchronized關鍵字使用說明
????????synchronized只能標記非抽象的方法,不能標識成員變量。
????????為了演示同步方法的使用,構建了一個信用卡賬戶,起初信用額為100w,然后模擬透支、存款等多個操作。顯然銀行賬戶User對象是個競爭資源,而多個并發操作的是賬戶方法oper(int x),當然應該在此方法上加上同步,并將賬戶的余額設為私有變量,禁止直接訪問。
/**?*?Java線程:線程的同步?*/?? public?class?Test?{??public?static?void?main(String[]?args)?{??User?u?=?new?User("張三",?100);??MyThread?t1?=?new?MyThread("線程A",?u,?20);??MyThread?t2?=?new?MyThread("線程B",?u,?-60);??MyThread?t3?=?new?MyThread("線程C",?u,?-80);??MyThread?t4?=?new?MyThread("線程D",?u,?-30);??MyThread?t5?=?new?MyThread("線程E",?u,?32);??MyThread?t6?=?new?MyThread("線程F",?u,?21);??t1.start();??t2.start();??t3.start();??t4.start();??t5.start();??t6.start();??}?? }??class?MyThread?extends?Thread?{??private?User?u;??private?int?y?=?0;??MyThread(String?name,?User?u,?int?y)?{??super(name);??this.u?=?u;??this.y?=?y;??}??public?void?run()?{??u.oper(y);??}?? }??class?User?{??private?String?code;??private?int?cash;??User(String?code,?int?cash)?{??this.code?=?code;??this.cash?=?cash;??}??public?String?getCode()?{??return?code;??}??public?void?setCode(String?code)?{??this.code?=?code;??}??/**?*?業務方法?*?@param?x??添加x萬元?*/??public?synchronized?void?oper(int?x)?{??try?{??Thread.sleep(10L);??this.cash?+=?x;??System.out.println(Thread.currentThread().getName()?+?"運行結束,增加“"??+?x?+?"”,當前用戶賬戶余額為:"?+?cash);??Thread.sleep(10L);??}?catch?(InterruptedException?e)?{??e.printStackTrace();??}??}??@Override??public?String?toString()?{??return?"User{"?+?"code='"?+?code?+?'\''?+?",cash="?+?cash?+?'}';??}?? }??
????????執行結果:
????????反面教材,不同步的情況,也就是去掉oper(int x)方法的synchronized修飾符,然后運行程序,結果如下:
????????很顯然,上面的結果是錯誤的,導致錯誤的原因是多個線程并發訪問了競爭資源u,并對u的屬性做了改動。
????????可見同步的重要性。
????????注意:
????????通過前文可知,線程退出同步方法時將釋放掉方法所屬對象的鎖,但還應該注意的是,同步方法中還可以使用特定的方法對線程進行調度。這些方法來自于java.lang.Object類。
void?notify()??????喚醒在此對象監視器上等待的單個線程。?????? void?notifyAll()??????喚醒在此對象監視器上等待的所有線程。?????? void?wait()??????導致當前的線程等待,直到其他線程調用此對象的?notify()方法或?notifyAll()方法。?????? void?wait(long?timeout)??????導致當前的線程等待,直到其他線程調用此對象的?notify()方法或?notifyAll()方法,或者超過指定的時間量。?????? void?wait(long?timeout,int?nanos)??????導致當前的線程等待,直到其他線程調用此對象的?notify()方法或?notifyAll()方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量。??
????????結合以上方法,處理多線程同步與互斥問題非常重要,著名的生產者-消費者例子就是一個經典的例子,任何語言多線程必學的例子。
Java線程:線程的同步-同步塊
????????對于同步,除了同步方法外,還可以使用同步代碼塊,有時候同步代碼塊會帶來比同步方法更好的效果。
????????追其同步的根本的目的,是控制競爭資源的正確的訪問,因此只要在訪問競爭資源的時候保證同一時刻只能一個線程訪問即可,因此Java引入了同步代碼快的策略,以提高性能。
????????在上個例子的基礎上,對oper方法做了改動,由同步方法改為同步代碼塊模式,程序的執行邏輯并沒有問題。
/**?*?Java線程:線程的同步-同步代碼塊?*/?? public?class?Test?{??public?static?void?main(String[]?args)?{??User?u?=?new?User("張三",?100);??MyThread?t1?=?new?MyThread("線程A",?u,?20);??MyThread?t2?=?new?MyThread("線程B",?u,?-60);??MyThread?t3?=?new?MyThread("線程C",?u,?-80);??MyThread?t4?=?new?MyThread("線程D",?u,?-30);??MyThread?t5?=?new?MyThread("線程E",?u,?32);??MyThread?t6?=?new?MyThread("線程F",?u,?21);??t1.start();??t2.start();??t3.start();??t4.start();??t5.start();??t6.start();??}?? }??class?MyThread?extends?Thread{??private?User?u;??private?int?y?=?0;??MyThread(String?name,?User?u,?int?y)?{??super(name);??this.u?=?u;??this.y?=?y;??}??public?void?run()?{??u.oper(y);??}?? }??class?User?{??private?String?code;??private?int?cash;??User(String?code,?int?cash)?{??this.code?=?code;??this.cash?=?cash;??}??public?String?getCode()?{??return?code;??}??public?void?setCode(String?code)?{??this.code?=?code;??}??/**?*?業務方法?*?@param?x??添加x萬元?*/??public?void?oper(int?x)?{??try?{??Thread.sleep(10L);??synchronized?(this)?{??this.cash?+=?x;??System.out.println(Thread.currentThread().getName()?+?"運行結束,增加“"??+?x?+?"”,當前用戶賬戶余額為:"?+?cash);??}???????????Thread.sleep(10L);??}?catch?(InterruptedException?e)?{??e.printStackTrace();??}??}??@Override??public?String?toString()?{??return?"User{"?+?"code='"?+?code?+?'\''?+?",cash="?+?cash?+?'}';??}?? }??
????????執行結果:
????????注意:
????????在使用synchronized關鍵字時候,應該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因為synchronized程序塊占有著對象鎖,你休息那么其他的線程只能一邊等著你醒來執行完了才能執行。不但嚴重影響效率,也不合邏輯。
????????同樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,因為你占用著鎖,其他互斥線程還是無法訪問同步程序塊。當然與同步程序塊無關的線程可以獲得更多的執行時間。
?
總結
以上是生活随笔為你收集整理的Java线程详解(8)-线程的同步的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java线程详解(7)-线程的调度
- 下一篇: Java线程详解(9)-并发协作