欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java并發(fā)編程之對(duì)象的共享

 更新時(shí)間:2022年04月07日 14:11:09   作者:萬(wàn)貓學(xué)社  
這篇文章主要介紹了Java并發(fā)編程之對(duì)象的共享,介紹如何共享和發(fā)布對(duì)象,使它們被安全地由多個(gè)進(jìn)程訪問(wèn)。需要的小伙伴可以參考一下

1.可見(jiàn)性

通常,我們無(wú)法保證執(zhí)行讀操作的線程能看到其他線程寫入的值,因?yàn)槊總€(gè)線程都由自己的緩存機(jī)制。為了確保多個(gè)線程之間對(duì)內(nèi)存寫入操作的可見(jiàn)性,必須使用同步機(jī)制。

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

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

以上代碼,看起來(lái)會(huì)輸出42,但事實(shí)上很可能根本無(wú)法終止,因?yàn)樽x線程永遠(yuǎn)看不到ready的值;很有可能輸出0,因?yàn)樽x線程看到了寫入ready的值,卻沒(méi)有看到之后寫入number的值,這種現(xiàn)象稱為“重排序”。在沒(méi)有同步的情況下,編譯器、處理器、運(yùn)行時(shí)等都有可能對(duì)操作的執(zhí)行順序進(jìn)行一些意想不到的調(diào)整。

所以,只要有數(shù)據(jù)在多個(gè)線程之間共享時(shí),就應(yīng)該使用正確的同步。

1.1 失效數(shù)據(jù)

除非使用同步,否則很可能獲得變量的失效值。失效值可能不會(huì)同時(shí)出現(xiàn),一個(gè)線程可能獲得一個(gè)變量的最新值,而獲得另一個(gè)變量的失效值。失效數(shù)據(jù)還可能導(dǎo)致一些令人困惑的故障,如:意料之外的異常、被破壞的數(shù)據(jù)結(jié)構(gòu)、不精確的計(jì)算、無(wú)限循環(huán)等等。

1.2 非原子的64位操作

對(duì)于非volatile類型的long和double變量,JVM允許將64位的讀操作或?qū)懖僮鞣纸鉃閮蓚€(gè)32位的操作。所以,很可能會(huì)讀取到最新值的高32位和失效值的低32值,造成讀取到是一個(gè)隨機(jī)值。除非用關(guān)鍵字volatile來(lái)聲明它們,或者用鎖保護(hù)起來(lái)。

1.3 加鎖和可見(jiàn)性

當(dāng)某線程執(zhí)行由鎖保護(hù)的同步代碼塊時(shí),可以看到其他線程之前在同一同步代碼塊中的所有操作結(jié)果。如果沒(méi)有同步,將無(wú)法實(shí)現(xiàn)上述保證。加鎖的含義不僅僅局限于互斥行為,還包括可見(jiàn)性。為了確保所有線程都能看到共享變量的最新值,所有執(zhí)行讀操作或?qū)懖僮鞯木€程都必須在同一個(gè)鎖上同步。

1.4 volatile變量

當(dāng)把變量聲明為volatile類型后,編譯器和運(yùn)行時(shí)都不會(huì)將該變量上的操作也其他內(nèi)存操作一起重排序。volatile變量不會(huì)被緩存在寄存器或者其他處理器不可見(jiàn)的地方,因此在讀取volatile變量時(shí)總會(huì)返回最新寫入的值。加鎖機(jī)制既可以確??梢?jiàn)性又可以確保原子性,而volatile變量只能確??梢?jiàn)性。

當(dāng)且僅當(dāng)滿足以下所有條件時(shí),才應(yīng)該使用volatile變量:

  • 對(duì)變量的寫入操作不依賴變量的當(dāng)前值,或者能確保只用單個(gè)線程更新變量的值。
  • 該變量不會(huì)與其他狀態(tài)變量一起納入不變性條件中。
  • 在訪問(wèn)變量時(shí)不需要加鎖。

2. 發(fā)布與泄露

發(fā)布一個(gè)對(duì)象是指,是對(duì)象能夠在當(dāng)前作用域之外的代碼中使用。發(fā)布對(duì)象的方式包括:非私用變量的引用、方法調(diào)用返回的引用、發(fā)布內(nèi)部類對(duì)象隱含外部類的引用等等。當(dāng)某個(gè)不應(yīng)該發(fā)布的對(duì)象被發(fā)布是,就被稱為泄露。

public class ThisEscape {
   private int status;
   public ThisEscape(EventSource source) {
      source.registerListener(new EventListener() {
         public void onEvent(Event e) {
            doSomething(e);
         }
      });
      status = 1;
   }

   void doSomething(Event e) {
      status = e.getStatus();
   }

   interface EventSource {
      void registerListener(EventListener e);
   }

   interface EventListener {
      void onEvent(Event e);
   }

   interface Event {
      int getStatus();
   }
}

由于內(nèi)部類的實(shí)例包含了對(duì)外部類實(shí)例的隱含引用,當(dāng)ThisEscape發(fā)布EventListener時(shí),也隱含發(fā)布了ThisEscape實(shí)例本身。但在此時(shí),變量status還沒(méi)有被初始化,造成了this引用在構(gòu)造函數(shù)中泄露??梢允褂靡粋€(gè)私有的構(gòu)造函數(shù)和一個(gè)公共的工廠方法,避免不正確的構(gòu)造過(guò)程:

public class SafeListener {
    private int status;
    private final EventListener listener;
    private SafeListener() {
        listener = new EventListener() {
            public void onEvent(Event e) {
                doSomething(e);
            }
        };
        status = 1;
    }
    public static SafeListener newInstance(EventSource source) {
        SafeListener safe = new SafeListener();
        source.registerListener(safe.listener);
        return safe;
    }

    void doSomething(Event e) {
        status = e.getStatus();
    }

    interface EventSource {
        void registerListener(EventListener e);
    }

    interface EventListener {
        void onEvent(Event e);
    }

    interface Event {
        int getStatus();
    }
}

3. 線程封閉

一種避免使用同步的方式就是不共享。如果僅在單線程內(nèi)訪問(wèn)數(shù)據(jù),就不需要同步,這就被稱為線程封閉。線程封閉是程序設(shè)計(jì)中的考慮因素,必須在程序中實(shí)現(xiàn)。Java也提供了一些機(jī)制幫助維護(hù)線程封閉,比如局部變量和ThreadLocal。

3.1 Ad-hoc線程封閉

Ad-hoc線程封閉是指,維護(hù)線程封閉性的職責(zé)完全由程序?qū)崿F(xiàn)來(lái)承擔(dān)。使用volatile變量是實(shí)現(xiàn)Ad-hoc線程封閉的一種方式,只要能保證只有單個(gè)線程對(duì)共享volatile變量執(zhí)行寫入操作,那么就可以安全低在這些變量上進(jìn)行“讀取-修改-寫入”操作,volatile變量的可見(jiàn)性又保證了其他線程能夠看到最新的值。

Ad-hoc線程封閉是非常脆弱的,因此在程序中盡量少使用。在可能的情況下,使用其他線程封閉技術(shù),比如:棧封閉、ThreadLocal。

3.2 棧封閉

在棧封閉中,只能通過(guò)局部變量才能訪問(wèn)對(duì)象。它們位于執(zhí)行線程的棧中,其他線程無(wú)法訪問(wèn)到。即使這些對(duì)象是非線程安全的對(duì)象,它們?nèi)匀皇蔷€程安全的。然而,值得注意的是,只要編寫代碼的人才知道哪些對(duì)象是棧封閉的。如果沒(méi)有明確的說(shuō)明,后續(xù)的維護(hù)人員很容易錯(cuò)誤的泄露這些對(duì)象。

3.3 ThreadLocal類

使用ThreadLocal是一種更規(guī)范的線程封閉方式,它能是線程中的某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來(lái)。如下代碼,通過(guò)將JDBC的連接保存到ThreadLocal對(duì)象中,每個(gè)線程都會(huì)擁有屬于自己的連接:

public class ConnectionDispenser {
    static String DB_URL = "jdbc:mysql://localhost/mydatabase";

    private ThreadLocal<Connection> connectionHolder
        = new ThreadLocal<Connection>() {
            public Connection initialValue() {
                try {
                    return DriverManager.getConnection(DB_URL);
                } catch (SQLException e) {
                    throw new RuntimeException("Unable to acquire Connection, e");
                }
        };
    };

    public Connection getConnection() {
        return connectionHolder.get();
    }
}

從概念上看,你可以將ThreadLocal<T>視為包含了Map<Thread,T>對(duì)象,其中保存了特定于改線程的值,但ThreadLocal的實(shí)現(xiàn)并非如此。這些特定于線程的值保存在Thread對(duì)象中,當(dāng)線程終止后,這些值會(huì)作為垃圾被回收。

4. 不變性

如果某個(gè)對(duì)象在被創(chuàng)建后其狀態(tài)就不能被修改,那么這個(gè)對(duì)象就被稱為不可變對(duì)象。滿足同步需求的另一種方法就是使用不可變對(duì)象。不可變對(duì)象一定是線程安全的。當(dāng)滿足以下條件時(shí),對(duì)象才是不可變的:

  • 對(duì)象創(chuàng)建以后其狀態(tài)就不能改變
  • 對(duì)象的所有域都是final類型
  • 對(duì)象是正確創(chuàng)建的,在對(duì)象創(chuàng)建期間,this引用沒(méi)有泄露
public final class ThreeStooges {
    private final Set<String> stooges = new HashSet<String>();

    public ThreeStooges() {
        stooges.add("Moe");
        stooges.add("Larry");
        stooges.add("Curly");
    }

    public boolean isStooge(String name) {
        return stooges.contains(name);
    }
}

上述代碼中,盡管stooges對(duì)象是可變的,但在它構(gòu)造完成后無(wú)法對(duì)其修改。stooges是一個(gè)final類型的引用變量,因此所有的對(duì)象狀態(tài)都通過(guò)一個(gè)final域訪問(wèn)。在構(gòu)造函數(shù)中,this引用不能被除了構(gòu)造函數(shù)之外的代碼訪問(wèn)到。

4.1 final域

final類型的域是不能修改的,但如果final域所引用的對(duì)象是可變的,那么這些被引用的對(duì)象是可以修改的。final域的對(duì)象在構(gòu)造函數(shù)中不會(huì)被重排序,所以final域也能保證初始化過(guò)程的安全性。和“除非需要更高的可見(jiàn)性,否則應(yīng)將所有的域都聲明為私用域”一樣,“除非需要某個(gè)域是可變的,否則應(yīng)將其聲明為final域”也是一個(gè)良好的編程習(xí)慣。

4.2 使用volatile類型來(lái)發(fā)布不可變對(duì)象

因式分解Sevlet將執(zhí)行兩個(gè)原子操作:

  • 更新緩存
  • 通過(guò)判斷緩存中的數(shù)值是否等于請(qǐng)求的數(shù)值來(lái)決定是否直接讀取緩存中的結(jié)果

每當(dāng)需要一組相關(guān)數(shù)據(jù)以原子方式執(zhí)行某個(gè)操作時(shí),就可以考慮創(chuàng)建一個(gè)不可變的類來(lái)包含這些數(shù)據(jù):

public class OneValueCache {
    private final BigInteger lastNumber;
    private final BigInteger[] lastFactors;

    public OneValueCache(BigInteger i, BigInteger[] factors) {
        lastNumber = i;
        lastFactors = Arrays.copyOf(factors, factors.length);
    }

    public BigInteger[] getFactors(BigInteger i) {
        if (lastNumber == null || !lastNumber.equals(i))
            return null;
        else
            return Arrays.copyOf(lastFactors, lastFactors.length);
    }
}

當(dāng)線程獲取了不可變對(duì)象的引用后,不必?fù)?dān)心另一個(gè)線程會(huì)修改對(duì)象的狀態(tài)。如果要更新這些變量,可以創(chuàng)建一個(gè)新的容器對(duì)象,但其他使用原有對(duì)象的線程仍然會(huì)看到對(duì)象處于一致的狀態(tài)。當(dāng)一個(gè)線程將volatile類型的cache設(shè)置為引用一個(gè)新的OneValueCache時(shí),其他線程就會(huì)立即看到新緩存的數(shù)據(jù):

public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }
        encodeIntoResponse(resp, factors);
    }
}

5 安全發(fā)布

5.1 不正確的發(fā)布

像這樣將對(duì)象引用保存到公有域中就是不安全的:

public Holder holder;
public void initialize(){
    holder = new Holder(42);
}

由于存在可見(jiàn)性問(wèn)題,其他線程看到的Holder對(duì)象將處于不一致的狀態(tài)。除了發(fā)布對(duì)象的線程外,其他線程可以看到Holder域是一個(gè)失效值,因此將看到一個(gè)空引用或者之前的舊值。

public class Holder {
    private int n;

    public Holder(int n) {
        this.n = n;
    }

    public void assertSanity() {
        if (n != n)
            throw new AssertionError("This statement is false.");
    }
}

上述代碼,即使Holder對(duì)象被正確的發(fā)布,assertSanity也有可能拋出AssertionError。因?yàn)榫€程看到Holder引用的值是最新的,但由于重排序Holder狀態(tài)的值卻是時(shí)效的。

5.2 不可變對(duì)象與初始化安全性

即使在發(fā)布不可變對(duì)象的引用時(shí)沒(méi)有使用同步,也仍然可以安全地訪問(wèn)該對(duì)象。任何線程都可以在不需要額外同步的情況下安全地訪問(wèn)不可變對(duì)象,即使在發(fā)布這些對(duì)象時(shí)沒(méi)有使用同步。在沒(méi)有額外同步的情況下,也可以安全地訪問(wèn)final類型的域。然而,如果final類型的域所指向的是可變對(duì)象,那么在訪問(wèn)這些域所指向的對(duì)象的狀態(tài)時(shí)仍然需要同步。

5.3 安全發(fā)布的常用模式

要安全地發(fā)布一個(gè)對(duì)象,對(duì)象的引用以及對(duì)象的狀態(tài)必須同時(shí)對(duì)其他線程可見(jiàn)。一個(gè)正確構(gòu)造的對(duì)象可以通過(guò)以下方式來(lái)安全發(fā)布:

  • 在靜態(tài)初始化函數(shù)里初始化一個(gè)對(duì)象引用。
  • 將對(duì)象的引用保存到volatile類型的域或者AtomicReference對(duì)象中。
  • 將對(duì)象的引用保存到某個(gè)正確構(gòu)造對(duì)象的final類型域中。
  • 將對(duì)象的引用保存到一個(gè)由鎖保護(hù)的域中。

線程安全庫(kù)中的容器類提供了以下的安全發(fā)布保證:

  • 通過(guò)將一個(gè)鍵或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地將它發(fā)布給任何從這些容器中訪問(wèn)它的線程。
  • 通過(guò)將某個(gè)對(duì)象放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或者synchronizedSet中,可以將該對(duì)象安全地發(fā)布到任何從這些容器中訪問(wèn)該對(duì)象的線程。
  • 通過(guò)將某個(gè)對(duì)象放入BlockingQueue或者ConcurrentLinkedQueue中,可以將該對(duì)象安全地發(fā)布到任何從這些隊(duì)列中訪問(wèn)該對(duì)象的線程。

5.4 事實(shí)不可變對(duì)象

如果對(duì)象從技術(shù)上來(lái)看是可變的,但其狀態(tài)在發(fā)布后不會(huì)再改變,那么把這種對(duì)象稱為事實(shí)不可變對(duì)象。在沒(méi)有額外的同步的情況下,任何線程都可以安全地使用被安全發(fā)布的事實(shí)不可變對(duì)象。例如維護(hù)一個(gè)Map對(duì)象,其中保存了每位用戶的最新登錄時(shí)間:

public Map<String, Date> lastLogin =
Collections.synchronizedMap(new HashMap<String, Date());

如果Date對(duì)象的值在被放入Map后就不會(huì)改變,那么synchronizedMap中的同步機(jī)制就足以使Date值被安全地發(fā)布,并且在訪問(wèn)這些Date值時(shí)不需要額外的同步。

5.5 可變對(duì)象

對(duì)于可變對(duì)象,不僅在發(fā)布對(duì)象是需要使用同步,而且在每次對(duì)象訪問(wèn)時(shí)同樣需要使用同步來(lái)確保后續(xù)修改操作的可見(jiàn)性。對(duì)象的發(fā)布需求取決于它的可變性:

  • 不可變對(duì)象可以通過(guò)任意機(jī)制來(lái)發(fā)布。
  • 事實(shí)不可變對(duì)象必須通過(guò)安全方式來(lái)發(fā)布。
  • 可變對(duì)象必須通過(guò)安全方式來(lái)發(fā)布,而且必須是線程安全的或者用某個(gè)鎖保護(hù)起來(lái)。

5.6 安全的共享對(duì)象

在并發(fā)程序中使用和共享對(duì)象時(shí),可以使用一些實(shí)用的策略,包括:

  • 線程封閉。線程封閉的對(duì)象只能由一個(gè)線程擁有,對(duì)象被封閉在該線程中,并且只能由這個(gè)線程修改。
  • 只讀共享。在沒(méi)有額外同步的情況下,共享的只讀對(duì)象可以由多個(gè)線程并發(fā)訪問(wèn),但任何線程都不能修改它。共享的只讀對(duì)象包括不可變對(duì)象和事實(shí)不可變對(duì)象。
  • 線程安全共享。線程安全的對(duì)象在其內(nèi)部實(shí)現(xiàn)同步,因此多個(gè)線程可以通過(guò)對(duì)象的公共接口來(lái)進(jìn)行訪問(wèn)而不需要進(jìn)一步的同步。
  • 保護(hù)對(duì)象。被保護(hù)的對(duì)象只能通過(guò)持有特定的鎖來(lái)訪問(wèn)。保護(hù)對(duì)象包括封裝在其他線程安全對(duì)象中的對(duì)象,以及已發(fā)布的并且由某個(gè)特定鎖保護(hù)的對(duì)象。

到此這篇關(guān)于Java并發(fā)編程之對(duì)象的共享的文章就介紹到這了,更多相關(guān)Java對(duì)象的共享內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java帶返回值的方法的定義和調(diào)用詳解

    Java帶返回值的方法的定義和調(diào)用詳解

    在java中,方法就是用來(lái)完成解決某件事情或?qū)崿F(xiàn)某個(gè)功能的辦法。方法實(shí)現(xiàn)的過(guò)程中,會(huì)包含很多條語(yǔ)句用于完成某些有意義的功能——通常是處理文本,控制輸入或計(jì)算數(shù)值,這篇文章我們來(lái)探究一下帶返回值的方法的定義和調(diào)用
    2022-04-04
  • Java動(dòng)態(tài)調(diào)用類中方法代碼

    Java動(dòng)態(tài)調(diào)用類中方法代碼

    這篇文章主要介紹了Java動(dòng)態(tài)調(diào)用類中方法代碼,需要的朋友可以參考下
    2014-02-02
  • springboot?@Async?注解如何實(shí)現(xiàn)方法異步

    springboot?@Async?注解如何實(shí)現(xiàn)方法異步

    這篇文章主要介紹了springboot?@Async?注解如何實(shí)現(xiàn)方法異步,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP軟件主界面(4)

    Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP軟件主界面(4)

    這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件,F(xiàn)TP軟件主界面編寫的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 基于SpringBoot構(gòu)建電商秒殺項(xiàng)目代碼實(shí)例

    基于SpringBoot構(gòu)建電商秒殺項(xiàng)目代碼實(shí)例

    這篇文章主要介紹了基于SpringBoot構(gòu)建電商秒殺項(xiàng)目代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制

    RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制

    這篇文章主要介紹了RocketMQ設(shè)計(jì)之故障規(guī)避機(jī)制,故障規(guī)避機(jī)制就是用來(lái)解決當(dāng)Broker出現(xiàn)故障,Producer不能及時(shí)感知而導(dǎo)致消息發(fā)送失敗的問(wèn)題,下面詳細(xì)介紹需要的小伙伴可以參考一下
    2022-03-03
  • Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例詳解

    Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例詳解

    這篇文章主要介紹了Java基于裝飾者模式實(shí)現(xiàn)的染色饅頭案例,簡(jiǎn)單描述了裝飾者模式的概念、原理及Java使用裝飾者模式的相關(guān)實(shí)現(xiàn)步驟、操作技巧與注意事項(xiàng),需要的朋友可以參考下
    2018-05-05
  • Admin - SpringBoot + Maven 多啟動(dòng)環(huán)境配置實(shí)例詳解

    Admin - SpringBoot + Maven 多啟動(dòng)環(huán)境配置實(shí)例詳解

    這篇文章主要介紹了Admin - SpringBoot + Maven 多啟動(dòng)環(huán)境配置,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • Java編程中隨機(jī)數(shù)的生成方式總結(jié)

    Java編程中隨機(jī)數(shù)的生成方式總結(jié)

    在Java中利用自帶的類庫(kù)可以有三種途徑可以產(chǎn)生隨機(jī)數(shù),這里我們舉了一些簡(jiǎn)單的例子來(lái)進(jìn)行Java編程中隨機(jī)數(shù)的生成方式總結(jié),需要的朋友可以參考下
    2016-05-05
  • Java?天生就是多線程

    Java?天生就是多線程

    這篇文章主要介紹了Java天生就是多線程,程序天生就是多線程程序,因?yàn)閳?zhí)行main()方法的是一個(gè)名稱為main的線程,更多相關(guān)內(nèi)容需要的小伙伴可以參考一下
    2022-07-07

最新評(píng)論