激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

香港云服务器
服務器之家 - 編程語言 - JAVA教程 - Java 并發編程學習筆記之核心理論基礎

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

2020-04-29 12:09liuxiaopeng JAVA教程

編寫優質的并發代碼是一件難度極高的事情。Java語言從第一版本開始內置了對多線程的支持,這一點在當年是非常了不起的,但是當我們對并發編程有了更深刻的認識和更多的實踐后,實現并發編程就有了更多的方案和更好的選擇

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

一、共享性

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

代碼段一:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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毫秒,增加并發問題出現的幾率
            Thread.sleep(1);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          for (int j = 0; j < 100; j++) {
            data.addCount();
          }
          System.out.print(count + " ");
        }
      }).start();
 
    }
    try {
      //主程序暫停3秒,以保證上面的程序執行完成
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("count=" + count);
  }
 
  public void addCount() {
    count++;
  }
}

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

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

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

二、互斥性

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

代碼段二:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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毫秒,增加并發問題出現的幾率
            Thread.sleep(1);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          for (int j = 0; j < 100; j++) {
            data.addCount();
          }
          System.out.print(count + " ");
        }
      }).start();
 
    }
    try {
      //主程序暫停3秒,以保證上面的程序執行完成
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("count=" + count);
  }
 
  /**
   * 增加 synchronized 關鍵字
   */
  public synchronized void addCount() {
    count++;
  }
}

  現在再執行上述代碼,會發現無論執行多少次,返回的最終結果都是1000。

三、原子性

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

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

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

四、可見性

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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的值是不會打印出來的。實際上,如果多次執行上面代碼的話,可能會出現多種不同的結果,下面是我運行出來的某兩次的結果:

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

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

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

五、順序性

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

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

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

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

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

       ?。?)                   ?。?)

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

六 總結

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

延伸 · 閱讀

精彩推薦
415
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 日韩大片在线永久观看视频网站免费 | 日韩激情在线视频 | 在线日韩av电影 | 国产精品夜色视频一级区 | 国产肥熟 | 久久精品视频国产 | 国产高潮好爽受不了了夜色 | 国内精品免费一区二区2001 | 久久久久夜色精品国产老牛91 | 久久久久久久久久性 | 国产一级一级片 | 乱淫67194| 国产精品入口夜色视频大尺度 | 黄www片| 国产在线一级视频 | 亚洲看片网 | 91网站免费在线观看 | 国产亚洲精品久久久久5区 综合激情网 | 成人高清在线 | 日韩一级片 | 亚洲精品在线观看免费 | 日韩在线欧美在线 | 手机黄网www8xcn| 一级免费观看 | 欧美日韩色 | 国产一区日韩精品 | 色老师影院| 免费在线观看一级片 | 成人在线97 | wwwxxx国产 | 久综合色 | 99seav| 久草视频国产在线 | 97久久人人超碰caoprom | 精品久久久久久久久久久久久 | 国产女厕一区二区三区在线视 | 毛片视频免费观看 | 天堂二区 | 精品成人免费一区二区三区 | 欧美日韩在线播放 | 999久久国精品免费观看网站 |