天天看點

Java多線程下對Synchronized的了解

對于Synchronized學習了多次,但是有時候還是不了解其中的差別,找了很久的材料,才對Synchronized有較深的了解。

1.Synchronized相關概念

    在Java程式設計中,經常會用到同步,而用的最多也許就是Synchronized這個關鍵字。首先Synchronized關鍵字涉及到鎖的概念。首先對相關概念進行解釋。先借用别人的總結,感覺還不錯!!!!

    java的内置鎖:每個java對象都可以用做一個實作同步的鎖,這些鎖成為内置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得内置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。

    java内置鎖是一個互斥鎖,這就是意味着最多隻有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的内置鎖時,線程A必須等待或者阻塞,知道線程B釋放這個鎖,如果B線程不釋放這個鎖,那麼A線程将永遠等待下去。

    java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和内置鎖是一緻的,但是,兩個鎖實際是有很大的差別的,對象鎖是用于對象執行個體方法,或者一個對象執行個體上的,類鎖是用于類的靜态方法或者一個類的class對象上的。我們知道,類的對象執行個體可以有很多個,但是每個類隻有一個class對象,是以不同對象執行個體的對象鎖是互不幹擾的,但是每個類隻有一個類鎖。但是有一點必須注意的是,其實類鎖隻是一個概念上的東西,并不是真實存在的,它隻是用來幫助我們了解鎖定執行個體方法和靜态方法的差別的。      

2.對象鎖

        通過以上相關概念的描述,對Synchronized關鍵字有了初步的了解,從中可以的出Synchronized的用法:Synchronized修飾方法和Synchronized修飾代碼塊。為了加深相關概念,通過一段執行個體加深對相關概念的了解。

public class SynchronizedTest
{
	public void test1() 
    {  
         synchronized(this) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
       
        test1.start();  
        test2.start();  
        
       
	}
}
           

        上述方法代碼中,第一個方法使用了同步代碼塊的方式進行同步,傳入的是對象執行個體this,表示是目前對象,當然,如果需要同步其他對象,也可以傳入其他對象的執行個體。第二個方法是修飾方法的方式進行同步。其運作結果如下:

Java多線程下對Synchronized的了解

因為第一個同步塊傳入的是this,是以兩個同步塊代碼需要獲得的對象鎖是同一個對象鎖,下面main方法是同時開啟兩個線程,分别調用test1和test2的方法,那麼兩個線程都需要獲得該對象鎖,一個獲得之後,另一個線程必須等到哪一個線程結束之後,才能得到線程鎖。從結果中我們也可以看出:test2線程結束之後,釋放鎖,test1線程才開始運作。可能有人會疑問,代碼裡明明是先開啟的test1線程,為什麼先執行的是test2線程呢?這是因為java編譯器在編譯成位元組碼的時候,會對代碼進行一個重排序,也就是說,編譯器會根據實際情況對代碼進行一個合理的排序,編譯前代碼寫在前面,在編譯後的位元組碼不一定排在前面,是以這種運作結果是正常的, 這裡是題外話,最主要是檢驗synchronized的用法的正确性。

如果我們把test1方法的Synchronized(this)去掉,改在方法上呢?
package SynchronizedTest;


public class SynchronizedTest
{
	public synchronized void test1() 
    {  
         /*synchronized(this) */
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
       
        test1.start();  
        test2.start();  
        
       
	}
}
           

運作結果如下,你會發現和上圖運作的結果是一樣的。進而進一步說明了對象鎖的概念。

Java多線程下對Synchronized的了解
當我們把test2方法中的Synchronized去掉将會如何呢?
Java多線程下對Synchronized的了解
    上面是執行結果,我們可以看到,結果輸出是交替着進行輸出的,這是因為,某個線程得到了對象鎖,但是另一個線程還是可以通路沒有進行同步的方法或者代碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,得到了對象鎖,其他線程還是可以通路那些沒有同步的方法(普通方法)。這裡涉及到内置鎖的一個概念(此概念出自java并發程式設計實戰第二章):對象的内置鎖和對象的狀态之間是沒有内在的關聯的,雖然大多數類都将内置鎖用做一種有效的加鎖機制,但對象的域并不一定通過内置鎖來保護。當擷取到與對象關聯的内置鎖時,并不能阻止其他線程通路該對象,當某個線程獲得對象的鎖之後,隻能阻止其他線程獲得同一個鎖。之是以每個對象都有一個内置鎖,是為了免去顯式地建立鎖對象。      
當建立多個對象時,結果又将如何呢?
package SynchronizedTest;


public class SynchronizedTest
{
	public void test1() 
    {  
         synchronized(this) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
		final SynchronizedTest myt3 = new SynchronizedTest();
		final SynchronizedTest myt4 = new SynchronizedTest();
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
        
        Thread test3 = new Thread(  new Runnable() {  public void run() {  myt3.test1();  }  }, "test3"  );  
        Thread test4 = new Thread(  new Runnable() {  public void run() { myt3.test2();   }  }, "test4"  );
        
        Thread test5 = new Thread(  new Runnable() {  public void run() { myt4.test1();  }  }, "test5"  );  
        Thread test6 = new Thread(  new Runnable() {  public void run() { myt4.test2();   }  }, "test6"  );
        test1.start();  
        test2.start();  
        
        test3.start();  
        test4.start(); 
        
        test5.start();  
        test6.start(); 
	}
}
           

其運作結果如下:

Java多線程下對Synchronized的了解

        執行個體中有三個不同的對象,六種線程。test1線程和test2線程共享對象myt2;test3線程和test4線程共享對象myt3;test5線程和test6線程共享對象myt4;從結果中得出不同對象執行個體myt2、myt3、myt4的線程交替進行輸出,說明不同對象執行個體的對象鎖是互不幹擾。同時你也會發現,先運作myt2的test1線程、myt3的test3線程、myt4的test5線程,然後運作myt2的test2線程、myt3的test4線程、myt4的test6線程.當某一個對象的對象鎖被占有之後,這個對象的另一個線程就會等目前運作線程結束,釋放對象鎖之後才會運作。

3.類鎖的修飾方法或者代碼塊

為詳細了解類鎖,先看一段代碼及其運作結果:

package SynchronizedTest;


public class SynchronizedTest
{
	public void test1() 
    {  
         synchronized(SynchronizedTest.class) 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public static synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
		/*final SynchronizedTest myt3 = new SynchronizedTest();
		final SynchronizedTest myt4 = new SynchronizedTest();*/
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
        
        /*Thread test3 = new Thread(  new Runnable() {  public void run() {  myt3.test1();  }  }, "test3"  );  
        Thread test4 = new Thread(  new Runnable() {  public void run() { myt3.test2();   }  }, "test4"  );
        
        Thread test5 = new Thread(  new Runnable() {  public void run() { myt4.test1();  }  }, "test5"  );  
        Thread test6 = new Thread(  new Runnable() {  public void run() { myt4.test2();   }  }, "test6"  );*/
        test1.start();  
        test2.start();  
        
       /* test3.start();  
        test4.start(); 
        
        test5.start();  
        test6.start(); */
	}
}其
           

其運作結果如下:

Java多線程下對Synchronized的了解

你會發現他的運作結果和對象鎖是一樣的,這分辨不出其中的差别。

當出現不同對象執行個體又當如何呢?

final SynchronizedTest myt2 = new SynchronizedTest();
		final SynchronizedTest myt3 = new SynchronizedTest();
		final SynchronizedTest myt4 = new SynchronizedTest();
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
        
        Thread test3 = new Thread(  new Runnable() {  public void run() {  myt3.test1();  }  }, "test3"  );  
        Thread test4 = new Thread(  new Runnable() {  public void run() { myt3.test2();   }  }, "test4"  );
        
        Thread test5 = new Thread(  new Runnable() {  public void run() { myt4.test1();  }  }, "test5"  );  
        Thread test6 = new Thread(  new Runnable() {  public void run() { myt4.test2();   }  }, "test6"  );
        test1.start();  
        test2.start();  
        
        test3.start();  
        test4.start(); 
        
        test5.start();  
        test6.start(); 
           

其運作結果如下:

Java多線程下對Synchronized的了解

        從結果可以分析得出,類鎖是一個類獨一無二的,不管這一個類有多少個執行個體,他的類鎖隻有一個。當一個類中有很多個線程,對類鎖而言,他隻能一個線程運作結束之後,釋放類鎖之後,另一個線程才能運作。

synchronized同時修飾靜态和非靜态方法時結果又當如何呢?

package SynchronizedTest;


public class SynchronizedTest
{
	public synchronized void test1() 
    {  
         /*synchronized(SynchronizedTest.class)*/ 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i);  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public static synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i);  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
		/*final SynchronizedTest myt3 = new SynchronizedTest();
		final SynchronizedTest myt4 = new SynchronizedTest();*/
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
        
        /*Thread test3 = new Thread(  new Runnable() {  public void run() {  myt3.test1();  }  }, "test3"  );  
        Thread test4 = new Thread(  new Runnable() {  public void run() { myt3.test2();   }  }, "test4"  );
        
        Thread test5 = new Thread(  new Runnable() {  public void run() { myt4.test1();  }  }, "test5"  );  
        Thread test6 = new Thread(  new Runnable() {  public void run() { myt4.test2();   }  }, "test6"  );*/
        test1.start();  
        test2.start();  
        
       /* test3.start();  
        test4.start(); 
        
        test5.start();  
        test6.start(); */
	}
}
           

運作結果如下:

Java多線程下對Synchronized的了解
上面代碼synchronized同時修飾靜态方法和執行個體方法,但是運作結果是交替進行的,這證明了類鎖和對象鎖是兩個不一樣的鎖,控制着不同的區域,它們是互不幹擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
      
當出現多個執行個體之後,又當如何呢?
package SynchronizedTest;


public class SynchronizedTest
{
	public synchronized void test1() 
    {  
         /*synchronized(SynchronizedTest.class)*/ 
         {  
              int i = 5;  
              while( i-- > 0) 
              {  
                   System.out.println(Thread.currentThread().getName() + " : " + i+" 對象鎖被占用--");  
                   try 
                   {  
                        Thread.sleep(500);  
                   } 
                   catch (InterruptedException ie) 
                   {  
                   }  
              }  
         }  
    }  
	
	public static synchronized void test2() 
    {  
         int i = 5;  
         while( i-- > 0) 
         {  
              System.out.println(Thread.currentThread().getName() + " : " + i+" 類鎖占用+++");  
              try 
              {  
                   Thread.sleep(500);  
              } 
              catch (InterruptedException ie) 
              {  
              }  
         }  
    }  
	
	public static void main(String[] args)
	{
		final SynchronizedTest myt2 = new SynchronizedTest();
		final SynchronizedTest myt3 = new SynchronizedTest();
		final SynchronizedTest myt4 = new SynchronizedTest();
        Thread test1 = new Thread(  new Runnable() {  public void run() {  myt2.test1();  }  }, "test1"  );  
        Thread test2 = new Thread(  new Runnable() {  public void run() { myt2.test2();   }  }, "test2"  );
        
        Thread test3 = new Thread(  new Runnable() {  public void run() {  myt3.test1();  }  }, "test3"  );  
        Thread test4 = new Thread(  new Runnable() {  public void run() { myt3.test2();   }  }, "test4"  );
        
        Thread test5 = new Thread(  new Runnable() {  public void run() { myt4.test1();  }  }, "test5"  );  
        Thread test6 = new Thread(  new Runnable() {  public void run() { myt4.test2();   }  }, "test6"  );
        test1.start();  
        test2.start();  
        
        test3.start();  
        test4.start(); 
        
        test5.start();  
        test6.start(); 
	}
}
           
運作結果如下:
Java多線程下對Synchronized的了解

通過這個結果,可以得出分析:1.這證明了類鎖和對象鎖是兩個不一樣的鎖,控制着不同的區域,它們是互不幹擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。

2.一個對象的線程,如果他的一個線程運作的是類鎖,那麼就不能運作對象鎖,myt2下的線程test1、myt3下的線程test3、myt4下的線程test5運作的是對象鎖。則其他運作的是類鎖。

4.問題發現:

到這裡,對synchronized的用法已經有了一定的了解。這時有一個疑問,既然有了synchronized修飾方法的同步方式,為什麼還需要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在

 synchronized的缺陷:當某個線程進入同步方法獲得對象鎖,那麼其他線程通路這裡對象的同步方法時,必須等待或者阻塞,這對高并發的系統是緻命的,這很容易導緻系統的崩潰。如果某個線程在同步方法裡面發生了死循環,那麼它就永遠不會釋放這個對象鎖,那麼其他線程就要永遠的等待。這是一個緻命的問題。
 
當然同步方法和同步代碼塊都會有這樣的缺陷,隻要用了synchronized關鍵字就會有這樣的風險和缺陷。既然避免不了這種缺陷,那麼就應該将風險降到最低。這也是同步代碼塊在某種情況下要優于同步方法的方面。例如在某個類的方法裡面:這個類裡面聲明了一個對象執行個體,SynObject so=new SynObject();在某個方法裡面調用了這個執行個體的方法so.testsy();但是調用這個方法需要進行同步,不能同時有多個線程同時執行調用這個方法。

這時如果直接用synchronized修飾調用了so.testsy();代碼的方法,那麼當某個線程進入了這個方法之後,這個對象其他同步方法都不能給其他線程通路了。假如這個方法需要執行的時間很長,那麼其他線程會一直阻塞,影響到系統的性能。

如果這時用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那麼這個方法加鎖的對象是so這個對象,跟執行這行代碼的對象沒有關系,當一個線程執行這個方法時,這對其他同步方法時沒有影響的,因為他們持有的鎖都完全不一樣。

不過這裡還有一種特例,就是上面示範的第一個例子,對象鎖synchronized同時修飾方法和代碼塊,這時也可以展現到同步代碼塊的優越性,如果test1方法同步代碼塊後面有非常多沒有同步的代碼,而且有一個100000的循環,這導緻test1方法會執行時間非常長,那麼如果直接用synchronized修飾方法,那麼在方法沒執行完之前,其他線程是不可以通路test2方法的,但是如果用了同步代碼塊,那麼當退出代碼塊時就已經釋放了對象鎖,當線程還在執行test1的那個100000的循環時,其他線程就已經可以通路test2方法了。這就讓阻塞的機會或者線程更少。讓系統的性能更優越。

一個類的對象鎖和另一個類的對象鎖是沒有關聯的,當一個線程獲得A類的對象鎖時,它同時也可以獲得B類的對象鎖。