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

Java 并發(fā)編程學習筆記之核心理論基礎

 更新時間:2016年05月17日 08:48:48   作者:liuxiaopeng  
編寫優(yōu)質的并發(fā)代碼是一件難度極高的事情。Java語言從第一版本開始內(nèi)置了對多線程的支持,這一點在當年是非常了不起的,但是當我們對并發(fā)編程有了更深刻的認識和更多的實踐后,實現(xiàn)并發(fā)編程就有了更多的方案和更好的選擇。本文是對并發(fā)編程的核心理論做了下小結

并發(fā)編程是Java程序員最重要的技能之一,也是最難掌握的一種技能。它要求編程者對計算機最底層的運作原理有深刻的理解,同時要求編程者邏輯清晰、思維縝密,這樣才能寫出高效、安全、可靠的多線程并發(fā)程序。本系列會從線程間協(xié)調(diào)的方式(wait、notify、notifyAll)、Synchronized及Volatile的本質入手,詳細解釋JDK為我們提供的每種并發(fā)工具和底層實現(xiàn)機制。在此基礎上,我們會進一步分析java.util.concurrent包的工具類,包括其使用方式、實現(xiàn)源碼及其背后的原理。本文是該系列的第一篇文章,是這系列中最核心的理論部分,之后的文章都會以此為基礎來分析和解釋。

一、共享性

  數(shù)據(jù)共享性是線程安全的主要原因之一。如果所有的數(shù)據(jù)只是在線程內(nèi)有效,那就不存在線程安全性問題,這也是我們在編程的時候經(jīng)常不需要考慮線程安全的主要原因之一。但是,在多線程編程中,數(shù)據(jù)共享是不可避免的。最典型的場景是數(shù)據(jù)庫中的數(shù)據(jù),為了保證數(shù)據(jù)的一致性,我們通常需要共享同一個數(shù)據(jù)庫中數(shù)據(jù),即使是在主從的情況下,訪問的也同一份數(shù)據(jù),主從只是為了訪問的效率和數(shù)據(jù)安全,而對同一份數(shù)據(jù)做的副本。我們現(xiàn)在,通過一個簡單的示例來演示多線程下共享數(shù)據(jù)導致的問題:

代碼段一:

package com.paddx.test.concurrent;

public class ShareData {
  public static int count = 0;

  public static void main(String[] args) {
    final ShareData data = new ShareData();
    for (int i = 0; i < 10; i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            //進入的時候暫停1毫秒,增加并發(fā)問題出現(xiàn)的幾率
            Thread.sleep(1);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          for (int j = 0; j < 100; j++) {
            data.addCount();
          }
          System.out.print(count + " ");
        }
      }).start();

    }
    try {
      //主程序暫停3秒,以保證上面的程序執(zhí)行完成
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("count=" + count);
  }

  public void addCount() {
    count++;
  }
}

  上述代碼的目的是對count進行加一操作,執(zhí)行1000次,不過這里是通過10個線程來實現(xiàn)的,每個線程執(zhí)行100次,正常情況下,應該輸出1000。不過,如果你運行上面的程序,你會發(fā)現(xiàn)結果卻不是這樣。下面是某次的執(zhí)行結果(每次運行的結果不一定相同,有時候也可能獲取到正確的結果):

可以看出,對共享變量操作,在多線程環(huán)境下很容易出現(xiàn)各種意想不到的的結果。

二、互斥性

  資源互斥是指同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。我們通常允許多個線程同時對數(shù)據(jù)進行讀操作,但同一時間內(nèi)只允許一個線程對數(shù)據(jù)進行寫操作。所以我們通常將鎖分為共享鎖和排它鎖,也叫做讀鎖和寫鎖。如果資源不具有互斥性,即使是共享資源,我們也不需要擔心線程安全。例如,對于不可變的數(shù)據(jù)共享,所有線程都只能對其進行讀操作,所以不用考慮線程安全問題。但是對共享數(shù)據(jù)的寫操作,一般就需要保證互斥性,上述例子中就是因為沒有保證互斥性才導致數(shù)據(jù)的修改產(chǎn)生問題。Java 中提供多種機制來保證互斥性,最簡單的方式是使用Synchronized。現(xiàn)在我們在上面程序中加上Synchronized再執(zhí)行:

代碼段二:

package com.paddx.test.concurrent;

public class ShareData {
  public static int count = 0;

  public static void main(String[] args) {
    final ShareData data = new ShareData();
    for (int i = 0; i < 10; i++) {
      new Thread(new Runnable() {
        @Override
        public void run() {
          try {
            //進入的時候暫停1毫秒,增加并發(fā)問題出現(xiàn)的幾率
            Thread.sleep(1);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          for (int j = 0; j < 100; j++) {
            data.addCount();
          }
          System.out.print(count + " ");
        }
      }).start();

    }
    try {
      //主程序暫停3秒,以保證上面的程序執(zhí)行完成
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("count=" + count);
  }

  /**
   * 增加 synchronized 關鍵字
   */
  public synchronized void addCount() {
    count++;
  }
}

  現(xiàn)在再執(zhí)行上述代碼,會發(fā)現(xiàn)無論執(zhí)行多少次,返回的最終結果都是1000。

三、原子性

  原子性就是指對數(shù)據(jù)的操作是一個獨立的、不可分割的整體。換句話說,就是一次操作,是一個連續(xù)不可中斷的過程,數(shù)據(jù)不會執(zhí)行的一半的時候被其他線程所修改。保證原子性的最簡單方式是操作系統(tǒng)指令,就是說如果一次操作對應一條操作系統(tǒng)指令,這樣肯定可以能保證原子性。但是很多操作不能通過一條指令就完成。例如,對long類型的運算,很多系統(tǒng)就需要分成多條指令分別對高位和低位進行操作才能完成。還比如,我們經(jīng)常使用的整數(shù) i++ 的操作,其實需要分成三個步驟:(1)讀取整數(shù) i 的值;(2)對 i 進行加一操作;(3)將結果寫回內(nèi)存。這個過程在多線程下就可能出現(xiàn)如下現(xiàn)象:

這也是代碼段一執(zhí)行的結果為什么不正確的原因。對于這種組合操作,要保證原子性,最常見的方式是加鎖,如Java中的Synchronized或Lock都可以實現(xiàn),代碼段二就是通過Synchronized實現(xiàn)的。除了鎖以外,還有一種方式就是CAS(Compare And Swap),即修改數(shù)據(jù)之前先比較與之前讀取到的值是否一致,如果一致,則進行修改,如果不一致則重新執(zhí)行,這也是樂觀鎖的實現(xiàn)原理。不過CAS在某些場景下不一定有效,比如另一線程先修改了某個值,然后再改回原來值,這種情況下,CAS是無法判斷的。

四、可見性

   要理解可見性,需要先對JVM的內(nèi)存模型有一定的了解,JVM的內(nèi)存模型與操作系統(tǒng)類似,如圖所示:

從這個圖中我們可以看出,每個線程都有一個自己的工作內(nèi)存(相當于CPU高級緩沖區(qū),這么做的目的還是在于進一步縮小存儲系統(tǒng)與CPU之間速度的差異,提高性能),對于共享變量,線程每次讀取的是工作內(nèi)存中共享變量的副本,寫入的時候也直接修改工作內(nèi)存中副本的值,然后在某個時間點上再將工作內(nèi)存與主內(nèi)存中的值進行同步。這樣導致的問題是,如果線程1對某個變量進行了修改,線程2卻有可能看不到線程1對共享變量所做的修改。通過下面這段程序我們可以演示一下不可見的問題:

package com.paddx.test.concurrent;

public class VisibilityTest {
  private static boolean ready;
  private static int number;

  private static class ReaderThread extends Thread {
    public void run() {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      if (!ready) {
        System.out.println(ready);
      }
      System.out.println(number);
    }
  }

  private static class WriterThread extends Thread {
    public void run() {
      try {
        Thread.sleep(10);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      number = 100;
      ready = true;
    }
  }

  public static void main(String[] args) {
    new WriterThread().start();
    new ReaderThread().start();
  }
}

從直觀上理解,這段程序應該只會輸出100,ready的值是不會打印出來的。實際上,如果多次執(zhí)行上面代碼的話,可能會出現(xiàn)多種不同的結果,下面是我運行出來的某兩次的結果:

當然,這個結果也只能說是有可能是可見性造成的,當寫線程(WriterThread)設置ready=true后,讀線程(ReaderThread)看不到修改后的結果,所以會打印false,對于第二個結果,也就是執(zhí)行if (!ready)時還沒有讀取到寫線程的結果,但執(zhí)行System.out.println(ready)時讀取到了寫線程執(zhí)行的結果。不過,這個結果也有可能是線程的交替執(zhí)行所造成的。Java 中可通過Synchronized或Volatile來保證可見性,具體細節(jié)會在后續(xù)的文章中分析。

五、順序性

  為了提高性能,編譯器和處理器可能會對指令做重排序。重排序可以分為三種:

 ?。?)編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

  (2)指令級并行的重排序?,F(xiàn)代處理器采用了指令級并行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應機器指令的執(zhí)行順序。
 ?。?)內(nèi)存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

  我們可以直接參考一下JSR 133 中對重排序問題的描述:

       ?。?)                    (2)

先看上圖中的(1)源碼部分,從源碼來看,要么指令 1 先執(zhí)行要么指令 3先執(zhí)行。如果指令 1 先執(zhí)行,r2不應該能看到指令 4 中寫入的值。如果指令 3 先執(zhí)行,r1不應該能看到指令 2 寫的值。但是運行結果卻可能出現(xiàn)r2==2,r1==1的情況,這就是“重排序”導致的結果。上圖(2)即是一種可能出現(xiàn)的合法的編譯結果,編譯后,指令1和指令2的順序可能就互換了。因此,才會出現(xiàn)r2==2,r1==1的結果。Java 中也可通過Synchronized或Volatile來保證順序性。

六 總結

  本文對Java 并發(fā)編程中的理論基礎進行了講解,有些東西在后續(xù)的分析中還會做更詳細的討論,如可見性、順序性等。后續(xù)的文章都會以本章內(nèi)容作為理論基礎來討論。如果大家能夠很好的理解上述內(nèi)容,相信無論是去理解其他并發(fā)編程的文章還是在平時的并發(fā)編程的工作中,都能夠對大家有很好的幫助。

相關文章

  • Apache CXF如何把wsdl生成java代碼

    Apache CXF如何把wsdl生成java代碼

    這篇文章主要介紹了Apache CXF如何把wsdl生成java代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • Java實現(xiàn)多線程斷點下載

    Java實現(xiàn)多線程斷點下載

    這篇文章主要為大家詳細介紹了Java實現(xiàn)多線程斷點下載的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-03-03
  • 手把手教你如何搭建SpringBoot+Vue前后端分離

    手把手教你如何搭建SpringBoot+Vue前后端分離

    這篇文章主要介紹了手把手教你如何搭建SpringBoot+Vue前后端分離,前后端分離是目前開發(fā)中常用的開發(fā)模式,達成充分解耦,需要的朋友可以參考下
    2023-03-03
  • 簡單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別

    簡單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別

    這篇文章主要介紹了簡單了解JAVA SimpleDateFormat yyyy和YYYY的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • 分布式系統(tǒng)中的降級熔斷設計問題面試

    分布式系統(tǒng)中的降級熔斷設計問題面試

    這篇文章主要為大家介紹了分布式系統(tǒng)中的降級熔斷設計問題面試解答,有需要的朋友可以借鑒參考下,希望能有所幫助,祝大家多多進步,早日升職加薪
    2022-03-03
  • Spring中的@ComponentScan注解詳解

    Spring中的@ComponentScan注解詳解

    這篇文章主要介紹了Spring中的@ComponentScan注解詳解,ComponentScan做的事情就是告訴Spring從哪里找到bean,由你來定義哪些包需要被掃描,一旦你指定了,Spring將會在被指定的包及其下級包中尋找bean,需要的朋友可以參考下
    2024-01-01
  • Java接口方法默認靜態(tài)實現(xiàn)代碼實例

    Java接口方法默認靜態(tài)實現(xiàn)代碼實例

    這篇文章主要介紹了Java接口方法默認靜態(tài)實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • springBoot項目啟動類啟動無法訪問的解決方法

    springBoot項目啟動類啟動無法訪問的解決方法

    這篇文章主要介紹了springBoot項目啟動類啟動無法訪問的解決方法,需要的朋友可以參考下
    2018-10-10
  • Java詳解如何將excel數(shù)據(jù)轉為樹形

    Java詳解如何將excel數(shù)據(jù)轉為樹形

    在平常的辦公工作中,excel數(shù)據(jù)的操作是最常見的需求,今天就來看一下通過Java如何來實現(xiàn)將excel數(shù)據(jù)轉為樹形,感興趣的朋友可以了解下
    2022-08-08
  • BeanDefinitionRegistryPostProcessor如何動態(tài)注冊Bean到Spring

    BeanDefinitionRegistryPostProcessor如何動態(tài)注冊Bean到Spring

    這篇文章主要介紹了BeanDefinitionRegistryPostProcessor如何動態(tài)注冊Bean到Spring,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03

最新評論

宁武县| 乡宁县| 西藏| 平遥县| 洛南县| 临洮县| 宁陕县| 漳浦县| 延庆县| 揭西县| 台东市| 江口县| 汤原县| 庄浪县| 漯河市| 米林县| 明光市| 监利县| 白银市| 兖州市| 日喀则市| 呼伦贝尔市| 南京市| 大渡口区| 大宁县| 新民市| 晋江市| 珠海市| 江达县| 镇沅| 衡水市| 密山市| 濮阳县| 萨迦县| 涪陵区| 华宁县| 景泰县| 保山市| 贺州市| 武陟县| 满洲里市|