国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

這篇具有很好參考價值的文章主要介紹了【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

攝影分享~~
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

volatile關(guān)鍵字

volatile能保證內(nèi)存可見性

import java.util.Scanner;

class MyCounter {
    public int flag = 0;
}

public class ThreadDemo14 {
    public static void main(String[] args) {
        MyCounter myCounter = new MyCounter();

        Thread t1 = new Thread(() -> {
            while (myCounter.flag == 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("t1 循環(huán)結(jié)束");
        });

        Thread t2 = new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            System.out.println("請輸入一個整數(shù): ");
            myCounter.flag = scanner.nextInt();
        });

        t1.start();
        t2.start();
    }
}

以上代碼運行的結(jié)果可能是輸入1后,t1這個線程并沒有結(jié)束。而是一直在while中循環(huán)。而t2線程已經(jīng)執(zhí)行完了。
以上情況,就叫做內(nèi)存可見性問題
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
這里使用匯編來理解,大概分為兩步操作:

  1. load,把內(nèi)存中flag的值,讀到寄存器中。
  2. cmp,把寄存器中的值,和0進行比較。根據(jù)比較結(jié)果,決定下一步往哪個地方執(zhí)行(條件跳轉(zhuǎn)指令)

上述循環(huán)循環(huán)體為空,循環(huán)執(zhí)行速度極快。循環(huán)執(zhí)行很多次,在t2真正修改之前,load得到的結(jié)果都是一樣的。另一方面,load操作和cmp操作相比,速度慢的多得多。由于load執(zhí)行速度太慢(相比于cmp),再加上反復(fù)load到的結(jié)果都是一樣的,JVM就做出了一個大膽的決定:不再真正的重復(fù)load,判定沒有人修改flag值(但實際上是有人在修改的,t2在修改),直接就讀取一次就好。(編譯器優(yōu)化的一種方式)
內(nèi)存可見性問題:一個線程針對一個變量進行讀取操作,同時另一個線程針對這個變量進行修改。此時讀到的值,并不一定是修改之后的值。(jvm/編譯器在多線程環(huán)境下優(yōu)化時殘生了誤判)
此時,我們就需要手動干預(yù)了。我們可以給flag這個變量加上volatile關(guān)鍵字。告訴編譯器,這個變量是“易變”的,需要每一次都重新讀取這個變量的內(nèi)容。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
volatile不保證原子性,原子性是由synchronized來保證的。

wait和notify

舉個列子:
t1,t2兩個線程,希望t1先執(zhí)行任務(wù),任務(wù)執(zhí)行快結(jié)束了讓t2來干,就可以讓t2先wait(阻塞,主動放棄cpu)。等t1任務(wù)執(zhí)行快結(jié)束了,在通過notify通知t2,把t2喚醒,讓t2開始執(zhí)行任務(wù)。
上述場景中,使用join和sleep可以嗎?
使用join,必須要t1徹底執(zhí)行完,t2才能執(zhí)行。如果希望t1執(zhí)行一半任務(wù)然后讓t2執(zhí)行,join無法完成。
使用sleep,必須制定一個休眠時間,但是t1執(zhí)行任務(wù)的時間是難以估計的。
使用wait和notify可以解決上述問題。

wait

wait進行阻塞,某個線程調(diào)用wait方法,就會進入阻塞,此時就處于WAITING.
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
這個異常,很多帶有阻塞功能的方法都帶,這些方法都是可以被interrupt方法通過以上異常喚醒。
我們再來看一個代碼:

public class ThreadDemo17 {
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("執(zhí)行完畢!");
            });

            t.start();
            System.out.println("wait前");
            t.wait();
            System.out.println("wait后");
        }
}

【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
這里會出現(xiàn)非法的鎖狀態(tài)異常。鎖的狀態(tài)一般是被加鎖的狀態(tài),被解鎖的狀態(tài)。
為什么會出現(xiàn)這個異常呢?和wait的操作有關(guān):

wait的操作:

  1. 先釋放鎖
  2. 進行阻塞等待
  3. 收到通知后,重新嘗試獲取鎖,并且在獲取鎖后,繼續(xù)往下執(zhí)行。

上述代碼沒有鎖就想要釋放鎖,所以出現(xiàn)了非法的鎖狀態(tài)異常。
因此,wait操作要搭配synchronized來使用。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

notify

wait和notify一般搭配使用。notify方法用來喚醒wait等待的線程, wait能夠釋放鎖, 使線程等待, 而notify喚醒線程后能夠獲取鎖, 然后使線程繼續(xù)執(zhí)行。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
如果上述代碼中,t1還沒有執(zhí)行wait,t2已經(jīng)執(zhí)行了notify,那么此時的聲明就是沒有用的。t2執(zhí)行notify后,t1執(zhí)行wait后會一直阻塞等待。
注意上述代碼在t2喚醒t1之后,t1和t2之間的執(zhí)行是隨機的,也是就標(biāo)號3和標(biāo)號4的地方的順序是不確定的。

方法 效果
wait(); 無參數(shù),一直等直到notify喚醒
wait(時間參數(shù)); 指定最長等待時間

notifyAll

notify方法只是喚醒某一個等待線程. 使用notifyAll方法可以一次喚醒所有的等待線程.
一般情況下,使用notify。因為全部喚醒會導(dǎo)致線程之間搶占式執(zhí)行。不一定安全。

wait和sleep的區(qū)別

相同點:

  • 都可以使線程暫停一段時間來控制線程之間的執(zhí)行順序.
  • wait可以設(shè)置一個最長等待時間, 和sleep一樣都可以提前喚醒.

不同點:

  • wait是Object類中的一個方法, sleep是Thread類中的一個方法.
  • wait必須在synchronized修飾的代碼塊或方法中使用, sleep方法可以在任何位置使用.
  • wait被調(diào)用后當(dāng)前線程進入BLOCK狀態(tài)并釋放鎖,并可以通過notify和notifyAll方法進行喚醒;sleep被調(diào)用后當(dāng)前線程進入TIMED_WAITING狀態(tài),不涉及鎖相關(guān)的操作.
  • 使用sleep只能指定一個固定的休眠時間, 線程中執(zhí)行操作的執(zhí)行時間是無法確定的; 而使用wait在指定操作位置就可以喚醒線程.
  • sleep和wait都可以被提前喚醒, interruppt喚醒sleep, 是會報異常的, 這種方式是一個非正常的執(zhí)行邏輯; 而noitify喚醒wait是正常的業(yè)務(wù)執(zhí)行邏輯, 不會有任何異常.

小練習(xí)

有三個線程, 分別只能打印 A, B, C. 控制三個線程固定按照 ABC 的順序來打印.

public class ThreadDemo18 {
    // 有三個線程, 分別只能打印 A, B, C. 控制三個線程固定按照 ABC 的順序來打印.
    public static void main(String[] args) throws InterruptedException {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()->{
            System.out.println("A");
            synchronized (locker1) {
                locker1.notify();
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker1) {
                try {
                    locker1.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
            System.out.println("B");
            synchronized (locker2) {
                    locker2.notify();
            }
        });
        Thread t3 = new Thread(()->{
            synchronized (locker2) {
                try {
                    locker2.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("C");
        });
        t2.start();
        t3.start();
        Thread.sleep(100);
        t1.start();
    }
}

創(chuàng)建locker1,供1,2使用
創(chuàng)建locker2,供2,3使用
線程3,locker2.wait()
線程2, locker1.wait()喚醒后執(zhí)行l(wèi)ocker2.notify
線程1執(zhí)行自己的任務(wù),執(zhí)行完后locker.notify
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

多線程案例

單例模式

單例模式是設(shè)計模式的一種。
單例模式能保證某個類在程序中只存在唯一一份的實例,而不會創(chuàng)建出多個實例。
單例模式具體的實現(xiàn)方式分為“餓漢”和“懶漢”。

餓漢模式

類加載的同時,創(chuàng)建實例。
類對象在一個java進程中,只有一份。因此類對象內(nèi)部的類屬性也是唯一的。
在類加載階段,就把實例創(chuàng)建出來了。

//餓漢模式的單例模式的實現(xiàn)
//保證Singleton這個類只能創(chuàng)建出一個實例
class Singleton{
    //在此處,先將實例創(chuàng)建出來
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
    //為了避免Singleton類不小心被多復(fù)制出來
    //把構(gòu)造方法設(shè)為private,在類外,無法通過new的方式來創(chuàng)建一個Singleton
    private Singleton(){

    }
}
public class ThreadDemo19 {
    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        //Singleton s3 = new Singleton();
        System.out.println(s == s2);
    }
}

【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

  • static保證這個實例唯一
  • static保證這個實例被創(chuàng)建出來。

懶漢模式

class SingletonLazy{
    private static SingletonLazy instance = null;


    public static SingletonLazy getIsntance() {
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
}
public class ThreadDemo20 {
        public static void main(String[] args) {
            SingletonLazy s = SingletonLazy.getIsntance();
            SingletonLazy s2 = SingletonLazy.getIsntance();
            System.out.println(s == s2);
        }
}

【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

在多線程中調(diào)用instance,餓漢模式是線程不安全的。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
那么如何保證懶漢模式線程安全呢?
**加鎖。**線程安全的本質(zhì)問題,就是讀,比較,寫這三個操作不是原子的。所以我們可以加鎖來解決線程安全問題。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式

但是,加鎖操作就導(dǎo)致每次調(diào)用getInstance都需要花一定的開銷。而我們的加鎖只針對new對象之前,所以我們就可以判斷一下對象是否創(chuàng)建,再去決定加鎖。
如果對象創(chuàng)建了,就不加鎖。如果對象沒有創(chuàng)建,就加鎖。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
上述代碼還存在一個問題,即內(nèi)存可見性問題:
假如調(diào)用getInstance的線程有很多,此時代碼就有可能被優(yōu)化(第一次讀內(nèi)存,后續(xù)讀的是寄存器/cache)
除此之外,可能還會涉及到指令重排序。
【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式
上述代碼中,分為三個步驟:

  1. 申請內(nèi)存空間
  2. 調(diào)用構(gòu)造方法,把這個內(nèi)存空間初始化成一個對象
  3. 把內(nèi)存空間的地址賦值給instance引用

而編譯器的指令重排序操作就會調(diào)整代碼執(zhí)行順序,123可能會變成132.(單線程中沒有影響)
我們可以給代碼中加上volatile。
volatile有兩個功能:

  1. 解決內(nèi)存可見性
  2. 禁止指令重排序。

以下為懶漢模式的單例模式的完整代碼:文章來源地址http://www.zghlxwxcb.cn/news/detail-425019.html

class SingletonLazy{
    private volatile static SingletonLazy instance = null;


    public static SingletonLazy getInstance() {
        if(instance ==null){
            synchronized (SingletonLazy.class){
                if(instance == null){
                    instance = new SingletonLazy();
                }
            }
        }  

        return instance;
    }
}
public class ThreadDemo20 {
        public static void main(String[] args) {
            SingletonLazy s = SingletonLazy.getInstance();
            SingletonLazy s2 = SingletonLazy.getInstance();
            System.out.println(s == s2);
        }
}

到了這里,關(guān)于【JavaEE初階】多線程(三)volatile wait notify關(guān)鍵字 單例模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • volatile 關(guān)鍵字詳解

    volatile 關(guān)鍵字詳解

    目錄 volatile volatile 關(guān)鍵用在什么場景下: volatile 防止編譯器優(yōu)化: volatile ? 是一個在許多編程語言中(包括C和C++)用作的標(biāo)識符。它用于告訴編譯器不要對帶有該修飾的變量進行優(yōu)化,以確保變量在特定情況下的可見性和預(yù)測性。 在C和C++中, volatile

    2024年02月11日
    瀏覽(25)
  • 【C】volatile 關(guān)鍵字

    【C】volatile 關(guān)鍵字

    1)基本概念 const 是C語言的一個。 const 用于告訴編譯器相應(yīng)的變量可能會在程序的控制之外被修改,因此編譯器不應(yīng)該對其進行優(yōu)化。 聲明語法: 作用: 防止編譯器優(yōu)化,確保對變量的每次訪問都是實際的讀寫操作,而不是使用緩存中的值。 用于表示可能會 被異步

    2024年01月22日
    瀏覽(42)
  • JAVA volatile 關(guān)鍵字

    volatile 是JAVA虛擬機提供的輕量級的同步機制,有三大特性 1、保證可見性? 2、不保證原子性? 3、禁止指令重排 JMM? JAVA內(nèi)存模型本身是一種抽象的概念并不真實存在 它描述的是一組規(guī)則或規(guī)范,提供這組規(guī)范定義了程序中各個變量(包括實例變量、靜態(tài)變量)的訪問方式。

    2024年02月13日
    瀏覽(24)
  • 【Java基礎(chǔ)】volatile關(guān)鍵字

    【Java基礎(chǔ)】volatile關(guān)鍵字

    關(guān)于作者:CSDN內(nèi)容合伙人、技術(shù)專家, 從零開始做過日活千萬級APP。 專注于分享各領(lǐng)域原創(chuàng)系列文章 ,擅長java后端、移動開發(fā)、人工智能等,希望大家多多支持。 我們繼續(xù)總結(jié)學(xué)習(xí)Java基礎(chǔ)知識,溫故知新。 volatile 是一個Java,可以用來修飾變量,volatile也被稱為輕

    2024年02月11日
    瀏覽(19)
  • C語言volatile關(guān)鍵字

    在C語言中, volatile 是一個類型修飾符,用于告訴編譯器對象的值可能會在編譯器無法檢測到的情況下被改變。這通常發(fā)生在以下兩種情況: 硬件的輸入/輸出操作,例如一個設(shè)備寄存器的讀取或?qū)懭搿?共享內(nèi)存的并行程序,其中一個線程修改了一個內(nèi)存位置,而另一個線程

    2024年02月07日
    瀏覽(29)
  • 淺析Java中volatile關(guān)鍵字

    淺析Java中volatile關(guān)鍵字

    ????????Java中的volatile用于修飾一個變量,當(dāng)這個變量被多個線程共享時,這個變量的值如果發(fā)生更新,每個線程都能獲取到最新的值。volatile在多線程環(huán)境下還會禁止指令重排序,確保變量的賦值操作按照代碼的順序執(zhí)行。需要注意是它不能保證變量操作的

    2024年01月21日
    瀏覽(23)
  • volatile關(guān)鍵字(輕量級鎖)

    volatile關(guān)鍵字(輕量級鎖)

    目錄 一、volatile出現(xiàn)背景 二、JMM概述 2.1、JMM的規(guī)定 ?三、volatile的特性 3.1、可見性 ?3.1.1、舉例說明 ?3.1.2、總結(jié) 3.2、無法保證原子性 3.2.1、舉例說明 3.2.2、分析 3.2.3、使用volatile對原子性測試 ?3.2.4、使用鎖機制 ?3.2.5、總結(jié) 3.3、禁止指令重排序 ?四、volatile的內(nèi)存語義 4

    2024年02月15日
    瀏覽(26)
  • Java中的volatile關(guān)鍵字實現(xiàn)原理

    在并發(fā)編程中,線程之間的可見性問題是非常重要的一項難題。Java中提供了一種解決并發(fā)可見性問題的機制,即volatile。 在本文中,我們將會講解Java中volatile的實現(xiàn)原理,為什么它能夠保證可見性,以及背后的實現(xiàn)原理涉及到的內(nèi)存屏障和JVM屏障等內(nèi)容。在學(xué)習(xí)

    2023年04月27日
    瀏覽(21)
  • C/C++ 中 volatile 關(guān)鍵字詳解

    轉(zhuǎn)載自菜鳥教程【C/C++ 中 volatile 詳解 | 菜鳥教程】 1、為什么用volatile? C/C++ 中的 volatile 和 const 對應(yīng),用來修飾變量,通常用于建立語言級別的 memory barrier。這是 BS 在 \\\"The C++ Programming Language\\\" 對 volatile 修飾詞的說明: A volatile specifier is a hint to a compiler that an ob

    2024年02月11日
    瀏覽(17)
  • Java面試題:請談?wù)凧ava中的volatile關(guān)鍵字?

    在Java中,volatile是一種特殊的修飾符,用于確保多線程環(huán)境下的變量 可見性和順序性 。當(dāng)一個變量被聲明為volatile時,它可以確保以下兩點: 內(nèi)存可見性 :當(dāng)一個線程修改了一個volatile變量的值,其他線程會立即看到這個改變。這是因為volatile會禁止CPU緩存和編

    2024年04月23日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包