首頁 Java java教程 Java 並發程式設計學習筆記之Synchronized簡介

Java 並發程式設計學習筆記之Synchronized簡介

Jan 05, 2017 pm 04:20 PM

一、Synchronized的基本使用

  Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。 Synchronized的功能主要有三:(1)確保執行緒互斥的存取同步程式碼(2)確保共享變數的修改能夠及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:

  (1)修飾普通方法

  (2)修飾靜態方法

  (3)修飾代碼塊🎀

〜接下來幾個例子三種使用方式(為了方便比較,三段程式碼除了Synchronized的使用方式不同以外,其他基本上保持一致)。

1、沒有同步的情況:

程式碼段一:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}
登入後複製

執行結果如下,執行緒1和執行緒2同時進入執行狀態,執行緒2執行速度比執行緒1快,所以執行緒2先執行完成,這個過程中線程1和線程2是同時執行的。

Method 1 start

Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end

 2、對普通方法同步:以下程式碼同步段一比較,可以很明顯的看出,執行緒2需要等待執行緒1的method1執行完成才能開始執行method2方法。

Method 1 start

Method 1 execute

Method 1 end

Method 2 start

Method 2 execute

Method 2 end


3、靜態方法(類)同步化靜態方法的同步本質上是對類別的同步(靜態方法本質上是屬於類別的方法,而不是對像上的方法),所以即使test和test2屬於不同的對象,但是它們都屬於SynchronizedTest類別的實例,所以也只能順序的執行method1和method2,不能並發執行。

Method 1 start
Method 1 execute

Method 1 end

Method 2 start

Method 2 execute

Method 2 end

rr

4、代碼塊同步

Method 2 end

線程2都進入了對應的方法開始執行,但是線程2在進入同步區塊之前,需要等待線程1中同步區塊執行完成。

Method 1 start
Method 1 execute
Method 2 start

Method 1 end

Method 2 execute

Method 2 end

、Synchronized 問題還是急先來了解Synchronized的原理,再回頭上面的問題就一目了然了。我們先透過反編譯下面的程式碼來看看Synchronized是如何實現對程式碼區塊進行同步的:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public synchronized void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public synchronized void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}
登入後複製

反編譯結果:




關於這兩條指令的作用,我們直接參考JVM規範中描述:

monitorenter :

package com.paddx.test.concurrent;
  
 public class SynchronizedTest {
   public static synchronized void method1(){
     System.out.println("Method 1 start");
     try {
       System.out.println("Method 1 execute");
       Thread.sleep(3000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 1 end");
   }
  
   public static synchronized void method2(){
     System.out.println("Method 2 start");
     try {
       System.out.println("Method 2 execute");
       Thread.sleep(1000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 2 end");
   }
  
   public static void main(String[] args) {
     final SynchronizedTest test = new SynchronizedTest();
     final SynchronizedTest test2 = new SynchronizedTest();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test.method1();
       }
     }).start();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test2.method2();
       }
     }).start();
   }
 }
登入後複製

   

這段話的大概意思為:

Java 并发编程学习笔记之Synchronized简介每個物件都有監視器鎖定(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試取得monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然後將進入數設為1 ,該線程即為monitor的所有者。

2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0 ,再重新嘗試取得monitor的所有權。

monitorexit: 

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      synchronized (this) {
        System.out.println("Method 1 execute");
        Thread.sleep(3000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      synchronized (this) {
        System.out.println("Method 2 execute");
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}
登入後複製

   

這段話的大概意思是:

執行monitorexit的執行緒必須是objectref所對應的monitor的擁有者。

指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去取得這個 monitor 的所有權。

  透過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是透過一個monitor的物件來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有monitor在同步的區塊或方法中才能呼叫wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

  我們再來看看同步方法的反編譯結果:

原始碼:

package com.paddx.test.concurrent;
 
public class SynchronizedMethod {
  public synchronized void method() {
    System.out.println("Hello World!");
  }
}
登入後複製

  从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

三、运行结果解释

  有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。

1、代码段2结果:

  虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

2、代码段3结果:

  虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

3、代码段4结果:

  对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

四 总结

  Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。


更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

公司安全軟件導致應用無法運行?如何排查和解決? 公司安全軟件導致應用無法運行?如何排查和解決? Apr 19, 2025 pm 04:51 PM

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

如何使用MapStruct簡化系統對接中的字段映射問題? 如何使用MapStruct簡化系統對接中的字段映射問題? Apr 19, 2025 pm 06:21 PM

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

如何優雅地獲取實體類變量名構建數據庫查詢條件? 如何優雅地獲取實體類變量名構建數據庫查詢條件? Apr 19, 2025 pm 11:42 PM

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

如何將姓名轉換為數字以實現排序並保持群組中的一致性? 如何將姓名轉換為數字以實現排序並保持群組中的一致性? Apr 19, 2025 pm 11:30 PM

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? Apr 19, 2025 pm 11:45 PM

在使用IntelliJIDEAUltimate版本啟動Spring...

Java對像如何安全地轉換為數組? Java對像如何安全地轉換為數組? Apr 19, 2025 pm 11:33 PM

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? 使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名構建查詢條件? Apr 19, 2025 pm 09:51 PM

在使用TKMyBatis進行數據庫查詢時,如何優雅地獲取實體類變量名以構建查詢條件,是一個常見的難題。本文將針...

電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? 電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? Apr 19, 2025 pm 11:27 PM

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...

See all articles