現在的位置: 首頁 > 演算法 > 正文

Synchronized(對象鎖)和Static Synchronized(類鎖)的區別

2020年02月25日 演算法 ⁄ 共 7865字 ⁄ 字型大小 評論關閉

  通過分析Synchronized和Static Synchronized這兩個用法的分析,我們可以理解java中鎖的概念。一個是實例鎖(鎖在某一個實例對象上,如果該類是單例,那麼該鎖也具有全局鎖的概念),一個是全局鎖(該鎖針對的是類,無論實例多少個對象,那麼線程都共享該鎖)。實例鎖對應的就是synchronized關鍵字,而類鎖(全局鎖)對應的就是static synchronized(或者是鎖在該類的class或者classloader對象上)。下面的文章做了很好的總結:

  1.synchronized與static synchronized 的區別

  synchronized是對類的當前實例(當前對象)進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,注意這裡是「類的當前實例」, 類的兩個不同實例就沒有這種約束了。

  那麼static synchronized恰好就是要控制類的所有實例的並發訪問,static synchronized是限制多線程中該類的所有實例同時訪問jvm中該類所對應的代碼塊。實際上,在類中如果某方法或某代碼塊中有 synchronized,那麼在生成一個該類實例後,該實例也就有一個監視塊,防止線程並發訪問該實例的synchronized保護塊,而static synchronized則是所有該類的所有實例公用得一個監視塊,這就是他們兩個的區別。也就是說synchronized相當於 this.synchronized,而static synchronized相當於Something.synchronized。

  一個日本作者-結成浩的《java多線程設計模式》有這樣的一個列子:

  [java] view plaincopy

  pulbic class Something(){

  public synchronized void isSyncA(){}

  public synchronized void isSyncB(){}

  public static synchronized void cSyncA(){}

  public static synchronized void cSyncB(){}

  }

  那麼,假如有Something類的兩個實例x與y,那麼下列各組方法被多線程同時訪問的情況是怎樣的?

  [java] view plaincopy

  a. x.isSyncA()與x.isSyncB()

  b. x.isSyncA()與y.isSyncA()

  c. x.cSyncA()與y.cSyncB()

  d. x.isSyncA()與Something.cSyncA()

  這裡,很清楚的可以判斷:

  a,都是對同一個實例(x)的synchronized域訪問,因此不能被同時訪問。(多線程中訪問x的不同synchronized域不能同時訪問)

  如果在多個線程中訪問x.isSyncA(),因為仍然是對同一個實例,且對同一個方法加鎖,所以多個線程中也不能同時訪問。(多線程中訪問x的同一個synchronized域不能同時訪問)

  b,是針對不同實例的,因此可以同時被訪問(對象鎖對於不同的對象實例沒有鎖的約束)

  c,因為是static synchronized,所以不同實例之間仍然會被限制,相當於Something.isSyncA()與 Something.isSyncB()了,因此不能被同時訪問。

  那麼,第d呢?,書上的 答案是可以被同時訪問的,答案理由是synchronzied的是實例方法與synchronzied的類方法由於鎖定(lock)不同的原因。

  個人分析也就是synchronized 與static synchronized 相當於兩幫派,各自管各自,相互之間就無約束了,可以被同時訪問。

  舉個例子:

  Java代碼

  public class TestSynchronized

  {

  public synchronized void test1()

  {

  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 TestSynchronized myt2 = new TestSynchronized();

  Thread test1 = new Thread( new Runnable() { public void run() { myt2.test1(); } }, "test1" );

  Thread test2 = new Thread( new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2" );

  test1.start();

  test2.start();

  // TestRunnable tr=new TestRunnable();

  // Thread test3=new Thread(tr);

  // test3.start();

  }

  }

  Java代碼

  test1 : 4

  test2 : 4

  test1 : 3

  test2 : 3

  test2 : 2

  test1 : 2

  test2 : 1

  test1 : 1

  test1 : 0

  test2 : 0

  上面代碼synchronized同時修飾靜態方法和實例方法,但是運行結果是交替進行的,這證明了類鎖和對象鎖是兩個不一樣的鎖,控制著不同的區域,它們是互不干擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。

  結論:A: synchronized static是某個類的範圍,synchronized static cSync{}防止多個線程中多個實例同時訪問這個 類中的synchronized static 方法。它可以對類的所有對象實例起作用。

  B: synchronized 是某實例的範圍,synchronized isSync(){}防止多個線程中這一個實例同時訪問這個類的synchronized 方法。

  其實總結起來很簡單。

  一個鎖的是類對象,一個鎖的是實例對象。

  若類對象被lock,則類對象的所有同步方法全被lock;

  若實例對象被lock,則該實例對象的所有同步方法全被lock。

  2.synchronized方法與synchronized代碼快的區別

  synchronized methods(){} 與synchronized(this){}之間沒有什麼區別,只是synchronized methods(){} 便於閱讀理解,而synchronized(this){}可以更精確的控制衝突限制訪問區域,有時候表現更高效率。

  兩種方式效率比較:

  1、同步塊,代碼如下:

  [java] view plaincopy

  < span style="font-size:18px;">package com.bjtest.belen;

  import java.util.concurrent.CountDownLatch;

  import java.util.concurrent.ExecutorService;

  import java.util.concurrent.Executors;

  public class TestSynchronized {

  /**

  * @param args

  */

  public static void main(String[] args) {

  ExecutorService service = Executors.newCachedThreadPool();

  final CountDownLatch cdOrder = new CountDownLatch(1);

  final CountDownLatch cdAnswer = new CountDownLatch(3);

  final SynchonizedClass sc = new SynchonizedClass();

  for(int i=0; i<3; i++){   Runnable runnable = new Runnable(){   public void run() {   try{   cdOrder.await();   sc.start();   cdAnswer.countDown();   }catch(Exception e){   e.printStackTrace();   }   }   };   service.execute(runnable);   }   try{   Thread.sleep((long) (Math.random()*10000));   System.out.println("線程" + Thread.currentThread().getName() +   "發布執行命令");   cdOrder.countDown();   long beginTime = System.currentTimeMillis();   System.out.println("線程" + Thread.currentThread().getName() +   "已經發送命令,正在等待結果");   cdAnswer.await();   System.out.println("線程" + Thread.currentThread().getName() +   "已收到所有響應結果,所用時間為:" + (System.currentTimeMillis()-beginTime));   }catch(Exception e){   e.printStackTrace();   }   service.shutdown();   }   }   class SynchonizedClass{   public void start() throws InterruptedException{   Thread.sleep(100);//執行其它邏輯消耗時間   synchronized(this){   System.out.println("我運行使用了 10 ms");   }   }   }   < /span>

  運行結果如下:

  線程main發布執行命令

  線程main已經發送命令,正在等待結果

  我運行使用了 10 ms

  我運行使用了 10 ms

  我運行使用了 10 ms

  線程main已收到所有響應結果,所用時間為:110

  同步方法,代碼如下:

  [java] view plaincopy

  < span style="font-size:18px;">package com.bjtest.belen;

  import java.util.concurrent.CountDownLatch;

  import java.util.concurrent.ExecutorService;

  import java.util.concurrent.Executors;

  public class TestSynchronized {

  /**

  * @param args

  */

  public static void main(String[] args) {

  ExecutorService service = Executors.newCachedThreadPool();

  final CountDownLatch cdOrder = new CountDownLatch(1);

  final CountDownLatch cdAnswer = new CountDownLatch(3);

  final SynchonizedClass sc = new SynchonizedClass();

  for(int i=0; i<3; i++){   Runnable runnable = new Runnable(){   public void run() {   try{   cdOrder.await();   sc.start();   cdAnswer.countDown();   }catch(Exception e){   e.printStackTrace();   }   }   };   service.execute(runnable);   }   try{   Thread.sleep((long) (Math.random()*10000));   System.out.println("線程" + Thread.currentThread().getName() +   "發布執行命令");   cdOrder.countDown();   long beginTime = System.currentTimeMillis();   System.out.println("線程" + Thread.currentThread().getName() +   "已經發送命令,正在等待結果");   cdAnswer.await();   System.out.println("線程" + Thread.currentThread().getName() +   "已收到所有響應結果,所用時間為:" + (System.currentTimeMillis()-beginTime));   }catch(Exception e){   e.printStackTrace();   }   service.shutdown();   }   }   class SynchonizedClass{   public synchronized void start() throws InterruptedException{   Thread.sleep(100);//執行其它邏輯消耗時間   // synchronized(this){   System.out.println("我運行使用了 10 ms");   // }   }   }   < /span>

  運行結果如下:

  線程main發布執行命令

  線程main已經發送命令,正在等待結果

  我運行使用了 10 ms

  我運行使用了 10 ms

  我運行使用了 10 ms

  線程main已收到所有響應結果,所用時間為:332

  兩者相差:222ms。

  對比說明同步代碼塊比同步方法效率更高。

  補充記憶:

  1、synchronized關鍵字的作用域有二種:

  1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法;

  2)是某個類的範圍,synchronized static aStaticMethod{}防止多個線程中不同的實例對象(或者同一個實例對象)同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。

  2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/}(或者synchronized(obj){/*區塊*/}),它的作用域是當前對象;

  3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法為synchronized方法;

  對synchronized(this)的一些理解(很好的解釋了對象鎖,注意其中的this關鍵字)

  一、當兩個並發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。

  二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

  三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

  四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。

  五、以上規則對其它對象鎖同樣適用。

  補充一段代碼,方便對synchronized關鍵字進行測試(簡單修改即可)

  public class TestSynchronized

  {

  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 synchronized void test3()

  {

  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 TestSynchronized myt2 = new TestSynchronized();

  final TestSynchronized myt3 = new TestSynchronized();

  Thread test1 = new Thread( new Runnable() { public void run() { myt2.test2(); } }, "test1" );

  Thread test2 = new Thread( new Runnable() { public void run() { myt2.test3(); } }, "test3" );

  test1.start();;

  test2.start();

  }

  }

  運行結果:

  test1 : 4

  test1 : 3

  test1 : 2

  test1 : 1

  test1 : 0

  test3 : 4

  test3 : 3

  test3 : 2

  test3 : 1

  test3 : 0

  下面我們著重介紹java中的 Sychronized的用法,具體為:同步方法 與 同步塊

  synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。

  1. synchronized 方法:通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:

  public synchronized void accessVal(int newVal);

  synchronized 方法控制對類成員變數的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明為 synchronized 的成員函數中至多只有一個處於可執行狀態(因為至多只有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變數的訪問衝突(只要所有可能訪問類成員變數的方法均被聲明為 synchronized)。

  在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明為static synchronized ,以控制其對類的靜態成員變數的訪問。

  synchronized 方法的缺陷:若將一個大的方法聲明為synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明為 synchronized ,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何 synchronized 方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變數的代碼放到專門的方法中,將其聲明為 synchronized ,並在主方法中調用來解決這一問題,但是 Java 為我們提供了更好的解決辦法,那就是 synchronized 塊。

  2. synchronized 塊:通過 synchronized關鍵字來聲明synchronized 塊。語法如下:

  synchronized(syncObject) {

  //允許訪問控制的代碼

  }

  synchronized 塊是這樣一個代碼塊,其中的代碼必須獲得對象 syncObject (如前所述,可以是類實例或類)的鎖方能執行,具體機制同前所述。由於可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。

  注意:

  在使用synchronized關鍵字時候,應該儘可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因為synchronized程序塊佔有著對象鎖,你休息那麼其他的線程只能一邊等著你醒來執行完了才能執行。不但嚴重影響效率,也不合邏輯。

  同樣,在同步程序塊內調用yeild方法讓出CPU資源也沒有意義,因為你佔用著鎖,其他互斥線程還是無法訪問同步程序塊。當然與同步程序塊無關的線程可以獲得更多的執行時間。

抱歉!評論已關閉.