Java線程同步和併發第1部分

通過優銳課核心java學習筆記中,我們可以看到,碼了很多專業的相關知識, 分享給大家參考學習。我們將分兩部分介紹Java中的線程同步,以更好地理解Java的內存模型。

介紹

Java線程同步和併發是複雜應用程序各個設計階段中討論最多的主題。 線程,同步技術有很多方面,它們可以在應用程序中實現高併發性。 多年來,CPU(多核處理器,寄存器,高速緩存存儲器和主內存(RAM))的發展已導致通常是開發人員往往忽略的某些領域-例如線程上下文,上下文切換,變量可見性,JVM內存 型號與CPU內存型號。

在本系列中,我們將討論Java內存模型的各個方面,包括它如何影響線程上下文,Java中實現併發的同步技術,競爭條件等。在本文中,我們將重點討論線程,同步的概念 技術以及Java和我們的CPU的內存模型。

概括

在深入研究線程和同步這一主題之前,讓我們快速回顧一下一些與線程相關的術語和概念。

1.Lock —鎖是線程同步機制。
2.

  1. Java中的每個對象都有一個與之關聯的固有鎖。線程使用對象的監視器進行鎖定或解鎖。鎖可以視爲邏輯上是內存中對象標頭的一部分的數據。有關監視器無法實現的擴展功能,請參見ReentrantLock。

3.Java中的每個對象都有同步方法,wait()和notify()[也notifyAll()]。任何調用這些方法的線程都使用其監視器獲得該對象的鎖。必須使用synced關鍵字來調用此方法,否則將拋出IllegealMonitorStateException。

4.信號是一種通知線程應該繼續執行的方法。這是使用對象方法wait(),notify()和notifyAll()實現的。調用方法notify()或notifyAll()可以使線程單一以喚醒後臺線程(通過調用方法wait())。

5.丟失信號-方法notify()和notifyAll()不保存方法調用,也不知道其他線程是否調用過wait()。如果一個線程在被通知的線程調用了wait()之前調用了notify(),則該信號將被等待的線程錯過。這可能導致線程無休止地等待,因爲它錯過了信號

6.Runnable是一個功能性接口,可以由應用程序中的任何類實現,以便線程可以執行它。

7.volatile是分配給變量以使類成爲線程安全的另一個關鍵字。要了解此關鍵字的用法,必須瞭解CPU體系結構和JVM內存模型。我們稍後再討論。

8.ThreadLocal允許創建只能由所有者線程讀取/寫入的變量。這用於使代碼安全。

9.Thread Pool是線程的集合,線程將在其中執行任務。線程的創建和維護非常受服務控制。在Java中,線程池由ExecutorService的實例表示。

10.ThreadGroup是該類提供一種用於將多個線程收集到單個對象中的機制,並允許我們一次操縱/控制這些線程。

11.Daemon線程-這些線程在後臺運行。守護程序線程的一個很好的例子是Java Garbage Collector。 JVM在退出以完成其執行之前不等待守護程序線程(而JVM在等待非守護程序線程或用戶線程完成其執行之前)。

12.synchronized-關鍵字,用於在多個線程必須在併發模式下執行同一功能時控制單個線程執行代碼。此關鍵字可用於方法和代碼塊以實現線程安全。請注意,此關鍵字沒有超時,因此有可能發生死鎖情況。

13.死鎖-一種情況,一個或多個線程正在等待對象鎖被另一線程釋放。導致死鎖的可能情況是線程正在相互等待釋放鎖的地方!

14.虛假喚醒-由於莫名其妙的原因,即使未調用notify()和notifyAll(),線程也有可能喚醒。這是一個虛假的喚醒。爲了解決此問題,喚醒的線程圍繞自旋鎖中的條件自旋。

Java

1
public synchronized doWait() {
2
  while(!wasSignalled) { // spin-lock check to avoid spurious wake up calls
3
    wait();
4
  }
5
  // do something
6
}
7

8
public synchronized doNotify() {
9
  wasSignalled = true;
10
  notify();
11
}

線程匱乏

當沒有爲某個線程分配CPU時間(因爲其他線程佔用了所有線程)時,就會發生線程飢餓。 (例如,在某個對象上等待的線程(調用了wait())保持無限期等待,因爲其他線程不斷被喚醒(通過調用notify())。

爲了減輕這種情況,我們可以使用Thread.setPriority(int priority)方法爲線程設置優先級。 優先級參數必須在Thread.MIN_PRIORITY到Thread.MAX_PRIORITY之間的設置範圍內。 查看官方線程文檔以獲取有關線程優先級的更多信息。

鎖定界面與同步關鍵字

1.無法在同步塊或方法中具有超時。 這可能會在應用程序似乎掛起,死鎖等情況下結束。同步塊必須僅包含在單個方法中。

  1. Lock接口的實例可以使用單獨的方法調用lock()和unlock()。 此外,鎖也可以具有超時。 與synced關鍵字相比,這是兩個很大的好處。
Java

1
class CustomLock {
2
3
  private boolean isLocked = false;
4
5
  public synchronized void lock() 
6
            throws InterruptedException {
7
    
8
    isLocked = true;
9
    while(isLocked) {
10
      // calling thread releases the lock it holds on the monitor
11
      // object. Multiple threads can call wait() as the monitor is released.
12
      wait();
13
    }
14
  }
15
16
  public synchronized void unlock() {
17
    isLocked = false;
18
    notify();
19
    // only after the lock is released in this block, the wait() block
20
    // above can re-acquire the lock on this object's monitor.
21
  }
22
}

線程執行

我們可以通過兩種方式在Java中執行線程。 他們是:

1.擴展Thread類並調用start()方法。 (這不是從Thread子類化類的首選方式,因爲它減少了添加該類更多功能的範圍。)

2.實現Runnable或Callable接口 這兩個接口都是功能性接口,這意味着它們都只定義了一個抽象方法。 (將來也可以通過實現其他接口來擴展作爲類的首選方法。)

可運行的界面

這是用於通過線程執行特定任務的基本接口。 此接口僅描述一種方法,稱爲帶有無效返回類型的run()。 如果必須在線程中執行任何功能,但不期望返回類型,請實現此接口。 基本上,在失敗的情況下,無法檢索線程的結果或任何異常或錯誤。

通話界面

這是一個接口,除了獲得執行結果之外,還用於通過線程執行特定任務。 該接口遵循泛型。 對於實現此接口的類,它僅描述了一種稱爲call()的方法,並描述了返回類型。 如果必須在線程中執行任何功能並且必須捕獲執行結果,請實現此接口。
同步技術

如上所述,可以使用同步關鍵字或使用Lock實例來同步線程。 Lock接口的基本實現是ReentrantLock類。 同樣,用於讀/寫操作的Lock接口也有所不同。

當線程試圖讀取或寫入資源時,這有助於應用程序實現更高的併發性。 此實現稱爲ReentrantReadWriteLock。 這兩個類之間的主要區別如下所示:

Class ReentrantLock ReentrantReadWriteLock類

只允許訪問1個線程以進行讀取或寫入,但不能同時訪問兩者。 如果操作正在讀取資源,則一次允許訪問多個/所有線程。

如果該操作是寫操作,則一次只能訪問一個線程。

鎖定讀寫操作的資源,使操作互斥。 鎖定讀寫操作的資源,使操作互斥。

由於資源被鎖定(甚至用於讀取操作),因此降低了性能。 在性能方面更好,因爲它爲要執行讀取操作的所有線程提供併發訪問權限。

請參閱下面的ReentrantReadWriteLock示例,以瞭解如何在僅允許一個線程更新資源的同時實現對資源的併發讀取。

注意:資源可以是應用程序中各種線程嘗試同時訪問的任何數據。

Java

1
public class ConcurrentReadWriteResourceExample {
2

3
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
4
    private ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
5
    private ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
6

7
    private void readResource() {
8
        readLock.lock();
9
        // read the resource from a file, cache, database or from memory
10
        // this block can be accessed by 'N' threads concurrently for reading
11
        readLock.unlock();
12
    }
13

14
    private void writeResource(String value) {
15
        writeLock.lock();
16
        // write or update value to either a file, cache, database or from memory
17
        // this block can be accessed by at-most '1' thread at a time for writing
18
        writeLock.unlock();
19
    }
20
}

創建上述類的一個實例,並將其傳遞給多個線程; 將處理以下內容:

'N'個線程使用readLock或最多一個線程使用writeLock。
讀或寫都不會同時發生。

Java內存模型和CPU

有關Java和CPU內存模型的說明將幫助我們更好地瞭解對象和變量在Java堆/線程堆棧中的存儲方式以及實際CPU內存的存儲方式。 現代的CPU由寄存器組成,這些寄存器充當處理器本身的直接存儲器,高速緩存存儲器-每個處理器都有一個高速緩存層來存儲數據,最後是存在應用程序數據的RAM或主存儲器。

JVM與CPU內存模型

在硬件或CPU上,線程堆棧和堆都位於主內存中。 線程堆棧和堆的某些部分有時可能會出現在CPU緩存和內部寄存器中。 以下是由於上述體系結構而可能發生的問題:

1,並非所有訪問變量的線程都立即看到對共享變量的線程更新(寫)的可見性。
2.讀取,檢查和更新共享變量的數據時的種族條件。

volatile關鍵字

volatile關鍵字是Java 5中引入的,在實現線程安全方面有重要的用途。 此關鍵字可用於基元和對象。 在變量上使用volatile關鍵字可確保在更新後直接從主存儲器讀取給定變量並將其寫回主存儲器。

ThreadLocal類別

線程同步的最後一個主題是Lock是Java類ThreadLocal之後的主題。 此類可創建只能由同一線程讀取/寫入的變量。 這爲我們提供了一種通過定義線程局部變量來實現線程安全的簡單方法。 ThreadLocal在線程池或ExecutorService中有重要用途,因此每個線程都使用自己的某些資源或對象的實例。

例如,對於每個線程,都需要一個單獨的數據庫連接,或者一個單獨的計數器。 在這種情況下,ThreadLocal會有所幫助。 這在Spring Boot應用程序中也使用,其中爲每個傳入呼叫設置了用戶上下文(Spring Security),並且將通過各種實例在線程流之間共享用戶上下文。 在以下情況下使用ThreadLocal

線程限制。
每個線程的數據性能。
每個線程上下文。

Java

1
/**
2
 * This is a demo class only. The ThreadLocal snippet can be applied
3
 * to any number of threads and you can see that each thread gets it's
4
 * own instance of the ThreadLocal. This achieves thread safety.
5
*/
6
public class ThreadLocalDemo {
7

8
    public static void main(String...args) {
9
        ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
10
            protected String initialValue() {
11
                return "Hello World!";
12
            }
13
        };
14

15
        // below line prints "Hello World!"
16
        System.out.println(threadLocal.get());
17

18
        // below line sets new data into ThreadLocal instance
19
        threadLocal.set("Good bye!!!");
20

21
        // below line prints "Good bye!!!"
22
        System.out.println(threadLocal.get());
23

24
        // below line removes the previously set message
25
        threadLocal.remove();
26

27
        // below line prints "Hello World!" as the initial value will be
28
        // applied again
29
        System.out.println(threadLocal.get());
30
    }
31
}

線程同步和相關概念就是這樣。 併發將在本文的第2部分中介紹。

喜歡這篇文章的可以點個贊,歡迎大家留言評論,記得關注我,每天持續更新技術乾貨、職場趣事、海量面試資料等等
 > 如果你對java技術很感興趣也可以交流學習,共同學習進步。 
不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代

文章寫道這裏,歡迎完善交流。最後奉上近期整理出來的一套完整的java架構思維導圖,分享給大家對照知識點參考學習。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java乾貨

Java線程同步和併發第1部分

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章