日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

android 单例的作用,Android中单例模式的几个坑

發(fā)布時(shí)間:2024/8/1 Android 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 单例的作用,Android中单例模式的几个坑 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

先來看這樣一個(gè)單例,稍微有點(diǎn)經(jīng)驗(yàn)的同學(xué)可能都會(huì)說,這樣的單例是非線程安全的。要加個(gè)volatile關(guān)鍵字才可以。class?Singleton{????????private?static??Singleton?singleton;????????private?Singleton(){};????????public?static?Singleton?getInstance()

{????????????if?(singleton==null)

{????????????????synchronized?(Singleton.class)

{????????????????????if?(singleton==null)

{

singleton=new?Singleton();

}

}

}????????????return?singleton;

}

}

復(fù)制代碼但是你要是問他,為什么是非線程安全的單例就答不出來了。搞清楚這個(gè)問題其實(shí) 對我們的多線程理解是很有好處的。

我們首先明確一下對于jvm來說,完成對一個(gè)變量的寫操作 到底是如何進(jìn)行的。

寫操作:

(1)先把值寫入cpu的高速緩存cache中。(2)然后再把這個(gè)cache中的值拷貝到ram(也就是我們的內(nèi)存)中。

注意啊,對于一個(gè)寫操作來說,這個(gè)(1)(2) 可不是原子操作,很有可能(1)執(zhí)行完畢以后,cpu又去干了其他事情,

并沒有第一時(shí)間把cache的值 寫入到ram中。而我們讀操作,都是從ram中去讀取一個(gè)值的。

所以這里我們可以想一下,如果是多線程場景的話,會(huì)有一些坑。

然后再說一個(gè)概念,對于 singleton=new Singleton(); 這一條語句來說,他顯然不是一條指令就可以完成的。

正常情況來說,我們要完成這條語句涉及到的指令大約如下:

1.申請一段堆內(nèi)存空間

2.在這個(gè)堆內(nèi)存空間中把我們需要的對象初始化完畢

3.把singleton這個(gè)引用指向我們的堆內(nèi)存空間地址。

但是坑爹就坑爹在,虛擬機(jī)會(huì)有一個(gè)指令重排序的概念。當(dāng)虛擬機(jī)發(fā)現(xiàn)單線程下 指令的順序變更不會(huì)導(dǎo)致結(jié)果異常的時(shí)候

就會(huì)觸發(fā)指令重排序的機(jī)制, 他會(huì)導(dǎo)致上述的 123順序發(fā)生變更,比如我們把順序改成132 你就會(huì)發(fā)現(xiàn) 結(jié)果還是一樣的。

(指令重排序的觸發(fā)機(jī)制準(zhǔn)確的來說是happens before原則 有興趣的同學(xué)可以深挖)

如果發(fā)生132的執(zhí)行順序 會(huì)發(fā)生什么?

假設(shè)線程a 進(jìn)入到了同步代碼塊中,這個(gè)時(shí)候觸發(fā)了指令重排序,順序變成132,假設(shè)cpu這個(gè)時(shí)候執(zhí)行了13。然后轉(zhuǎn)頭

去執(zhí)行線程b,線程b 進(jìn)入getInstance方法的時(shí)候,他發(fā)現(xiàn)singleton 不是null了,于是歡天喜地的return了,

但是要知道這個(gè)時(shí)候線程a的 2還沒執(zhí)行,也就是說singleton雖然不是空,但是他指向的地址空間里面啥都沒有,對象還沒有初始化。所以這是一個(gè)非常大的隱患,雖然他發(fā)生的概率極低,低到我現(xiàn)在都沒有復(fù)現(xiàn)過這種現(xiàn)象,但是依舊有概率。

那么正確的寫法:class?Singleton{?????private?static?volatile?Singleton?singleton;?????private?Singleton(){};?????public?static?Singleton?getInstance()

{?????????if?(singleton==null)

{?????????????synchronized?(Singleton.class)

{?????????????????if?(singleton==null)

{

singleton=new?Singleton();

}

}

}?????????return?singleton;

}

}

復(fù)制代碼有很多人就會(huì)說 volatile 這個(gè)關(guān)鍵字以后,singleton=new Singleton(); 就不會(huì)發(fā)生指令重排了,所以這么做是正確的。

現(xiàn)在明確的告訴你,上面這個(gè)觀點(diǎn)是錯(cuò)誤的

singleton=new Singleton(); ?這條語句背后的指令依舊有概率發(fā)生指令重排,只不過 volatile修飾過以后,在 這條語句背后的

指令完全執(zhí)行完畢以前,對singleton這個(gè)引用的讀操作全部被屏蔽了。

也就是說 132的執(zhí)行順序依舊會(huì)發(fā)生,只不過 當(dāng)執(zhí)行完13 而2沒有執(zhí)行的時(shí)候,volatile修飾過的這個(gè)變量,所有對他的讀操作

都會(huì)暫時(shí)屏蔽,等待2操作執(zhí)行完以后,才會(huì)進(jìn)行讀操作。

這才是volatile關(guān)鍵字加上去以后的作用。

android很多代碼比如eventbus的單例就是用的上述寫法。

當(dāng)然了,上述寫法是典型的懶漢寫法,所謂懶漢你就理解成用的時(shí)候才實(shí)例化,不用的話不實(shí)例化。

但是如果你的需求是這個(gè)單例無論在什么情況下都會(huì)存在,你當(dāng)然可以寫成餓漢,餓漢的寫法更簡單。

缺點(diǎn)就是他會(huì)一直占用內(nèi)存。餓漢寫法很多,我寫個(gè)最簡單的:class?Singleton?{?????//最簡單的寫法就是這個(gè)了,直接public就行

public?static?final?Singleton?instance?=?new?Singleton();?????private?Singleton()?{

}

}

復(fù)制代碼單例序列化會(huì)破壞對象唯一性嗎?

答案是會(huì)的:package?com.wuyue.test;import?java.io.*;/**

*?Created?by?16040657?on?2019/2/12.

*/public?class?Test2?{????public?static?void?main(String?args[])?{

Singleton?s1?=?Singleton.instance;

File?f?=?new?File("../test.txt");????????try?{

ObjectOutputStream?oos?=?new?ObjectOutputStream(new?FileOutputStream(f));

oos.writeObject(s1);

oos.close();

ObjectInputStream?ois?=?new?ObjectInputStream(new?FileInputStream(f));

Singleton?s3?=?(Singleton)?ois.readObject();

System.out.println("s1==s3:"?+?(s1?==?s3));

}?catch?(IOException?e)?{

e.printStackTrace();

}?catch?(ClassNotFoundException?e)?{

e.printStackTrace();

}

}????static?class?Singleton?implements?Serializable?{????????//最簡單的寫法就是這個(gè)了,直接public就行

public?static?final?Singleton?instance?=?new?Singleton();????????private?Singleton()?{

}//????????//這個(gè)方法就可以保證序列化和反序列化得到的對象是同一個(gè)了//????????private?Object?readResolve()?{//????????????return?instance;//????????}

}

}

復(fù)制代碼代碼比較簡單,大家可以測試一下,s1和s3就是2個(gè)不同的對象,但是如果把注釋掉的readResolve方法放開的話,你就會(huì)發(fā)現(xiàn)

這個(gè)問題解決了,序列化和反序列化是同一個(gè)對象了。

對外部公開提供的sdk的單例要注意些什么?

尤其是對于很多金融安全類的sdk來說,如果你這個(gè)里面有單例的話,涉及到安全性要盡可能的不被業(yè)務(wù)方hook,

其中尤其要注意的就是 有人可能會(huì)利用反射來new一個(gè)對象,破壞單例

解決這個(gè)問題也不難,private?Singleton()?{????????????//防止有人利用反射惡意修改

if?(null?!=?instance)?{????????????????throw?new?RuntimeException("dont?construct?more!");

}

}

復(fù)制代碼項(xiàng)目中的單例太多,如何有效管理?

其實(shí)就拿map管理就可以了,android里面的 wms,ams 等等系統(tǒng)單例服務(wù)都是這樣的。你傳一個(gè)key進(jìn)去 返回一個(gè)單例給你。

這個(gè)真的很有用哦,特別是大型工程,可以有效管理單例,文檔輸出就簡單許多。static?class?SingletonManager?{

private?static?Map?objectMap?=?new?HashMap<>();?????private?SingletonManager()?{

}?????public?static?void?registerService(String?key,?Object?ins)?{?????????if?(!objectMap.containsKey(key))?{

objectMap.put(key,?ins);

}

}?????public?static?Object?getService(String?key)?{?????????return?objectMap.get(key);

}

}

復(fù)制代碼android中使用單例還要注意些什么?

最主要的就是盡量不要利用單例模式存儲傳遞數(shù)據(jù),因?yàn)閍pp掛在后臺的時(shí)候進(jìn)程會(huì)容易被殺掉,如果回到前臺再取這個(gè)單例里的

數(shù)據(jù)很容易就取到個(gè)null,所以android中寫單例的原則就是:

原則上不允許用單例模式傳遞數(shù)據(jù),如果一定要這么做,請考慮數(shù)據(jù)恢復(fù)現(xiàn)場。

作者:Android進(jìn)階開發(fā)

鏈接:https://www.jianshu.com/p/4c05aa06545d

總結(jié)

以上是生活随笔為你收集整理的android 单例的作用,Android中单例模式的几个坑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。