一、目錄

  1、多線程啟動(dòng)方式

  2、synchronized的基本用法

  3、深度解析synchronized

  4、同步方法與非同步方法是否能同時(shí)調(diào)用?

  5、同步鎖是否可重入(可重入鎖)?

  6、異常是否會(huì)導(dǎo)致鎖釋放?

  7、鎖定某對(duì)象,對(duì)象屬性改變是否會(huì)影響鎖?指定其他對(duì)象是否會(huì)影響鎖?

  8、synchronized編程建議

二、多線程啟動(dòng)方式

繼承Thread重寫(xiě)run()或者實(shí)現(xiàn)Runnable接口。

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 1 //實(shí)現(xiàn)runnable接口 2     static class MyThread implements Runnable{ 3         @Override 4         public void run() { 5              6         } 7     } 8      9     //繼承Thread+重寫(xiě)run10     static class MThread extends Thread{11         @Override12         public void run() {13             super.run();14         }15     }16     17     //測(cè)試方式18     public static void main(String[] args) {19         new Thread(new MyThread(),"t").start();20         new MThread().start();21     }

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

二、synchronized的基本用法

1、實(shí)例變量對(duì)象作為鎖對(duì)象

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

/**
 * synchronized 鎖對(duì)象
 * @author qiuyongAaron */public class T1 {     private int count=10;     //利用Object實(shí)例對(duì)象標(biāo)記互斥鎖,每個(gè)線程進(jìn)行同步代碼塊的時(shí)候,需要先去堆內(nèi)存object獲取鎖標(biāo)記,只有沒(méi)有被其它線程標(biāo)記的時(shí)候才能獲得鎖標(biāo)記。
     Object object =new Object();     public void method(){           synchronized(object){
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }
}/***鎖定當(dāng)前對(duì)象,原理跟上面一樣,只是談一下應(yīng)用情況。
*@author qiuyongAaron*/public class T2 {     private int count=10;     public void method(){           synchronized(this){
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }     //該種書(shū)寫(xiě)方式等價(jià)于上面的method
     public synchronized void cloneMethod(){
           count++;
          System.out.println(Thread.currentThread().getName()+":count="+count);
     }
}

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

總結(jié):synchronized不是鎖定代碼塊,它是在訪問(wèn)某段代碼塊的時(shí)候,去尋找鎖定對(duì)象上的標(biāo)記(實(shí)質(zhì)上就是一個(gè)變量增減,這就是這個(gè)標(biāo)記)。以T2為例,T2對(duì)象為鎖定對(duì)象,假設(shè)開(kāi)啟5個(gè)線程,線程A最先競(jìng)爭(zhēng)到鎖,那么線程A在T2對(duì)象上進(jìn)行標(biāo)記,相當(dāng)于標(biāo)記變量加1。就在這時(shí),其他4個(gè)線程競(jìng)爭(zhēng)到鎖以后,發(fā)現(xiàn)T2對(duì)象標(biāo)記變量不為0,那么他們就被阻塞,等待線程A釋放鎖的時(shí)候,標(biāo)記變量會(huì)減1使它變?yōu)?,其他鎖就能競(jìng)爭(zhēng)到鎖。虛擬機(jī):發(fā)生就近原則-鎖定原則:釋放鎖先于獲得鎖,簡(jiǎn)而言之,只有線程A釋放鎖(鎖定對(duì)象標(biāo)記變量為0),其他線程才能獲得鎖(鎖定對(duì)象標(biāo)記+1)。

 

2、靜態(tài)變量對(duì)象作為鎖對(duì)象

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

/**
 * 鎖定靜態(tài)變量
 * @author qiuyongAaron */public class T3 {     public static int count=10;     public synchronized void method(){
           count++;
          System.out.println(Thread.currentThread().getName()+":count="+count);
     }     //等價(jià)于上述方法
     public static void cloneMethod(){           synchronized (T3.class) {//這里寫(xiě)this可以嗎?
                count++;
                System.out.println(Thread.currentThread().getName()+":count="+count);
           }
     }
}

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

問(wèn)題:為什么靜態(tài)變量要寫(xiě)T3.class,不能寫(xiě)this?

回答:這需要了解反射與類(lèi)加載過(guò)程才能透徹解析。類(lèi)加載過(guò)程:類(lèi)加載-->驗(yàn)證-->準(zhǔn)備-->解析-->初始化-->使用卸載,在類(lèi)加載階段,將會(huì)把靜態(tài)變量、常量全部加載在堆內(nèi)存的方法區(qū)中,并且會(huì)生成Class對(duì)象,T3.class就相當(dāng)于Class對(duì)象,然而this是T3對(duì)象,而什么時(shí)候能夠產(chǎn)生T3對(duì)象?當(dāng)應(yīng)用程序調(diào)用new T3()的構(gòu)造器時(shí)候,也就是在初始化階段才會(huì)產(chǎn)生。所以靜態(tài)變量作為鎖定對(duì)象只能用T3.class,不能使用this對(duì)象。

總結(jié):靜態(tài)變量在類(lèi)加載的時(shí)候就存入內(nèi)存,而實(shí)例變量是要調(diào)用構(gòu)造器的時(shí)候才能加載進(jìn)內(nèi)存。所以,T3.class是類(lèi)加載產(chǎn)生,this是初始化產(chǎn)生,自然標(biāo)記鎖定對(duì)象的時(shí)候是用T3.class不用this。

三、深度解析synchronized

synchronized定義:互斥鎖,保證原子性、可見(jiàn)性。也就是,當(dāng)線程A獲得鎖,其他線程全部被阻塞。之前解析過(guò)不過(guò)多贅述。

多線程不加鎖:

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 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            @Override14            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 //運(yùn)行結(jié)果:每次運(yùn)行結(jié)果都不同25 t1:2 t2:2 t1:3 t2:4 t1:5 t2:6 t1:7 t2:8 t1:9 t2:10

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

多線程加鎖:

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

//多線程加鎖!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);
                }
           }
     }
}
運(yùn)行結(jié)果:
t1:1 t1:2 t1:3 t1:4 t1:5 t2:6 t2:7 t2:8 t2:9 t2:10

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

顯然,加了同步互斥鎖的例子程序符合我們業(yè)務(wù)需求,那么想一下這是為什么?

先談Java內(nèi)存模型:

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

分析:在虛擬機(jī)中,堆內(nèi)存用于存儲(chǔ)共享數(shù)據(jù)(實(shí)例對(duì)象),堆內(nèi)存也就是這里說(shuō)的主內(nèi)存。

   每個(gè)線程將會(huì)在堆內(nèi)存中開(kāi)辟一塊空間叫做線程的工作內(nèi)存,附帶一塊緩存區(qū)用于存儲(chǔ)共享數(shù)據(jù)副本。那么,共享數(shù)據(jù)在堆內(nèi)存當(dāng)中,線程通信就是通過(guò)主內(nèi)存為中介,線程在本地內(nèi)存讀并且操作完共享變量操作完畢以后,把值寫(xiě)入主內(nèi)存。

 

分析程序1:

  • t1從主存中讀取共享變量value:0,并且執(zhí)行完value++后value:1,寫(xiě)入主存。

  • t2啟動(dòng)讀取主存value:1到工作內(nèi)存,執(zhí)行并打印value為2,3。

  • t2讀取的是它工作內(nèi)存的值,所以這時(shí)t1的本地內(nèi)存并沒(méi)有改變還是1,執(zhí)行打印輸入value:2。

  • 同樣邏輯執(zhí)行...

  • 來(lái)看t2:6、t2:8、t1:7、t1:9,為什么?

  • 當(dāng)t2在工作內(nèi)存操作完共享變量,t2把共享變量為value:6寫(xiě)入主存。

  • 就在這時(shí),t1從主存讀取共享變量value:6并且value++為7,還沒(méi)來(lái)得及打印。

  • t2從主存讀取共享變量value:7,value++,打印value:8,并且寫(xiě)入主存。

  • 這時(shí),繼續(xù)之前的操作value++,自然打印的值還是7,再讀取主存值value:8

  • 這時(shí)t1打印value:9,value:10。

 

分析程序2:

  • 在虛擬機(jī)的先行發(fā)生原則中(happen-before)的鎖定原則:對(duì)某一個(gè)對(duì)象加鎖的時(shí)候,它接鎖先于加鎖,意思就是必須等線程A鎖釋放,才能被線程B訪問(wèn)。

  • 回到這個(gè)小程序,t1啟動(dòng)、t2被阻塞不能訪問(wèn)共享變量。之前,我們談過(guò)java內(nèi)存模型,假設(shè)線t1啟動(dòng)讀取共享數(shù)據(jù),并且會(huì)把共享數(shù)據(jù)寫(xiě)入到工作內(nèi)存的緩存中,t1在本地內(nèi)存操作完,待它操作完不把數(shù)據(jù)寫(xiě)回主存,這樣即便t2被堵塞也沒(méi)用?所以,虛擬機(jī)規(guī)定,線程unlock的時(shí)候必須把數(shù)據(jù)刷新到主存,lock的時(shí)候必須從主存刷新數(shù)據(jù)到工作內(nèi)存。

  • 什么意思?最開(kāi)始主存共享變量value:0,t1獲得同步鎖,t2被阻塞。t1操作value:1-5,假設(shè)t1在本地內(nèi)存操作完就馬上釋放鎖并不把value寫(xiě)入主存,這時(shí)t2獲得同步鎖,從主存讀到的共享變量依然為0,這虛擬機(jī)豈能容忍?所以,虛擬機(jī)規(guī)定,t1必須unlock之前把數(shù)據(jù)從線程工作內(nèi)存刷新到主存,t2必須lock以后把數(shù)據(jù)從主存刷新到線程工作內(nèi)存。

四、同步方法與非同步方法是否能同時(shí)調(diào)用?

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 1 /** 2  * 線程是否可以同時(shí)調(diào)用同步方法與非同步方法? 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 //運(yùn)行結(jié)果:33 t1:start!34 t2:start!35 t1:end!

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 總結(jié):顯然可以,首先synchronized同步互斥鎖是鎖定對(duì)象,t1鎖定的T6對(duì)象。線程t1去訪問(wèn)代碼塊t.m1()的時(shí)候會(huì)去申請(qǐng)鎖,去查看鎖定標(biāo)記是否為0,再?zèng)Q定是否阻塞。然而線程t2訪問(wèn)t.m2()都不用申請(qǐng)鎖,所以你鎖定標(biāo)記為什么,與我有什么關(guān)系?所以,上述問(wèn)題當(dāng)然是成立!

五、同步互斥鎖是否可重入(可重入鎖)?

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 1 /** 2  * 當(dāng)鎖定同一個(gè)對(duì)象的時(shí)候,鎖只是在對(duì)象添加標(biāo)記,加鎖一次標(biāo)記+1,解鎖一次標(biāo)記-1,直到標(biāo)記為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 }

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

總結(jié):synchronized同步互斥鎖,支持可重入。在開(kāi)篇我們就談了,申請(qǐng)鎖意味著對(duì)鎖定對(duì)象的標(biāo)記變量值修改,如果是同一個(gè)鎖定變量,那么沒(méi)重入一次,鎖標(biāo)記變量+1。如果想鎖釋放,那么必須釋放鎖-1,直到標(biāo)記變量為0,鎖才能被釋放被其他線程占用。

六、異常是否會(huì)導(dǎo)致鎖釋放?

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

/**
 * 異常將導(dǎo)致鎖釋放!
 * @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();
     }

}
運(yùn)行結(jié)果:
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!

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

總結(jié):答案是產(chǎn)生異常將會(huì)釋放鎖,所以在編寫(xiě)代碼時(shí)候需要處理異常。從例子程序可看出,如果不釋放鎖的話,t1一直占用鎖,而t2不可能獲得鎖。從運(yùn)行結(jié)果看出,t2獲得鎖資源,所以證明了原命題。

七、鎖定某對(duì)象,對(duì)象屬性改變是否會(huì)影響鎖?指定其他對(duì)象是否會(huì)影響鎖?

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

/**
 * 鎖定對(duì)象改變屬性無(wú)影響,如果鎖定對(duì)象指定新對(duì)象,鎖定對(duì)象將會(huì)改變!
 * @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();
     }
}
運(yùn)行結(jié)果:
t1 t1 t2

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

總結(jié):從運(yùn)行結(jié)果看出原命題的答案是,修改鎖定變量的屬性不會(huì)改變鎖,鎖定變量指定新對(duì)象將會(huì)報(bào)錯(cuò)。看例子程序,假設(shè)鎖沒(méi)有轉(zhuǎn)移到新的實(shí)例變量,那么t2將會(huì)一直被阻塞。

八、synchronized編程建議

1、盡量鎖定有共享數(shù)據(jù)的代碼塊,這是并發(fā)編程的優(yōu)化中的鎖粗化。

2、不要用常量作為鎖定對(duì)象,因?yàn)槌A砍氐某A客瑫r(shí)被兩個(gè)地方引用將會(huì)產(chǎn)生很大的問(wèn)題。

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

/***鎖粗化
*@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++; 
         }
     }
}/***不要使用常量作為鎖定對(duì)象!!
*他們是同一個(gè)鎖定對(duì)象??!
*@author qiuyongAaron*/public void T11{
     String s1 = "Hello";
     String s2 = "Hello";     void m1() {        synchronized(s1) {}
     }     void m2() {         synchronized(s2) {}
     }
   
}

萬(wàn)碼學(xué)堂,電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),Java培訓(xùn),JavaEE開(kāi)發(fā)培訓(xùn),青島軟件培訓(xùn),軟件工程師培訓(xùn)

 九、版權(quán)聲明

  作者:邱勇Aaron

  出處:http://www.cnblogs.com/qiuyong/

  您的支持是對(duì)博主深入思考總結(jié)的最大鼓勵(lì)。

  本文版權(quán)歸作者所有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,尊重作者的勞動(dòng)成果。

http://www.cnblogs.com/qiuyong/p/7068258.html