最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 并發(fā)編程學習筆記之Synchronized簡介

 更新時間:2016年05月17日 09:09:21   作者:liuxiaopeng  
雖然多線程編程極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個數據庫表中插入不重復的數據,就可能會導致數據庫中插入了相同的數據。今天我們就來一起討論下線程安全問題,以及Java中提供了什么機制來解決線程安全問題。

一、Synchronized的基本使用

  Synchronized是Java中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法。Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改能夠及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:

 ?。?)修飾普通方法

 ?。?)修飾靜態(tài)方法

 ?。?)修飾代碼塊

  接下來我就通過幾個例子程序來說明一下這三種使用方式(為了便于比較,三段代碼除了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();
  }
}

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

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

 2、對普通方法同步:

代碼段二:

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();
  }
}

執(zhí)行結果如下,跟代碼段一比較,可以很明顯的看出,線程2需要等待線程1的method1執(zhí)行完成才能開始執(zhí)行method2方法。

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

3、靜態(tài)方法(類)同步

代碼段三:

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();
   }
 }

  執(zhí)行結果如下,對靜態(tài)方法的同步本質上是對類的同步(靜態(tài)方法本質上是屬于類的方法,而不是對象上的方法),所以即使test和test2屬于不同的對象,但是它們都屬于SynchronizedTest類的實例,所以也只能順序的執(zhí)行method1和method2,不能并發(fā)執(zhí)行。

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

4、代碼塊同步

代碼段四:

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();
  }
}

執(zhí)行結果如下,雖然線程1和線程2都進入了對應的方法開始執(zhí)行,但是線程2在進入同步塊之前,需要等待線程1中同步塊執(zhí)行完成。

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

二、Synchronized 原理

  如果對上面的執(zhí)行結果還有疑問,也先不用急,我們先來了解Synchronized的原理,再回頭上面的問題就一目了然了。我們先通過反編譯下面的代碼來看看Synchronized是如何實現對代碼塊進行同步的:

package com.paddx.test.concurrent;

public class SynchronizedDemo {
  public void method() {
    synchronized (this) {
      System.out.println("Method 1 start");
    }
  }
}

反編譯結果:

關于這兩條指令的作用,我們直接參考JVM規(guī)范中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

這段話的大概意思為:

每個對象有一個監(jiān)視器鎖(monitor)。當monitor被占用時就會處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時嘗試獲取monitor的所有權,過程如下:

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

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

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

monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

這段話的大概意思為:

執(zhí)行monitorexit的線程必須是objectref所對應的monitor的所有者。

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

  通過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是通過一個monitor的對象來完成,其實wait/notify等方法也依賴于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 訪問標志是否被設置,如果設置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個monitor對象。 其實本質上沒有區(qū)別,只是方法的同步是一種隱式的方式來實現,無需通過字節(jié)碼來完成。

三、運行結果解釋

  有了對Synchronized原理的認識,再來看上面的程序就可以迎刃而解了。

1、代碼段2結果:

  雖然method1和method2是不同的方法,但是這兩個方法都進行了同步,并且是通過同一個對象去調用的,所以調用之前都需要先去競爭同一個對象上的鎖(monitor),也就只能互斥的獲取到鎖,因此,method1和method2只能順序的執(zhí)行。

2、代碼段3結果:

  雖然test和test2屬于不同對象,但是test和test2屬于同一個類的不同實例,由于method1和method2都屬于靜態(tài)同步方法,所以調用的時候需要獲取同一個類上monitor(每個類只對應一個class對象),所以也只能順序的執(zhí)行。

3、代碼段4結果:

  對于代碼塊的同步實質上需要獲取Synchronized關鍵字后面括號中對象的monitor,由于這段代碼中括號的內容都是this,而method1和method2又是通過同一的對象去調用的,所以進入同步塊之前需要去競爭同一個對象上的鎖,因此只能順序執(zhí)行同步塊。

四 總結

  Synchronized是Java并發(fā)編程中最常用的用于保證線程安全的方式,其使用相對也比較簡單。但是如果能夠深入了解其原理,對監(jiān)視器鎖等底層知識有所了解,一方面可以幫助我們正確的使用Synchronized關鍵字,另一方面也能夠幫助我們更好的理解并發(fā)編程機制,有助我們在不同的情況下選擇更優(yōu)的并發(fā)策略來完成任務。對平時遇到的各種并發(fā)問題,也能夠從容的應對。

相關文章

  • SpringBoot多環(huán)境日志配置方式

    SpringBoot多環(huán)境日志配置方式

    SpringBoot?默認使用LogBack日志系統(tǒng),默認情況下,SpringBoot項目的日志只會在控制臺輸入,本文給大家介紹SpringBoot多環(huán)境日志配置方式,需要的朋友可以參考下
    2024-08-08
  • DOM解析XML報錯Content is not allowed in prolog解決方案詳解

    DOM解析XML報錯Content is not allowed in prolog解決方案詳解

    這篇文章主要介紹了DOM解析XML報錯解決方案詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-10-10
  • 基于binarywang封裝的微信工具包生成二維碼

    基于binarywang封裝的微信工具包生成二維碼

    這篇文章主要介紹了基于binarywang封裝的微信工具包生成二維碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-11-11
  • Java8 新特性之日期時間對象及一些其他特性

    Java8 新特性之日期時間對象及一些其他特性

    這篇文章主要介紹了Java8 新特性之日期時間對象及一些其他特性,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-01-01
  • Spring Cloud Feign實現文件上傳下載的示例代碼

    Spring Cloud Feign實現文件上傳下載的示例代碼

    Feign框架對于文件上傳消息體格式并沒有做原生支持,需要集成模塊feign-form來實現,本文就詳細的介紹一下如何使用,感興趣的可以了解一下
    2022-02-02
  • JavaWeb中的文件的上傳和下載

    JavaWeb中的文件的上傳和下載

    JavaWeb 文件的上傳和下載是指在Web應用中實現用戶上傳文件到服務器和從服務器下載文件的功能,通過JavaWeb技術,可以方便地實現文件的上傳和下載操作,提供更好的用戶體驗和數據交互,需要的朋友可以參考下
    2023-10-10
  • Java數據結構之堆(優(yōu)先隊列)的實現

    Java數據結構之堆(優(yōu)先隊列)的實現

    堆(優(yōu)先隊列)是一種典型的數據結構,其形狀是一棵完全二叉樹,一般用于求解topk問題。本文將利用Java語言實現堆,感興趣的可以學習一下
    2022-05-05
  • Spring?Cloud?Ribbon?負載均衡使用策略示例詳解

    Spring?Cloud?Ribbon?負載均衡使用策略示例詳解

    Spring?Cloud?Ribbon?是基于Netflix?Ribbon?實現的一套客戶端負載均衡工具,Ribbon客戶端組件提供了一系列的完善的配置,如超時,重試等,這篇文章主要介紹了Spring?Cloud?Ribbon?負載均衡使用策略示例詳解,需要的朋友可以參考下
    2023-03-03
  • MongoDB中ObjectId的誤區(qū)及引起的一系列問題

    MongoDB中ObjectId的誤區(qū)及引起的一系列問題

    這篇文章主要介紹了MongoDB中ObjectId的誤區(qū)及引起的一系列問題,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-12-12
  • 教你代碼中獲取當前?JAR?包的存放位置

    教你代碼中獲取當前?JAR?包的存放位置

    這篇文章主要介紹了如何獲取當前JAR包的存放位置,要獲取當前運行的 JAR 包所存放的位置,可以使用 ProtectionDomain 和 CodeSource 類,本文結合實例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2023-08-08

最新評論

万盛区| 精河县| 洮南市| 盱眙县| 渭南市| 涪陵区| 怀柔区| 明水县| 汝南县| 富平县| 林芝县| 噶尔县| 疏勒县| 北京市| 托里县| 成都市| 叙永县| 保康县| 秭归县| 卢氏县| 汤原县| 苏尼特左旗| 麦盖提县| 丹凤县| 宣汉县| 中方县| 分宜县| 吴江市| 无极县| 昌宁县| 新乡市| 闻喜县| 安化县| 射阳县| 富宁县| 文成县| 文安县| 临颍县| 镇康县| 定州市| 乌鲁木齐县|