一、線程安全 線程安全的概念:當多個線程訪問某一個類(對象或方法)時。這個類始終都能表現出正確的行為那麼這個類(對象或方法)就是線程安全的。 synchronized:可以在任何對象及方法上加鎖,而加鎖的這段代碼稱為“互斥區”或“臨界區” 示例:【com.study.base.thread.a_sy ...
一、線程安全
線程安全的概念:當多個線程訪問某一個類(對象或方法)時。這個類始終都能表現出正確的行為那麼這個類(對象或方法)就是線程安全的。
synchronized:可以在任何對象及方法上加鎖,而加鎖的這段代碼稱為“互斥區”或“臨界區”
示例:【com.study.base.thread.a_sync.sync001】MyThread
package com.study.base.thread.a_sync.sync001; /** * * 線程安全概念:當多個線程訪問某一個類(對象或方法)時,這個類始終都能表現出正確的行為,那麼這個類(對象或方法) * 就是線程安全的 * synchronized:可以在任意對象及方法上加鎖,而加鎖的這段代碼稱為“互斥區”或“臨界區” * * 示例總結 * 當多個線程訪問myThread的run方法時,以排隊的方式進處理(這裡排隊是按照CPU分配的先後順序商定的), * 一個線程想要執行Synchronized修飾的方法里的代碼,首先是嘗試獲得鎖,如果拿到鎖,執行synchronized代碼體內容; * 拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到為止,而且是多個線程同時去競爭這把鎖(也就是會有鎖競爭的問題)。 */ public class MyThread implements Runnable { private int count = 5; //synchronized 加鎖 public synchronized void run() { count--; System.out.println(Thread.currentThread().getName()+" count = "+count); } public static void main(String[] args) { /** * 分析: * 當多個線程訪問myThread的run方法時,以排隊的方式進行處理(這裡排隊是按照cpu分配的先後順序而定的) * 1 嘗試獲得鎖 * 2 如果拿到鎖,執行synchronized代碼體內容:拿不到鎖出,這個線程就會不斷的嘗試獲得這把鎖,直到拿到為止 * 而且是多個線程同時去競爭這把鎖,也就是會有鎖競爭的問題 */ MyThread mythread = new MyThread(); Thread t1 = new Thread(mythread,"t1"); Thread t2 = new Thread(mythread,"t2"); Thread t3 = new Thread(mythread,"t3"); Thread t4 = new Thread(mythread,"t4"); Thread t5 = new Thread(mythread,"t5"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }MyThread
示例總結
當多個線程訪問myThread的run方法時,以排隊的方式進處理(這裡排隊是按照CPU分配的先後順序商定的),一個線程想要執行Synchronized修飾的方法里的代碼,首先是嘗試獲得鎖,如果拿到鎖,執行synchronized代碼體內容;拿不到鎖,這個線程就會不斷的嘗試獲得這把鎖,直到拿到為止,而且是多個線程同時去競爭這把鎖(也就是會有鎖競爭的問題)。
二、多個線程多個鎖
多個線程多個鎖:多個線程,每個線程都可以拿到自己指定的鎖分別獲得鎖之後執行synchronized方法體的內容
示例:【com.study.base.thread.a_sync.sync002】MultiThread
package com.study.base.thread.a_sync.sync002; /** * 多個線程多個鎖:多個線程,每個線程都可以拿到自己指定的鎖分別獲得鎖之後執行synchronized方法體的內容 * 關鍵字synchronized獲取的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,所以示例代碼中哪個線程先執行synchronized關鍵字的方法,哪個線下就持有該方法所屬對象的鎖(Lock),兩個對象。線程獲得的就是兩個不同的鎖,他們互不影響 * 有一種情況則是相同的鎖,即使在靜態方法上加synchronized關鍵字,表示鎖定.class,類一級別的鎖(獨占.class類). */ public class MultiThread { private int num = 0; public synchronized void printNum(String tag) { try { if(tag.equals("a")) { num = 100; System.out.println("tag a , set num over"); Thread.sleep(1000); } else { num = 200 ; System.out.println("tag b , set num over"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("tag "+tag+" , num = "+num); } //註意觀察run方法的輸出順序 public static void main(String[] args) { //兩個不同的對象 final MultiThread m1 = new MultiThread(); final MultiThread m2 = new MultiThread(); Thread t1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub m1.printNum("a"); } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub m2.printNum("b"); } },"t2"); t1.start(); t2.start(); } }MultiThread
示例總結
關鍵字synchronized獲取的鎖都是對象鎖,而不是把一段代碼(方法)當做鎖,所以示例代碼中哪個線程先執行synchronized關鍵字的方法,哪個線下就持有該方法所屬對象的鎖(Lock),兩個對象。線程獲得的就是兩個不同的鎖,他們互不影響 有一種情況則是相同的鎖,即使在靜態方法上加synchronized關鍵字,表示鎖定.class,類一級別的鎖(獨占.class類).
三、對象鎖的同步和非同步
同步:synchronized 同步的概念就是共用,我們要牢牢記住“共用”這兩個字,如果不是共用的資源,就沒有必要進行同步
非同步:asynchronized 非同步的概念就是獨立,相互之間不受到任何制約。就好像我們學習http的時候,在頁面發起的Ajax請求,我們還可以繼續瀏覽操作頁面的內容,兩者之間沒有任何關係
同步的目的就是為了線程安全,其實對於線程安全來說,需要滿足兩個特性:
原子性(同步)
可見性
示例:【com.study.base.thread.a_sync.sync003】MyObject
package com.study.base.thread.a_sync.sync003; /** * * 對象的同步和非同步問題 * */ public class MyObject { public synchronized void method1() { try { System.out.print(Thread.currentThread().getName()); Thread.sleep(4000); } catch (Exception e) { e.printStackTrace(); } } /** synchronized */ public void method2() { System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { final MyObject mo = new MyObject(); /** * 分析: * t1線程先持有object對象的lock鎖,t2線程可以以非同步的方式調用對象中的非synchronized修飾的方法 * t1線程先持有object對象的lock鎖,t2線程如果在這個時候調用對象中的同步(synchronized)方法則需等待,也就是同步 */ Thread t1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mo.method1(); } },"t1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub mo.method2(); } },"t2"); t1.start(); t2.start(); } }MyObject
示例總結
A線程先持有object對象的Lock鎖,B線程如果在這個時候調用對象的同步(synchronized)方法則需要等待,也就是同步
A線程先持有object對象的Lock鎖,B線程可以以非同步的方式調用對象中的非synchronized修飾的方法
四、臟讀
對於對象的同步和非同步方法,我們在設計自己的程式的時候,一定要考慮問題的整體,不然會出現數據不一致的錯誤,很經典的錯誤就是臟讀(dirtyread)
示例:【com.study.base.thread.a_sync.sync004】DirtyRead
package com.study.base.thread.a_sync.sync004; /** * 業務整體需要使用完整的synchronized,保持業務的原子性 * * */ public class DirtyRead { private String username = "Rang"; private String password = "123"; public synchronized void setValue(String username, String password) { this.username = username; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.password = password; System.out.println("setValue最終結果, username = " + username + " password = " + password); } public synchronized void getValue() { System.out.println("getValue方法得到, username = " + username + " password = " + password); } public static void main(String[] args) throws InterruptedException { final DirtyRead dr = new DirtyRead(); Thread t1 = new Thread(new Runnable() { @Override public void run() { dr.setValue("z3", "456"); } },"t1"); t1.start(); Thread.sleep(1000); dr.getValue(); } }DirtyRead
示例總結:
在我們對一個對象的方法加鎖的時候,需要考慮業務的整體性,即為setValue / getValue 方法同時加鎖synchronized同步的關鍵字,保證業務(service)的原子性,不然會出現業務錯誤(也從側面保證業務的一致性)。
五、synchronized 其他概念
synchronized鎖重入:關鍵字synchronized擁有鎖重入的功能,也就是使用synchronized時,當一個線程得到一個對象的鎖後,再次請求此對象時是可以再次得到該對象的鎖的。
示例:【com.study.base.thread.a_sync.sync005】SyncDubbo1
package com.study.base.thread.a_sync.sync005; /** * synchronized 的重入 * */ public class SyncDubbo1 { public synchronized void method1() { System.out.println("method1......"); method2(); } public synchronized void method2() { System.out.println("method2......"); method3(); } public synchronized void method3() { System.out.println("method3......"); } public static void main(String[] args) { final SyncDubbo1 sa = new SyncDubbo1(); Thread t1 = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub sa.method1(); } }); t1.start(); } }SyncDubbo1
示例:【com.study.base.thread.a_sync.sync005】SyncDubbo2
package com.study.base.thread.a_sync.sync005; /** * synchronized 的重入 * */ public class SyncDubbo2 { static class Main{ public int i = 10; public synchronized void operationSup() { try { i--; System.out.println("Main print i = "+i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * 有父子繼承關係的 互相之間調用有synchronized修飾的方法也是線程安全的 * */ static class Sub extends Main{ public synchronized void operationSub() { try { while (i > 0) { i--; System.out.println("Sub print i = "+i); Thread.sleep(100); this.operationSup(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { Sub sub = new Sub(); sub.operationSub(); } }); t1.start(); } }SyncDubbo2
示例:【com.study.base.thread.a_sync.sync005】SyncException
package com.study.base.thread.a_sync.sync005; /** * synchronized 異常 * 異常之後 前後是一個整體時 拋出異常 回滾 * 前後不是一個整體沒有關聯關係的話 記錄日誌 continue 執行下個迴圈 */ public class SyncException { private int i = 0 ; public synchronized void operation() { while (true) { try { i++; Thread.sleep(200); System.out.println(Thread.currentThread().getName()+", i = "+i); if (i == 10) { Integer.parseInt("a"); //throw new RuntimeException(); } } catch (Exception e) { //InterruptedException e.printStackTrace(); System.out.println(" log info i = "+i); //throw new RuntimeException //continue; } } } public static void main(String[] args) { final SyncException se = new SyncException(); Thread t1 = new Thread(new Runnable() { @Override public void run() { se.operation(); } },"t1"); t1.start(); } }SyncException
示例說明:
對於web應用程式。異常釋放鎖的情況,如果不及時處理,很可能對你的應用程式業務邏輯產生嚴重的錯誤,比如你現在執行一個隊列任務,很多對象都去在等待第一個對象正常執行完畢再去釋放鎖。但是第一個對象由於異常的出現,導致業務邏輯沒有正常執行完畢,就釋放了鎖。那麼可想而知後續的對象執行的都是錯誤的邏輯。所以這一點一定要引起註意,在編寫代碼的時候,一定要考慮周全