java之synchronized關鍵字用法(隱式鎖)

1、一套清晰的規則java

不管是加了鎖的方法仍是加了鎖的代碼塊,不管加的是對象鎖仍是加的是類鎖,只要上的是同一把鎖,那麼它們的訪問規則就應該是相同的。也就是上了同一把鎖的東西,一個線程進來要麼一塊兒容許被訪問,要麼一塊兒禁止訪問。所以,若是想搞清楚訪問的規則,咱們首先要搞清楚鎖的類型。而後判斷,只要上的是同一把鎖,訪問的規則就應該相同。那麼java中的鎖有那些類型呢。能夠簡單的總結爲兩種類型:緩存

一)java中鎖的類型:多線程

1.類鎖:只有synchronized修飾靜態方法或者修飾一個類的chass對象時,纔是類鎖。性能

2.對象鎖:除了類鎖,全部其餘上鎖方式都認爲是對象鎖,好比:synchronized修飾普通方法或者synchronized(this)給代碼塊上鎖。測試

應該注意的是,由於一個類只有一個.class對象,所以全部的訪問者在訪問被加了類鎖的方法或者代碼塊時候,都是共用同一把鎖,而類的實例卻能夠有不少個,所以不一樣對象訪問加了對象鎖的方法或者代碼塊,它們的訪問互不干擾。this

二)知道了鎖的類型,那麼咱們就能夠總結出一個通用且清晰的規則了。以下:線程

1.加了相同鎖的方法或者代碼塊,他們的訪問規則是相同的,即當否個訪問者得到該鎖的時候,他們(上鎖的方法或者代碼塊)一塊兒向訪問者開放(當前訪問者能夠調用這些方法)同時其餘訪問者若是訪問(上相同鎖的這些方法訪問)只能等待。對象

2.加了不一樣鎖的代碼或者代碼塊,多線程訪問互不影響繼承

3.沒有加鎖的方法或者代碼塊能夠隨意訪問不受限制字符串

三)而後再來看怎麼判斷什麼狀況下是相同的鎖。以下:

1.不一樣類型的鎖不是同一把鎖

2.加的是對象鎖,那麼必須是同一對象實例纔是同一把鎖

3.加的是類鎖,那必須是同一類纔是同一把鎖

四)咱們判斷訪問的規則,就是基於個步驟:

1.首先判斷是否是同一把鎖

2.而後判斷各自的訪問規則

五)鎖的重入

鎖重入:好比2個方法one、two都加了synchronized,one裏調用了two,那麼能夠執行two方法(即便one沒有執行完)。繼承也能夠用鎖重入。

2、使用這個規則

1.例子1

//對象鎖

   private synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱們是test1方法。"); 

      }

   }

   private  void test2(){

      synchronized(this){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱們是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我沒有鎖,隨便訪問。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

     

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

在代碼中,能夠看到咱們給test1方法上了對象鎖,加鎖的方式是給方法加鎖,加的是當前對象鎖;而給test2方法上也上了對象鎖,加鎖的方式給代碼塊加鎖,注意上的是當前對象鎖,你也能夠將this替換成任意其餘對象。而後咱們在main方法中看到,新建了兩個線程,都是用同一個對象來調用這兩個方法。所以,能夠斷定線程t1和t2使用的是同一把鎖,所以訪問規則就是t1得到了這把鎖後,tes1方法和test2中被加鎖的代碼塊都容許t1訪問,都拒絕t2訪問,等t1運行完,t2纔會得到該鎖,進行訪問。所以輸出的結果很明顯了,t1執行完,再執行t2。而sync.name()不受限制,咱們運行下程序,看看咱們按照規則來推理的是否正確,運行結果以下:

0:咱們是test1方法。

我沒有鎖,隨便訪問。

1:咱們是test1方法。

2:咱們是test1方法。

0:咱們是test2方法。

1:咱們是test2方法。

2:咱們是test2方法。

由於t1訪問test1後上個對象鎖,t2這時候也去訪問test2,發現test2也被上了同一個對象的鎖,沒有鑰匙,進不去,只能等t1將對象鎖釋放後才能進去

 

而後咱們再作個試驗,好比將main方法中的代碼改爲下面的:

private synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱們是test1方法。"); 

      }

   }

   private  void test2(){

      synchronized(this){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱們是test2方法。"); 

            }

      }

   }

public static void main(String arg[]) {

      final SyncSameObjectTest1 st=new SyncSameObjectTest1();

      final SyncSameObjectTest1 st2=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            st.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            st2.test2();

            //或者

            //st2.test1();

         }

      });

      t1.start();

      t2.start();

   }

只是多出一個實例而已,而後線程t2經過st2來調用test2。那麼就test1和test2加的都是當前對象的鎖,顯然它們的當前對象不一樣吧。因此它們不是同一把鎖,互相不干擾。那麼咱們運行程序,效果以下:它們各自運行各自的,因此沒什麼順序。

關鍵字synchronized取得的鎖都是對象鎖,而不是把一段代碼(方法)看成鎖,因此示例代碼中的那個線程先執行synchronized關鍵字的方法,那個線程就持有該方法所屬對象的鎖(Lock),兩個對象,線程得到的就是兩個不一樣的鎖,他們互不影響。

0:咱們是test1方法。

0:咱們是test2方法。

1:咱們是test2方法。

2:咱們是test2方法。

1:咱們是test1方法。

2:咱們是test1方法。

例子2:

//類鎖

   private static synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱們是test1方法。"); 

      }

   }

   //類鎖

   private  void test2(){

      synchronized(SyncSameObjectTest1.class){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱們是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我沒有鎖,隨便訪問。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            SyncSameObjectTest1.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

很簡單,test1是一個靜態方法,因此它加的鎖是類鎖, test2也加了一個類鎖,是加在了一個代碼塊中,所以他們加的是相同的鎖。一個類只有一個.class對象,所以全部的訪問者在訪問被加了類鎖的方法或者代碼塊時候,都是共用同一把鎖,即t1訪問完,t2再訪問。

有一種狀況是全部對象都是相同的鎖, 即在靜態方法上加synchronized關鍵字,static方法是類獨有,表示鎖定.class類,類一級別的鎖(獨佔.class類)。而不是對象,這樣就是synchronized的.class類,全部對象都擁有共同的鎖。

運行程序,結果以下:

0:咱們是test1方法。

我沒有鎖,隨便訪問。

1:咱們是test1方法。

2:咱們是test1方法。

0:咱們是test2方法。

1:咱們是test2方法。

2:咱們是test2方法。

例3://類鎖

   private static synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱們是test1方法。"); 

      }

   }

   //對象鎖

   private synchronized void test2(){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱們是test2方法。"); 

            }

   }

   private void  name() {

      System.out.println("我沒有鎖,隨便訪問。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            SyncSameObjectTest1.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

代碼中很顯然了,test1是靜態方法,它上的是類鎖。而test2是普通方法,它上的對象鎖。這是不一樣的鎖。因此t1訪問test1時,test2方法是不受干擾的,t2確定能夠同時訪問test2.所以打印順序爲任意順序。以下:

0:咱們是test1方法。

我沒有鎖,隨便訪問。

1:咱們是test1方法。

0:咱們是test2方法。

2:咱們是test1方法。

1:咱們是test2方法。

2:咱們是test2方法。

爲亂序。

此時若是你的打印結果爲先打印出t1的結果再是t2的結果,也沒必要驚訝,由於程序簡單,循環次數少,CPU性能高,因此極可能t2剛啓動就瞬間運行完了t1。

例子3:

//對象鎖

   private  synchronized void test1(){

      for (int i = 0; i < 3; i++) {

         System.out.println(i+":咱們是test1方法。"); 

      }

   }

   //類鎖

   private  void test2(){

      synchronized(SyncSameObjectTest1.class){

         for (int i = 0; i < 3; i++) {

               System.out.println(i+":咱們是test2方法。"); 

            }

      }

   }

   private void  name() {

      System.out.println("我沒有鎖,隨便訪問。");

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.test1();

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.name();

            sync.test2();

         }

      });

      t1.start();

      t2.start();

   }

很明顯一個類鎖,一個對象鎖,訪問規則互不干擾。即,t1訪問test1方法時,並不影響t2訪問test2(可是會禁止t2訪問test1,由於t2用的也是sync對象,此時t1已給sync對象上鎖了)。因此打印順序可能爲任意順序,還有若是sync同時訪問test2方法,因爲test2是類鎖,因此全部對象共有一把鎖,會按照順序打印。好了,運行程序,結果以下:

0:咱們是test1方法。

我沒有鎖,隨便訪問。

0:咱們是test2方法。

1:咱們是test2方法。

2:咱們是test2方法。

1:咱們是test1方法。

2:咱們是test1方法。

還有不少的實驗咱們就再也不作了,都是根據上面總結的規則來作的。相信這下,你對上鎖和上鎖後的訪問規則都清楚了吧。

3、鎖非this對象  如:(鎖String對象)

在Java中是有常量池緩存的功能的,就是說若是我先聲明瞭一個String str1 = 「a」; 再聲明一個同樣的字符串的時候,取值是從原地址去取的,也就是說是同一個對象。這也就致使了在鎖字符串對象的時候,能夠會取得意料以外的結果(字符串同樣會取得相同鎖),下面看一個例子介紹。都是將「xc」字符串傳給上面的測試方法。下面看下測試結果。

//對象鎖

   private   void lord(String str){

      try {

         synchronized(str){

            while (true) {

                System.out.println(Thread.currentThread().getName());

                Thread.sleep(100);

               

            }

         }

      } catch (Exception e) {

      }

   }

   public static void main(String arg[]) {

      final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.lord("xc");

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.lord("xc");

         }

      });

      t1.start();

      t2.start();

   }

 

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

從結果能夠就看到,線程B並無進來,也就說明兩個線程持有的是同一個鎖,線程1一直循環沒釋放鎖,其餘線程也進不來,即字符串對象是同一個。這就是String常量池會帶來的問題。因此在大多數狀況下,同步代碼塊synchronized代碼塊不使用String做爲鎖對象,而採用其餘。稍加改造,每次都建立一個新的字符串對象,不一樣對象的鎖:

final SyncSameObjectTest1 sync=new SyncSameObjectTest1();

      Thread t1=new Thread(new Runnable() {

         public void run() {

            sync.lord(new String("xc"));

         }

      });

      Thread t2=new Thread(new Runnable() {

         public void run() {

            sync.lord(new String("xc"));

         }

      });

      t1.start();

      t2.start();

   }

 

Thread-0

Thread-1

Thread-1

Thread-0

Thread-1

Thread-0

4、若是你還不理解的話

若是你還不理解的話,這是某位大牛作的一個很好的比喻。你能夠看看。摘抄以下:

 

相關文章
相關標籤/搜索