久久r热视频,国产午夜精品一区二区三区视频,亚洲精品自拍偷拍,欧美日韩精品二区

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

淺談java 單例模式DCL的缺陷及單例的正確寫法

瀏覽:75日期:2022-08-23 16:08:22

1 前言

單例模式是我們經(jīng)常使用的一種模式,一般來(lái)說(shuō)很多資料都建議我們寫成如下的模式:

/** * Created by qiyei2015 on 2017/5/13. */public class Instance { private String str = ''; private int a = 0; private static Instance ins = null; /** * 構(gòu)造方法私有化 */ private Instance(){ str = 'hello'; a = 20; } /** * DCL方式獲取單例 * @return */ public static Instance getInstance(){ if (ins == null){ synchronized (Instance.class){if (ins == null){ ins = new Instance();} } } return ins; } }

但是這種方式其實(shí)是有缺陷的,具體什么缺陷呢?我們首先要了解JVM了內(nèi)存模型,請(qǐng)看下面分析

2 JVM內(nèi)存模型

JVM模型如下圖:

淺談java 單例模式DCL的缺陷及單例的正確寫法

這里著重介紹下VM Stack,其他的我相信都比較熟悉。

VM Stack是線程私有的區(qū)域。他是java方法執(zhí)行時(shí)的字典:它里面記錄了局部變量表、 操作數(shù)棧、 動(dòng)態(tài)鏈接、 方法出口等信息。

在《java虛擬機(jī)規(guī)范》一書中對(duì)這部分的描述如下:

棧幀( Frame)是用來(lái)存儲(chǔ)數(shù)據(jù)和部分過(guò)程結(jié)果的數(shù)據(jù)結(jié)構(gòu),同時(shí)也被用來(lái)處理動(dòng)態(tài)鏈接 (Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。

棧幀隨著方法調(diào)用而創(chuàng)建,隨著方法結(jié)束而銷毀——無(wú)論方法是正常完成還是異常完成(拋出了在方法內(nèi)未被捕獲的異常)都算作方法結(jié)束。

棧幀的存儲(chǔ)空間分配在 Java 虛擬機(jī)棧( §2.5.5)之中,每一個(gè)棧幀都有自己的局部變量表( Local Variables, §2.6.1)、操作數(shù)棧( OperandStack, §2.6.2)和指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池( §2.5.5)的引用。

java中某個(gè)線程在訪問堆中的線程共享變量時(shí),為了加快訪問速度,提升效率,會(huì)把該變量臨時(shí)拷貝一份到自己的VM Stack中,并保持和堆中數(shù)據(jù)的同步。

3 傳統(tǒng)DCL方式的缺陷

有了以上的基礎(chǔ)知識(shí)我們就可以知道DCL方式的缺陷在哪兒了。當(dāng)線程A在獲取了Instance.class鎖時(shí),對(duì)ins進(jìn)行 ins = new Instance() 初始化時(shí),由于這是很多條指令,jvm可能會(huì)亂序執(zhí)行。

這個(gè)時(shí)候如果線程B在執(zhí)行if (ins == null)時(shí),正常情況下,如果為true,說(shuō)明需要獲取Instance.class鎖,等待初始化。

但是這時(shí)候,假設(shè)線程A再?zèng)]有對(duì)ins進(jìn)行初始化完,比如只對(duì)str進(jìn)行了賦值,還沒有來(lái)的及對(duì)a進(jìn)行賦值,假如jvm將未完成賦值的值拷貝回堆中,這個(gè)時(shí)候線程B有可能讀到的值就不是為null了,就會(huì)造成數(shù)據(jù)丟失的情況。這時(shí)候我們發(fā)現(xiàn)線程B獲取的對(duì)象中a的值是0,而不是20

因?yàn)椋簩?duì)ins的寫操作不 happen-before 對(duì)它的讀操作

這就是DCL方式的缺陷,那么怎么避免呢?首先我們需要了解分析多線程的一大利器

4 happen-before原則

Happen-Before規(guī)則:

1 同一個(gè)線程中,書寫在前面的操作happen-before書寫在后面的操作。這條規(guī)則是說(shuō),在單線程 中操作間happen-before關(guān)系完全是由源代碼的順序決定的,這里的前提“在同一個(gè)線程中”是很重要的,這條規(guī)則也稱為單線程規(guī)則 。

這個(gè)規(guī)則多少說(shuō)得有些簡(jiǎn)單了,考慮到控制結(jié)構(gòu)和循環(huán)結(jié)構(gòu),書寫在后面的操作可能happen-before書寫在前面的操作,不過(guò)我想讀者應(yīng)該明白我的意思。

2 對(duì)鎖的unlock操作happen-before后續(xù)的對(duì)同一個(gè)鎖的lock操作。這里的“后續(xù)”指的是時(shí)間上的先后關(guān)系,unlock操作發(fā)生在退出同步塊之后,lock操作發(fā)生在進(jìn)入同步塊之前。這是條最關(guān)鍵性的規(guī)則,線程安全性主要依賴于這條規(guī)則。

但是僅僅是這條規(guī)則仍然不起任何作用,它必須和下面這條規(guī)則聯(lián)合起來(lái)使用才顯得意義重大。這里關(guān)鍵條件是必須對(duì)“同一個(gè)鎖”的lock和unlock。

如果操作A happen-before操作B,操作B happen-before操作C,那么操作A happen-before操作C。這條規(guī)則也稱為傳遞規(guī)

3 對(duì)volatile字段的寫操作happen-before后續(xù)的對(duì)同一個(gè)字段的讀操作.(Java5 新增)

4 單例模式的正確寫法

有了以上的分析我們知道,我們只需要在保證對(duì)ins的訪問是讀在寫之后即可,因此正確的做法是在ins 前加上一個(gè)關(guān)鍵字volatile。因此DCL的正確寫法應(yīng)該如下:

/** * Created by qiyei2015 on 2017/5/13. */public class Instance { private String str = ''; private int a = 0; private volatile static Instance ins = null; /** * 構(gòu)造方法私有化 */ private Instance(){ str = 'hello'; a = 20; } /** * DCL方式獲取單例 * @return */ public static Instance getInstance(){ if (ins == null){ synchronized (Instance.class){if (ins == null){ ins = new Instance();} } } return ins; }}

其實(shí)單例模式也有另一種我很喜歡的寫法,那就是內(nèi)部類:

/** * Created by qiyei2015 on 2017/5/13. */public class Instance { /** * 構(gòu)造方法私有化 */ private Instance(){ } private static class SingleHolder{ private static final Instance ins = new Instance(); } /** * 內(nèi)部類方式獲取單例 * @return */ public static Instance getInstance(){ return SingleHolder.ins; } }

這種從jvm虛擬機(jī)上保證了單例,并且也是懶式加載。

以上這篇淺談java 單例模式DCL的缺陷及單例的正確寫法就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 北海市| 乐昌市| 永川市| 富宁县| 安新县| 财经| 井陉县| 湄潭县| 礼泉县| 蓬安县| 长岛县| 肥西县| 聊城市| 那曲县| 高淳县| 贵溪市| 丰城市| 临泽县| 丰原市| 仲巴县| 积石山| 格尔木市| 颍上县| 安宁市| 福贡县| 娱乐| 浦城县| 托里县| 保靖县| 莎车县| 无极县| 宁晋县| 灌云县| 天等县| 南京市| 南乐县| 建瓯市| 长沙县| 凯里市| 金溪县| 柏乡县|