一、多線程啓動方式java
二、synchronized的基本用法編程
三、深度解析synchronized小程序
四、同步方法與非同步方法是否能同時調用?緩存
五、同步鎖是否可重入(可重入鎖)?多線程
六、異常是否會致使鎖釋放?併發
七、鎖定某對象,對象屬性改變是否會影響鎖?指定其餘對象是否會影響鎖?app
八、synchronized編程建議ide
繼承Thread重寫run()或者實現Runnable接口。測試
1 //實現runnable接口
2 static class MyThread implements Runnable{ 3 @Override 4 public void run() { 5
6 } 7 } 8
9 //繼承Thread+重寫run
10 static class MThread extends Thread{ 11 @Override 12 public void run() { 13 super.run(); 14 } 15 } 16
17 //測試方式
18 public static void main(String[] args) { 19 new Thread(new MyThread(),"t").start(); 20 new MThread().start(); 21 }
一、實例變量對象做爲鎖對象優化
/** * synchronized 鎖對象 * @author qiuyongAaron */
public class T1 { private int count=10; //利用Object實例對象標記互斥鎖,每一個線程進行同步代碼塊的時候,須要先去堆內存object獲取鎖標記,只有沒有被其它線程標記的時候才能得到鎖標記。
Object object =new Object(); public void method(){ synchronized(object){ count++; System.out.println(Thread.currentThread().getName()+":count="+count); } } } /** *鎖定當前對象,原理跟上面同樣,只是談一下應用狀況。 *@author qiuyongAaron */
public class T2 { private int count=10; public void method(){ synchronized(this){ count++; System.out.println(Thread.currentThread().getName()+":count="+count); } } //該種書寫方式等價於上面的method
public synchronized void cloneMethod(){ count++; System.out.println(Thread.currentThread().getName()+":count="+count); } }
總結:synchronized不是鎖定代碼塊,它是在訪問某段代碼塊的時候,去尋找鎖定對象上的標記(實質上就是一個變量增減,這就是這個標記)。以T2爲例,T2對象爲鎖定對象,假設開啓5個線程,線程A最早競爭到鎖,那麼線程A在T2對象上進行標記,至關於標記變量加1。就在這時,其餘4個線程競爭到鎖之後,發現T2對象標記變量不爲0,那麼他們就被阻塞,等待線程A釋放鎖的時候,標記變量會減1使它變爲0,其餘鎖就能競爭到鎖。虛擬機:發生就近原則-鎖定原則:釋放鎖先於得到鎖,簡而言之,只有線程A釋放鎖(鎖定對象標記變量爲0),其餘線程才能得到鎖(鎖定對象標記+1)。
二、靜態變量對象做爲鎖對象
/** * 鎖定靜態變量 * @author qiuyongAaron */
public class T3 { public static int count=10; public synchronized void method(){ count++; System.out.println(Thread.currentThread().getName()+":count="+count); } //等價於上述方法
public static void cloneMethod(){ synchronized (T3.class) {//這裏寫this能夠嗎?
count++; System.out.println(Thread.currentThread().getName()+":count="+count); } } }
問題:爲何靜態變量要寫T3.class,不能寫this?
回答:這須要瞭解反射與類加載過程才能透徹解析。類加載過程:類加載-->驗證-->準備-->解析-->初始化-->使用卸載,在類加載階段,將會把靜態變量、常量所有加載在堆內存的方法區中,而且會生成Class對象,T3.class就至關於Class對象,然而this是T3對象,而何時可以產生T3對象?當應用程序調用new T3()的構造器時候,也就是在初始化階段纔會產生。因此靜態變量做爲鎖定對象只能用T3.class,不能使用this對象。
總結:靜態變量在類加載的時候就存入內存,而實例變量是要調用構造器的時候才能加載進內存。因此,T3.class是類加載產生,this是初始化產生,天然標記鎖定對象的時候是用T3.class不用this。
synchronized定義:互斥鎖,保證原子性、可見性。也就是,當線程A得到鎖,其餘線程所有被阻塞。以前解析過不過多贅述。
多線程不加鎖:
1 //多線程不加鎖!
2 public class T4 { 3 public static void main(String[] args) { 4 MyThread t=new MyThread(); 5 Thread t1=new Thread(t,"t1"); 6 Thread t2=new Thread(t,"t2"); 7 t1.start(); 8 t2.start(); 9 } 10
11 static class MyThread implements Runnable{ 12 private int value =0; 13 @Override 14 public void run() { 15
16 for(int i=0;i<5;i++){ 17 value++; 18 System.out.println(Thread.currentThread().getName()+":"+this.value); 19 } 20 } 21 } 22 } 23
24 //運行結果:每次運行結果都不一樣
25 t1:2 t2:2 t1:3 t2:4 t1:5 t2:6 t1:7 t2:8 t1:9 t2:10
多線程加鎖:
//多線程加鎖!
public class T5 { public static void main(String[] args) { MyThread t=new MyThread(); Thread t1=new Thread(t,"t1"); Thread t2=new Thread(t,"t2"); t1.start(); t2.start(); } static class MyThread implements Runnable{ private int value =0; @Override public synchronized void run() { for(int i=0;i<5;i++){ value++; System.out.println(Thread.currentThread().getName()+":"+this.value); } } } } 運行結果: t1:1 t1:2 t1:3 t1:4 t1:5 t2:6 t2:7 t2:8 t2:9 t2:10
顯然,加了同步互斥鎖的例子程序符合咱們業務需求,那麼想一下這是爲何?
先談Java內存模型:
分析:在虛擬機中,堆內存用於存儲共享數據(實例對象),堆內存也就是這裏說的主內存。
每一個線程將會在堆內存中開闢一塊空間叫作線程的工做內存,附帶一塊緩存區用於存儲共享數據副本。那麼,共享數據在堆內存當中,線程通訊就是經過主內存爲中介,線程在本地內存讀而且操做完共享變量操做完畢之後,把值寫入主內存。
分析程序1:
分析程序2:
1 /**
2 * 線程是否能夠同時調用同步方法與非同步方法? 3 * @author qiuyongAaron 4 */
5 public class T6 { 6
7 public synchronized void m1() { 8 System.out.println(Thread.currentThread().getName() + " m1 start..."); 9 try { 10 Thread.sleep(10000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 System.out.println(Thread.currentThread().getName() + " m1 end"); 15 } 16
17 public void m2() { 18 try { 19 Thread.sleep(5000); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 System.out.println(Thread.currentThread().getName() + " m2 "); 24 } 25
26 public static void main(String[] args) { 27 T6 t = new T6(); 28 new Thread(()->t.m1(),"t1").start(); 29 new Thread(()->t.m2(),"t2").start(); 30 } 31 } 32 //運行結果:
33 t1:start!
34 t2:start!
35 t1:end!
總結:顯然能夠,首先synchronized同步互斥鎖是鎖定對象,t1鎖定的T6對象。線程t1去訪問代碼塊t.m1()的時候會去申請鎖,去查看鎖定標記是否爲0,再決定是否阻塞。然而線程t2訪問t.m2()都不用申請鎖,因此你鎖定標記爲何,與我有什麼關係?因此,上述問題固然是成立!
1 /**
2 * 當鎖定同一個對象的時候,鎖只是在對象添加標記,加鎖一次標記+1,解鎖一次標記-1,直到標記爲0釋放鎖。 3 * 可重入鎖 4 * @author qiuyongAaron 5 */
6 public class T7 { 7 public synchronized void m1(){ 8 try { 9 Thread.sleep(5000); 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } 13 m2(); 14 } 15
16 public synchronized void m2(){ 17 try { 18 Thread.sleep(5000); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23 }
總結:synchronized同步互斥鎖,支持可重入。在開篇咱們就談了,申請鎖意味着對鎖定對象的標記變量值修改,若是是同一個鎖定變量,那麼沒重入一次,鎖標記變量+1。若是想鎖釋放,那麼必須釋放鎖-1,直到標記變量爲0,鎖才能被釋放被其餘線程佔用。
/** * 異常將致使鎖釋放! * @author qiuyongAaron */
public class T9 { public synchronized void m1(){ int i=0; System.out.println(Thread.currentThread().getName()+":start!"); while(true){ if(i==10){ System.out.println(5/0); } i++; } } public void m2(){ System.out.println(Thread.currentThread().getName()+":start!"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":end!"); } public static void main(String[] args) { T9 t=new T9(); new Thread(()->t.m1(),"t1").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->t.m2(),"t2").start(); } } 運行結果: t1:start! Exception in thread "t1" java.lang.ArithmeticException: / by zero at com.ccut.aaron.synchronize.T9.m1(T9.java:12) at com.ccut.aaron.synchronize.T9.lambda$0(T9.java:30) at java.lang.Thread.run(Thread.java:745) t2:start! t2:end!
總結:答案是產生異常將會釋放鎖,因此在編寫代碼時候須要處理異常。從例子程序可看出,若是不釋放鎖的話,t1一直佔用鎖,而t2不可能得到鎖。從運行結果看出,t2得到鎖資源,因此證實了原命題。
/** * 鎖定對象改變屬性無影響,若是鎖定對象指定新對象,鎖定對象將會改變! * @author xiaoyongAaron */
public class T10 { Object o=new Object(); public void m(){ synchronized(o){ while(true){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { T10 t=new T10(); new Thread(()->t.m(),"t1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t.o=new Object(); new Thread(()->t.m(),"t2").start(); } } 運行結果: t1 t1 t2
總結:從運行結果看出原命題的答案是,修改鎖定變量的屬性不會改變鎖,鎖定變量指定新對象將會報錯。看例子程序,假設鎖沒有轉移到新的實例變量,那麼t2將會一直被阻塞。
一、儘可能鎖定有共享數據的代碼塊,這是併發編程的優化中的鎖粗化。
二、不要用常量做爲鎖定對象,由於常量池的常量同時被兩個地方引用將會產生很大的問題。
/** *鎖粗化 *@author qiuyongAaron */
public void T11{ int count=0; public synchronized void m(){ for(int i=0;i<10;i++){} System.out.println("hello world!"); synchronized(this){ count++; } } } /** *不要使用常量做爲鎖定對象!! *他們是同一個鎖定對象!! *@author qiuyongAaron */
public void T11{ String s1 = "Hello"; String s2 = "Hello"; void m1() { synchronized(s1) {} } void m2() { synchronized(s2) {} } }
做者:邱勇Aaron
出處:http://www.cnblogs.com/qiuyong/
您的支持是對博主深刻思考總結的最大鼓勵。
本文版權歸做者全部,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,尊重做者的勞動成果。
參考:深刻理解JVM、馬士兵併發編程、併發編程實踐