亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Java? 教程(同步)

Edison / 1194人閱讀

同步

線程主要通過(guò)共享對(duì)字段和引用對(duì)象的引用字段的訪問(wèn)來(lái)進(jìn)行通信,這種通信形式非常有效,但可能產(chǎn)生兩種錯(cuò)誤:線程干擾和內(nèi)存一致性錯(cuò)誤,防止這些錯(cuò)誤所需的工具是同步。

但是,同步可能會(huì)引入線程競(jìng)爭(zhēng),當(dāng)兩個(gè)或多個(gè)線程同時(shí)嘗試訪問(wèn)同一資源并導(dǎo)致Java運(yùn)行時(shí)更慢地執(zhí)行一個(gè)或多個(gè)線程,甚至?xí)和K鼈儓?zhí)行,饑餓和活鎖是線程競(jìng)爭(zhēng)的形式。

本節(jié)包括以下主題:

線程干擾描述了當(dāng)多個(gè)線程訪問(wèn)共享數(shù)據(jù)時(shí)如何引入錯(cuò)誤。

內(nèi)存一致性錯(cuò)誤描述了由共享內(nèi)存的不一致視圖導(dǎo)致的錯(cuò)誤。

同步方法描述了一種簡(jiǎn)單的語(yǔ)法,可以有效地防止線程干擾和內(nèi)存一致性錯(cuò)誤。

隱式鎖和同步描述了一種更通用的同步語(yǔ)法,并描述了同步是如何基于隱式鎖的。

原子訪問(wèn)討論的是不能被其他線程干擾的操作的一般概念。

線程干擾

考慮一個(gè)名為Counter的簡(jiǎn)單類:

class Counter {
    private int c = 0;

    public void increment() {
        c++;
    }

    public void decrement() {
        c--;
    }

    public int value() {
        return c;
    }

}

Counter的設(shè)計(jì)為每次increment的調(diào)用都會(huì)將c加1,每次decrement的調(diào)用都會(huì)從c中減去1,但是,如果從多個(gè)線程引用Counter對(duì)象,則線程之間的干擾可能會(huì)妨礙這種情況按預(yù)期發(fā)生。

當(dāng)兩個(gè)操作在不同的線程中運(yùn)行但作用于相同的數(shù)據(jù)時(shí),會(huì)發(fā)生干擾,這意味著這兩個(gè)操作由多個(gè)步驟組成,并且步驟序列交疊。

對(duì)于Counter實(shí)例的操作似乎不可能進(jìn)行交錯(cuò),因?yàn)閷?duì)c的兩個(gè)操作都是單個(gè)簡(jiǎn)單的語(yǔ)句,但是,即使是簡(jiǎn)單的語(yǔ)句也可以由虛擬機(jī)轉(zhuǎn)換為多個(gè)步驟,我們不會(huì)檢查虛擬機(jī)采取的具體步驟 — 只需知道單個(gè)表達(dá)式c++可以分解為三個(gè)步驟:

檢索c的當(dāng)前值。

將檢索的值增加1。

將增加的值存儲(chǔ)在c中。

表達(dá)式c--可以以相同的方式分解,除了第二步是遞減而不是遞增。

假設(shè)在大約同一時(shí)間,線程A調(diào)用increment,線程B調(diào)用decrement,如果c的初始值為0,則它??們的交錯(cuò)操作可能遵循以下順序:

線程A:檢索c。

線程B:檢索c。

線程A:遞增檢索值,結(jié)果是1。

線程B:遞減檢索值,結(jié)果是-1。

線程A:將結(jié)果存儲(chǔ)在c中,c現(xiàn)在是1。

線程B:將結(jié)果存儲(chǔ)在c中,c現(xiàn)在是-1。

線程A的結(jié)果丟失,被線程B覆蓋,這種特殊的交錯(cuò)只是一種可能性,在不同的情況下,可能是線程B的結(jié)果丟失,或者根本沒(méi)有錯(cuò)誤,因?yàn)樗鼈兪遣豢深A(yù)測(cè)的,所以難以檢測(cè)和修復(fù)線程干擾錯(cuò)誤。

內(nèi)存一致性錯(cuò)誤

當(dāng)不同的線程具有應(yīng)該是相同數(shù)據(jù)的不一致視圖時(shí),會(huì)發(fā)生內(nèi)存一致性錯(cuò)誤,內(nèi)存一致性錯(cuò)誤的原因很復(fù)雜,超出了本教程的范圍,幸運(yùn)的是,程序員不需要詳細(xì)了解這些原因,所需要的只是避免它們的策略。

避免內(nèi)存一致性錯(cuò)誤的關(guān)鍵是理解先發(fā)生關(guān)系,這種關(guān)系只是保證一個(gè)特定語(yǔ)句的內(nèi)存寫入對(duì)另一個(gè)特定語(yǔ)句可見(jiàn),要了解這一點(diǎn),請(qǐng)考慮以下示例,假設(shè)定義并初始化了一個(gè)簡(jiǎn)單的int字段:

int counter = 0;

counter字段在兩個(gè)線程A和B之間共享,假設(shè)線程A遞增counter

counter++;

然后,不久之后,線程B打印出counter

System.out.println(counter);

如果兩個(gè)語(yǔ)句已在同一個(gè)線程中執(zhí)行,則可以安全地假設(shè)打印出的值為“1”,但如果兩個(gè)語(yǔ)句在不同的線程中執(zhí)行,則打印出的值可能為“0”,因?yàn)闊o(wú)法保證線程A對(duì)counter的更改對(duì)線程B可見(jiàn) — 除非程序員在這兩條語(yǔ)句之間建立了先發(fā)生關(guān)系。

有幾種操作可以創(chuàng)建先發(fā)生關(guān)系,其中之一是同步,我們將在下面的部分中看到。

我們已經(jīng)看到了兩種創(chuàng)建先發(fā)生關(guān)系的操作。

當(dāng)一個(gè)語(yǔ)句調(diào)用Thread.start時(shí),與該語(yǔ)句具有一個(gè)先發(fā)生關(guān)系的每個(gè)語(yǔ)句也與新線程執(zhí)行的每個(gè)語(yǔ)句都有一個(gè)先發(fā)生關(guān)系,導(dǎo)致創(chuàng)建新線程的代碼的效果對(duì)新線程可見(jiàn)。

當(dāng)一個(gè)線程終止并導(dǎo)致另一個(gè)線程中的Thread.join返回時(shí),已終止的線程執(zhí)行的所有語(yǔ)句與成功join后的所有語(yǔ)句都有一個(gè)先發(fā)生關(guān)系,線程中代碼的效果現(xiàn)在對(duì)執(zhí)行join的線程可見(jiàn)。

有關(guān)創(chuàng)建先發(fā)生關(guān)系的操作列表,請(qǐng)參閱java.util.concurrent包的Summary頁(yè)面。

同步方法

Java編程語(yǔ)言提供了兩種基本的同步語(yǔ)法:同步方法和同步語(yǔ)句,下兩節(jié)將介紹兩個(gè)同步語(yǔ)句中較為復(fù)雜的語(yǔ)句,本節(jié)介紹同步方法。

要使方法同步,只需將synchronized關(guān)鍵字添加到其聲明:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

如果countSynchronizedCounter的一個(gè)實(shí)例,那么使這些方法同步有兩個(gè)效果:

首先,不可能對(duì)同一對(duì)象上的兩個(gè)同步方法的調(diào)用進(jìn)行交錯(cuò),當(dāng)一個(gè)線程正在為對(duì)象執(zhí)行同步方法時(shí),調(diào)用同一對(duì)象的同步方法的所有其他線程阻塞(暫停執(zhí)行),直到第一個(gè)線程使用完對(duì)象為止。

其次,當(dāng)一個(gè)同步方法退出時(shí),它會(huì)自動(dòng)與同一個(gè)對(duì)象的同步方法的任何后續(xù)調(diào)用建立一個(gè)先發(fā)生關(guān)系,這可以保證對(duì)象狀態(tài)的更改對(duì)所有線程都可見(jiàn)。

請(qǐng)注意,構(gòu)造函數(shù)無(wú)法同步 — 將synchronized關(guān)鍵字與構(gòu)造函數(shù)一起使用是一種語(yǔ)法錯(cuò)誤,同步構(gòu)造函數(shù)沒(méi)有意義,因?yàn)橹挥袆?chuàng)建對(duì)象的線程在構(gòu)造時(shí)才能訪問(wèn)它。

構(gòu)造將在線程之間共享的對(duì)象時(shí),要非常小心對(duì)對(duì)象的引用不會(huì)過(guò)早“泄漏”,例如,假設(shè)你要維護(hù)一個(gè)包含每個(gè)類實(shí)例的名為instancesList,你可能想要將以下行添加到你的構(gòu)造函數(shù)中:instances.add(this);

但是其他線程可以在構(gòu)造對(duì)象完成之前使用instances來(lái)訪問(wèn)對(duì)象。

同步方法支持一種簡(jiǎn)單的策略來(lái)防止線程干擾和內(nèi)存一致性錯(cuò)誤:如果一個(gè)對(duì)象對(duì)多個(gè)線程可見(jiàn),則對(duì)該對(duì)象的變量所有讀取或?qū)懭攵际峭ㄟ^(guò)synchronized方法完成的(一個(gè)重要的例外:一旦構(gòu)造了對(duì)象,就可以通過(guò)非同步方法安全地讀取構(gòu)造對(duì)象后無(wú)法修改的final字段),這種策略很有效,但可能會(huì)帶來(lái)活性問(wèn)題,我們將在本課后面看到。

固有鎖和同步

同步是圍繞稱為固有鎖或監(jiān)控鎖的內(nèi)部實(shí)體構(gòu)建的(API規(guī)范通常將此實(shí)體簡(jiǎn)稱為“監(jiān)視器”。),固有鎖在同步的兩個(gè)方面都起作用:強(qiáng)制執(zhí)行對(duì)對(duì)象狀態(tài)的獨(dú)占訪問(wèn),并建立對(duì)可見(jiàn)性至關(guān)重要的先發(fā)生關(guān)系。

每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的固有鎖,按照約定,需要對(duì)對(duì)象字段進(jìn)行獨(dú)占和一致訪問(wèn)的線程必須在訪問(wèn)對(duì)象之前獲取對(duì)象的固有鎖,然后在完成它們時(shí)釋放固有鎖。線程在獲取鎖和釋放鎖期間被稱為擁有固有鎖,只要一個(gè)線程擁有固有鎖,沒(méi)有其他線程可以獲得相同的鎖,另一個(gè)線程在嘗試獲取鎖時(shí)將阻塞。

當(dāng)線程釋放固有鎖時(shí),在該操作與同一鎖的任何后續(xù)獲取之間建立先發(fā)生關(guān)系。

同步方法中的鎖

當(dāng)線程調(diào)用同步方法時(shí),它會(huì)自動(dòng)獲取該方法對(duì)象的固有鎖,并在方法返回時(shí)釋放它,即使返回是由未捕獲的異常引起的,也會(huì)發(fā)生鎖定釋放。

你可能想知道調(diào)用靜態(tài)同步方法時(shí)會(huì)發(fā)生什么,因?yàn)殪o態(tài)方法與類相關(guān)聯(lián),而不是與對(duì)象相關(guān)聯(lián),在這種情況下,線程獲取與類關(guān)聯(lián)的Class對(duì)象的固有鎖,因此,對(duì)類的靜態(tài)字段的訪問(wèn)由一個(gè)鎖控制,該鎖與該類的任何實(shí)例的鎖不同。

同步語(yǔ)句

創(chuàng)建同步代碼的另一種方法是使用同步語(yǔ)句,與同步方法不同,同步語(yǔ)句必須指定提供固有鎖的對(duì)象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在此示例中,addName方法需要同步更改lastNamenameCount,但還需要避免同步調(diào)用其他對(duì)象的方法(從同步代碼中調(diào)用其他對(duì)象的方法可能會(huì)產(chǎn)生有關(guān)活性一節(jié)中描述的問(wèn)題),如果沒(méi)有同步語(yǔ)句,則必須有一個(gè)多帶帶的、不同步的方法,其唯一目的是調(diào)用nameList.add

同步語(yǔ)句對(duì)于通過(guò)細(xì)粒度同步提高并發(fā)性也很有用,例如,假設(shè)類MsLunch有兩個(gè)實(shí)例字段,c1c2,它們從不一起使用,必須同步這些字段的所有更新,但是沒(méi)有理由阻礙c1的更新與c2的更新交錯(cuò) — 并且這樣做會(huì)通過(guò)創(chuàng)建不必要的阻塞來(lái)減少并發(fā)性。我們創(chuàng)建兩個(gè)對(duì)象只是為了提供鎖,而不是使用同步方法或使用與此相關(guān)聯(lián)的鎖。

public class MsLunch {
    private long c1 = 0;
    private long c2 = 0;
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}

謹(jǐn)慎使用這種用法,你必須絕對(duì)確保對(duì)受影響字段的交錯(cuò)訪問(wèn)是安全的。

可重入同步

回想一下,線程無(wú)法獲取另一個(gè)線程擁有的鎖,但是一個(gè)線程可以獲得它已經(jīng)擁有的鎖,允許線程多次獲取同一個(gè)鎖可使可重入同步。這描述了一種情況,其中同步代碼直接或間接地調(diào)用也包含同步代碼的方法,并且兩組代碼使用相同的鎖,在沒(méi)有可重入同步的情況下,同步代碼必須采取許多額外的預(yù)防措施,以避免線程導(dǎo)致自身阻塞。

原子訪問(wèn)

在編程中,原子操作是一次有效地同時(shí)發(fā)生的操作,原子操作不能停在中間:它要么完全發(fā)生,要么根本不發(fā)生,在操作完成之前,原子操作的副作用在完成之前是不可見(jiàn)的。

我們已經(jīng)看到增量表達(dá)式(如c++),沒(méi)有描述原子操作,即使非常簡(jiǎn)單的表達(dá)式也可以定義可以分解為其他操作的復(fù)雜操作,但是,你可以指定為原子操作:

對(duì)于引用變量和大多數(shù)原始變量(除longdouble之外的所有類型),讀取和寫入都是原子的。

對(duì)于聲明為volatile的所有變量(包括longdouble),讀取和寫入都是原子的。

原子操作不能交錯(cuò),因此可以使用它們而不用擔(dān)心線程干擾,但是,這并不能消除所有同步原子操作的需要,因?yàn)槿匀豢赡艽嬖趦?nèi)存一致性錯(cuò)誤。使用volatile變量可以降低內(nèi)存一致性錯(cuò)誤的風(fēng)險(xiǎn),因?yàn)閷?duì)volatile變量的任何寫入都會(huì)建立與之后讀取相同變量的先發(fā)生關(guān)系,這意味著對(duì)volatile變量的更改始終對(duì)其他線程可見(jiàn)。更重要的是,它還意味著當(dāng)線程讀取volatile變量時(shí),它不僅會(huì)看到volatile的最新更改,還會(huì)看到導(dǎo)致更改的代碼的副作用。

使用簡(jiǎn)單的原子變量訪問(wèn)比通過(guò)同步代碼訪問(wèn)這些變量更有效,但程序員需要更加小心以避免內(nèi)存一致性錯(cuò)誤,額外的功夫是否值得取決于應(yīng)用程序的大小和復(fù)雜性。

java.util.concurrent包中的某些類提供了不依賴于同步的原子方法,我們將在高級(jí)并發(fā)對(duì)象一節(jié)中討論它們。

上一篇:Thread對(duì)象 下一篇:并發(fā)活性

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/73013.html

相關(guān)文章

  • Java 8 并發(fā)教程同步和鎖

    摘要:在接下來(lái)的分鐘,你將會(huì)學(xué)會(huì)如何通過(guò)同步關(guān)鍵字,鎖和信號(hào)量來(lái)同步訪問(wèn)共享可變變量。所以在使用樂(lè)觀鎖時(shí),你需要每次在訪問(wèn)任何共享可變變量之后都要檢查鎖,來(lái)確保讀鎖仍然有效。 原文:Java 8 Concurrency Tutorial: Synchronization and Locks譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 歡迎閱讀我的Java8并發(fā)教程的第二部分。這份指南將...

    wyk1184 評(píng)論0 收藏0
  • Java? 教程(原子變量)

    原子變量 java.util.concurrent.atomic包定義了支持單個(gè)變量的原子操作的類,所有類都有g(shù)et和set方法,類似于對(duì)volatile變量的讀寫操作,也就是說(shuō),set與在同一個(gè)變量上任何后續(xù)的get具有先發(fā)生關(guān)系,compareAndSet原子方法也具有這些內(nèi)存一致性特性,適用于整數(shù)原子變量的簡(jiǎn)單原子算法也是如此。 要查看如何使用此包,讓我們返回我們最初用于演示線程干擾的Cou...

    bang590 評(píng)論0 收藏0
  • Java? 教程(高級(jí)并發(fā)對(duì)象)

    高級(jí)并發(fā)對(duì)象 到目前為止,本課程重點(diǎn)關(guān)注從一開始就是Java平臺(tái)一部分的低級(jí)別API,這些API適用于非?;A(chǔ)的任務(wù),但更高級(jí)的任務(wù)需要更高級(jí)別的構(gòu)建塊,對(duì)于充分利用當(dāng)今多處理器和多核系統(tǒng)的大規(guī)模并發(fā)應(yīng)用程序尤其如此。 在本節(jié)中,我們將介紹Java平臺(tái)5.0版中引入的一些高級(jí)并發(fā)功能,大多數(shù)這些功能都在新的java.util.concurrent包中實(shí)現(xiàn),Java集合框架中還有新的并發(fā)數(shù)據(jù)結(jié)構(gòu)。 ...

    xiaotianyi 評(píng)論0 收藏0
  • Java 8 并發(fā)教程:原子變量和 ConcurrentMa

    摘要:并發(fā)教程原子變量和原文譯者飛龍協(xié)議歡迎閱讀我的多線程編程系列教程的第三部分。如果你能夠在多線程中同時(shí)且安全地執(zhí)行某個(gè)操作,而不需要關(guān)鍵字或上一章中的鎖,那么這個(gè)操作就是原子的。當(dāng)多線程的更新比讀取更頻繁時(shí),這個(gè)類通常比原子數(shù)值類性能更好。 Java 8 并發(fā)教程:原子變量和 ConcurrentMap 原文:Java 8 Concurrency Tutorial: Synchroni...

    bitkylin 評(píng)論0 收藏0
  • Java? 教程(并發(fā)活性)

    并發(fā)活性 并發(fā)應(yīng)用程序及時(shí)執(zhí)行的能力被稱為其活性,本節(jié)描述了最常見(jiàn)的活性問(wèn)題,死鎖,并繼續(xù)簡(jiǎn)要描述其他兩個(gè)活性問(wèn)題,饑餓和活鎖。 死鎖 死鎖描述了兩個(gè)或多個(gè)線程永遠(yuǎn)被阻塞,等待彼此的情況,這是一個(gè)例子。 Alphonse和Gaston是朋友,是禮貌的忠實(shí)信徒,禮貌的一個(gè)嚴(yán)格規(guī)則是,當(dāng)你向朋友鞠躬時(shí),你必須一直鞠躬,直到你的朋友有機(jī)會(huì)還禮,不幸的是,這條規(guī)則沒(méi)有考慮到兩個(gè)朋友可能同時(shí)互相鞠躬的可能性...

    KaltZK 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<