溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java線程同步和并發(fā)第1部分

發(fā)布時(shí)間:2020-03-04 00:58:27 來(lái)源:網(wǎng)絡(luò) 閱讀:170 作者:wx5deb0084464f6 欄目:編程語(yǔ)言

通過(guò)優(yōu)銳課核心java學(xué)習(xí)筆記中,我們可以看到,碼了很多專(zhuān)業(yè)的相關(guān)知識(shí), 分享給大家參考學(xué)習(xí)。我們將分兩部分介紹Java中的線程同步,以更好地理解Java的內(nèi)存模型。

介紹

Java線程同步和并發(fā)是復(fù)雜應(yīng)用程序各個(gè)設(shè)計(jì)階段中討論最多的主題。 線程,同步技術(shù)有很多方面,它們可以在應(yīng)用程序中實(shí)現(xiàn)高并發(fā)性。 多年來(lái),CPU(多核處理器,寄存器,高速緩存存儲(chǔ)器和主內(nèi)存(RAM))的發(fā)展已導(dǎo)致通常是開(kāi)發(fā)人員往往忽略的某些領(lǐng)域-例如線程上下文,上下文切換,變量可見(jiàn)性,JVM內(nèi)存 型號(hào)與CPU內(nèi)存型號(hào)。

在本系列中,我們將討論Java內(nèi)存模型的各個(gè)方面,包括它如何影響線程上下文,Java中實(shí)現(xiàn)并發(fā)的同步技術(shù),競(jìng)爭(zhēng)條件等。在本文中,我們將重點(diǎn)討論線程,同步的概念 技術(shù)以及Java和我們的CPU的內(nèi)存模型。

概括

在深入研究線程和同步這一主題之前,讓我們快速回顧一下一些與線程相關(guān)的術(shù)語(yǔ)和概念。

1.Lock —鎖是線程同步機(jī)制。
2.

  1. Java中的每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的固有鎖。線程使用對(duì)象的監(jiān)視器進(jìn)行鎖定或解鎖。鎖可以視為邏輯上是內(nèi)存中對(duì)象標(biāo)頭的一部分的數(shù)據(jù)。有關(guān)監(jiān)視器無(wú)法實(shí)現(xiàn)的擴(kuò)展功能,請(qǐng)參見(jiàn)ReentrantLock。

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

4.信號(hào)是一種通知線程應(yīng)該繼續(xù)執(zhí)行的方法。這是使用對(duì)象方法wait(),notify()和notifyAll()實(shí)現(xiàn)的。調(diào)用方法notify()或notifyAll()可以使線程單一以喚醒后臺(tái)線程(通過(guò)調(diào)用方法wait())。

5.丟失信號(hào)-方法notify()和notifyAll()不保存方法調(diào)用,也不知道其他線程是否調(diào)用過(guò)wait()。如果一個(gè)線程在被通知的線程調(diào)用了wait()之前調(diào)用了notify(),則該信號(hào)將被等待的線程錯(cuò)過(guò)。這可能導(dǎo)致線程無(wú)休止地等待,因?yàn)樗e(cuò)過(guò)了信號(hào)

6.Runnable是一個(gè)功能性接口,可以由應(yīng)用程序中的任何類(lèi)實(shí)現(xiàn),以便線程可以執(zhí)行它。

7.volatile是分配給變量以使類(lèi)成為線程安全的另一個(gè)關(guān)鍵字。要了解此關(guān)鍵字的用法,必須了解CPU體系結(jié)構(gòu)和JVM內(nèi)存模型。我們稍后再討論。

8.ThreadLocal允許創(chuàng)建只能由所有者線程讀取/寫(xiě)入的變量。這用于使代碼安全。

9.Thread Pool是線程的集合,線程將在其中執(zhí)行任務(wù)。線程的創(chuàng)建和維護(hù)非常受服務(wù)控制。在Java中,線程池由ExecutorService的實(shí)例表示。

10.ThreadGroup是該類(lèi)提供一種用于將多個(gè)線程收集到單個(gè)對(duì)象中的機(jī)制,并允許我們一次操縱/控制這些線程。

11.Daemon線程-這些線程在后臺(tái)運(yùn)行。守護(hù)程序線程的一個(gè)很好的例子是Java Garbage Collector。 JVM在退出以完成其執(zhí)行之前不等待守護(hù)程序線程(而JVM在等待非守護(hù)程序線程或用戶(hù)線程完成其執(zhí)行之前)。

12.synchronized-關(guān)鍵字,用于在多個(gè)線程必須在并發(fā)模式下執(zhí)行同一功能時(shí)控制單個(gè)線程執(zhí)行代碼。此關(guān)鍵字可用于方法和代碼塊以實(shí)現(xiàn)線程安全。請(qǐng)注意,此關(guān)鍵字沒(méi)有超時(shí),因此有可能發(fā)生死鎖情況。

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

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

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
}

線程匱乏

當(dāng)沒(méi)有為某個(gè)線程分配CPU時(shí)間(因?yàn)槠渌€程占用了所有線程)時(shí),就會(huì)發(fā)生線程饑餓。 (例如,在某個(gè)對(duì)象上等待的線程(調(diào)用了wait())保持無(wú)限期等待,因?yàn)槠渌€程不斷被喚醒(通過(guò)調(diào)用notify())。

為了減輕這種情況,我們可以使用Thread.setPriority(int priority)方法為線程設(shè)置優(yōu)先級(jí)。 優(yōu)先級(jí)參數(shù)必須在Thread.MIN_PRIORITY到Thread.MAX_PRIORITY之間的設(shè)置范圍內(nèi)。 查看官方線程文檔以獲取有關(guān)線程優(yōu)先級(jí)的更多信息。

鎖定界面與同步關(guān)鍵字

1.無(wú)法在同步塊或方法中具有超時(shí)。 這可能會(huì)在應(yīng)用程序似乎掛起,死鎖等情況下結(jié)束。同步塊必須僅包含在單個(gè)方法中。

  1. Lock接口的實(shí)例可以使用單獨(dú)的方法調(diào)用lock()和unlock()。 此外,鎖也可以具有超時(shí)。 與synced關(guān)鍵字相比,這是兩個(gè)很大的好處。
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
}

線程執(zhí)行

我們可以通過(guò)兩種方式在Java中執(zhí)行線程。 他們是:

1.擴(kuò)展Thread類(lèi)并調(diào)用start()方法。 (這不是從Thread子類(lèi)化類(lèi)的首選方式,因?yàn)樗鼫p少了添加該類(lèi)更多功能的范圍。)

2.實(shí)現(xiàn)Runnable或Callable接口 這兩個(gè)接口都是功能性接口,這意味著它們都只定義了一個(gè)抽象方法。 (將來(lái)也可以通過(guò)實(shí)現(xiàn)其他接口來(lái)擴(kuò)展作為類(lèi)的首選方法。)

可運(yùn)行的界面

這是用于通過(guò)線程執(zhí)行特定任務(wù)的基本接口。 此接口僅描述一種方法,稱(chēng)為帶有無(wú)效返回類(lèi)型的run()。 如果必須在線程中執(zhí)行任何功能,但不期望返回類(lèi)型,請(qǐng)實(shí)現(xiàn)此接口。 基本上,在失敗的情況下,無(wú)法檢索線程的結(jié)果或任何異?;蝈e(cuò)誤。

通話界面

這是一個(gè)接口,除了獲得執(zhí)行結(jié)果之外,還用于通過(guò)線程執(zhí)行特定任務(wù)。 該接口遵循泛型。 對(duì)于實(shí)現(xiàn)此接口的類(lèi),它僅描述了一種稱(chēng)為call()的方法,并描述了返回類(lèi)型。 如果必須在線程中執(zhí)行任何功能并且必須捕獲執(zhí)行結(jié)果,請(qǐng)實(shí)現(xiàn)此接口。
同步技術(shù)

如上所述,可以使用同步關(guān)鍵字或使用Lock實(shí)例來(lái)同步線程。 Lock接口的基本實(shí)現(xiàn)是ReentrantLock類(lèi)。 同樣,用于讀/寫(xiě)操作的Lock接口也有所不同。

當(dāng)線程試圖讀取或?qū)懭胭Y源時(shí),這有助于應(yīng)用程序?qū)崿F(xiàn)更高的并發(fā)性。 此實(shí)現(xiàn)稱(chēng)為ReentrantReadWriteLock。 這兩個(gè)類(lèi)之間的主要區(qū)別如下所示:

Class ReentrantLock ReentrantReadWriteLock類(lèi)

只允許訪問(wèn)1個(gè)線程以進(jìn)行讀取或?qū)懭?,但不能同時(shí)訪問(wèn)兩者。 如果操作正在讀取資源,則一次允許訪問(wèn)多個(gè)/所有線程。

如果該操作是寫(xiě)操作,則一次只能訪問(wèn)一個(gè)線程。

鎖定讀寫(xiě)操作的資源,使操作互斥。 鎖定讀寫(xiě)操作的資源,使操作互斥。

由于資源被鎖定(甚至用于讀取操作),因此降低了性能。 在性能方面更好,因?yàn)樗鼮橐獔?zhí)行讀取操作的所有線程提供并發(fā)訪問(wèn)權(quán)限。

請(qǐng)參閱下面的ReentrantReadWriteLock示例,以了解如何在僅允許一個(gè)線程更新資源的同時(shí)實(shí)現(xiàn)對(duì)資源的并發(fā)讀取。

注意:資源可以是應(yīng)用程序中各種線程嘗試同時(shí)訪問(wèn)的任何數(shù)據(jù)。

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
}

創(chuàng)建上述類(lèi)的一個(gè)實(shí)例,并將其傳遞給多個(gè)線程; 將處理以下內(nèi)容:

??'N'個(gè)線程使用readLock或最多一個(gè)線程使用writeLock。
??讀或?qū)懚疾粫?huì)同時(shí)發(fā)生。

Java內(nèi)存模型和CPU

有關(guān)Java和CPU內(nèi)存模型的說(shuō)明將幫助我們更好地了解對(duì)象和變量在Java堆/線程堆棧中的存儲(chǔ)方式以及實(shí)際CPU內(nèi)存的存儲(chǔ)方式。 現(xiàn)代的CPU由寄存器組成,這些寄存器充當(dāng)處理器本身的直接存儲(chǔ)器,高速緩存存儲(chǔ)器-每個(gè)處理器都有一個(gè)高速緩存層來(lái)存儲(chǔ)數(shù)據(jù),最后是存在應(yīng)用程序數(shù)據(jù)的RAM或主存儲(chǔ)器。

JVM與CPU內(nèi)存模型

在硬件或CPU上,線程堆棧和堆都位于主內(nèi)存中。 線程堆棧和堆的某些部分有時(shí)可能會(huì)出現(xiàn)在CPU緩存和內(nèi)部寄存器中。 以下是由于上述體系結(jié)構(gòu)而可能發(fā)生的問(wèn)題:

1,并非所有訪問(wèn)變量的線程都立即看到對(duì)共享變量的線程更新(寫(xiě))的可見(jiàn)性。
2.讀取,檢查和更新共享變量的數(shù)據(jù)時(shí)的種族條件。

volatile關(guān)鍵字

volatile關(guān)鍵字是Java 5中引入的,在實(shí)現(xiàn)線程安全方面有重要的用途。 此關(guān)鍵字可用于基元和對(duì)象。 在變量上使用volatile關(guān)鍵字可確保在更新后直接從主存儲(chǔ)器讀取給定變量并將其寫(xiě)回主存儲(chǔ)器。

ThreadLocal類(lèi)別

線程同步的最后一個(gè)主題是Lock是Java類(lèi)ThreadLocal之后的主題。 此類(lèi)可創(chuàng)建只能由同一線程讀取/寫(xiě)入的變量。 這為我們提供了一種通過(guò)定義線程局部變量來(lái)實(shí)現(xiàn)線程安全的簡(jiǎn)單方法。 ThreadLocal在線程池或ExecutorService中有重要用途,因此每個(gè)線程都使用自己的某些資源或?qū)ο蟮膶?shí)例。

例如,對(duì)于每個(gè)線程,都需要一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)連接,或者一個(gè)單獨(dú)的計(jì)數(shù)器。 在這種情況下,ThreadLocal會(huì)有所幫助。 這在Spring Boot應(yīng)用程序中也使用,其中為每個(gè)傳入呼叫設(shè)置了用戶(hù)上下文(Spring Security),并且將通過(guò)各種實(shí)例在線程流之間共享用戶(hù)上下文。 在以下情況下使用ThreadLocal

?線程限制。
?每個(gè)線程的數(shù)據(jù)性能。
?每個(gè)線程上下文。

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
}

線程同步和相關(guān)概念就是這樣。 并發(fā)將在本文的第2部分中介紹。

喜歡這篇文章的可以點(diǎn)個(gè)贊,歡迎大家留言評(píng)論,記得關(guān)注我,每天持續(xù)更新技術(shù)干貨、職場(chǎng)趣事、海量面試資料等等
?> 如果你對(duì)java技術(shù)很感興趣也可以交流學(xué)習(xí),共同學(xué)習(xí)進(jìn)步。?
不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來(lái)的自己一個(gè)交代

文章寫(xiě)道這里,歡迎完善交流。最后奉上近期整理出來(lái)的一套完整的java架構(gòu)思維導(dǎo)圖,分享給大家對(duì)照知識(shí)點(diǎn)參考學(xué)習(xí)。有更多JVM、Mysql、Tomcat、Spring Boot、Spring Cloud、Zookeeper、Kafka、RabbitMQ、RockerMQ、Redis、ELK、Git等Java干貨

Java線程同步和并發(fā)第1部分

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI