java中你的單例在裸奔嗎?
在上一篇文章java中你確定用對單例了嗎?中提到單例可以被惡意的破壞,如序列化破壞和反射破壞單例的結構
,好的,這個有點偏,確實在實際開發中基本也不會在意到這個問題,但是誰叫我們搞的是java,所以這個問題我們有必要知道下,這算是提高下自己的安全意識,有句古話是這樣說的,居安思危嘛.
好,請帶著歡樂
的心情繼續往下看.
通過反射破解單例結構
java中你的單例是不是一直在裸奔,估計你用的是假的單例.
我們就使用普通懶漢式
來做示例吧.
public class SingletonDemo6 implements Serializable{
private static SingletonDemo6 s1;
//普通懶漢式寫法
public static synchronized SingletonDemo6 getInstance() {
if (s1 == null) {
s1 = new SingletonDemo6();
}
return s1;
}
看看下面測試結果.
在正常情況下,沒毛病,輸出結果一毛一樣.
@Test
public void test() throws Exception{
SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
SingletonDemo6 nomarlInstance2 = SingletonDemo6.getInstance();
//這兩個單例輸入的例項都是一樣
System.out.println(nomarlInstance1);
System.out.println(nomarlInstance2);
log:
com.relice.singleton.SingletonDemo6@5a10411
com.relice.singleton.SingletonDemo6@5a10411
當反射遇上單例
看下面反射破解單例的測試程式碼,輸出兩個不同的結果.
@Test
public void test() throws Exception{
SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
Class<SingletonDemo6> forName = (Class<SingletonDemo6>) Class
.forName("com.relice.singleton.SingletonDemo6");
Constructor<SingletonDemo6> c = forName.getDeclaredConstructor();
//繞過許可權管理,獲取private
c.setAccessible(true);
//通過反射拿到`SingletonDemo6`的例項
SingletonDemo6 reflectInsatnce = c.newInstance();
// 兩者的輸出結是不一樣的
System.out.println(nomarlInstance1);
System.out.println(reflectInsatnce);
log:
com.relice.singleton.SingletonDemo6@5a10411
com.relice.singleton.SingletonDemo6@2ef1e4fa
如何解決這種問題?
大神說遇到問題不要急,先分析問題出現的原因.
-
forName.getDeclaredConstructor();
主要就是獲取無引數構造. - 也就是說通過反射拿到了私有構造方法從而再次建立例項.
知道問題的原因那就好辦了.
我們可以在SingletonDemo6
的構造方法裡做判斷,避免他再次建立例項.
// 解決反射 多獲取物件問題
private SingletonDemo6() {
if (s1 != null) {
try {
throw new RuntimeException("禁止反射獲取物件");
} catch (Exception e) {
e.printStackTrace();
}
}
}
這樣如果有人想要通過反射破壞單例結構,那就會丟擲執行時異常.
log:
java.lang.RuntimeException: 禁止反射獲取物件 at
com.relice.singleton.SingletonDemo6.<init>(SingletonDemo6.java:24)
通過序列化破解單例結構
還是用SingletonDemo6
來測試,通過序列化獲取到例項,得出了兩個不一樣的結果.
憋說話,繼續看問題.
@Test
public void test() throws Exception{
SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
//把物件寫入檔案
File file = new File(
"/xxx/xxx/xxx/xxx/xxx/SingletonDemo/a.txt");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(nomarlInstance1);
oos.close();
fos.close();
//序列化把物件讀取
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonDemo6 serilizeInstance = (SingletonDemo6) ois.readObject();
System.out.println(nomarlInstance1);
System.out.println(serilizeInstance);
}
log:
com.relice.singleton.SingletonDemo6@68de145
com.relice.singleton.SingletonDemo6@27fa135a
如何解決這種問題?
老規矩我們還是先分析.
- 在序列化裡我們可以通過流的方式將一個物件寫入記憶體中
oos.writeObject
,因此也就可以將這個物件從記憶體中讀取出來. - 但是當序列化遇到單例問題就發生了,在讀取物件時jvm會重新給序列化物件分配地址.
- 因此我們要考慮的問題就是反序列化
解決方法:
當反序列化的時候:
JVM會呼叫readObject方法,將我們剛剛在writeObject方法序列化好的屬性,反序列化回來. 然後在readResolve方法中,我們也可以指定JVM返回我們特定的物件(不是剛剛序列化回來的物件). .該方法的分析見
private Object readResolve() throws ObjectStreamException{
return SingletonDemo6.s1;
}
可能我們會考慮到一個問題,就是之前的反射不是在構造方法裡處理解決問題嗎,那是不是序列化也可以?
要知道序列化和反序列化,在java中是使用位元組碼技術生成物件,並不會執行構造器方法.
android開發中要注意的問題
接觸過android的都知道,在其中四大元件中就有三大元件是有生命週期的,生命週期最關鍵的的就是context
,連基本的Activty 和 Service都是從Context派生出來的,也因為這生命週期讓android應用在使用者體驗上附上了一些生命氣息,,如視訊播放根據生命週期來處理播放狀態;如我們想邊聽音樂邊幹些別事情,這是Service的生命週期就有幫我們做到等..
我想說的就是.
android元件中的生命週期是尤其重要,因此我們要善待context
,在處理或者使用到元件的生命週期時也要注意規範,提高容錯率.
Android開發 單例模式導致記憶體洩露
實際開發中用到最多的設計模式,如果單例設計模式認第二,我想沒有敢認第一的.如工具類,application類,配置檔案等.
不扯淡了,以工具類為例,存在記憶體洩露問題的一些程式碼片段像下面這樣:
public class Util {
private Context mContext;
private static Util mInstance;
private Util(Context context) {
this.mContext = context;
}
public static Util getInstance(Context context) {
if (mInstance == null) {
synchronized (Util.class) {
if (mInstance == null) {
mInstance = new Util(context);
}
}
}
return mInstance;
}
}
其實實際開發中排查問題和定位問題一直是佔據了大部分的工作時間,因此擁有一個好的開發方式可以減少很多不必要的時間浪費,這裡有篇關於使用android studio檢查記憶體洩漏的文章覺得不錯.
分析下問題:
-
Util.getInstance(this);
這個this使用的就是Activity的context. - Activity的生命週期都是比較短暫的,當使用者切換頁面的時候基本都會把activity銷燬掉,因此貫穿整個生命週期的context類也會被相應的相會.
- 而
Util.getInstance(mContext);
在工具類裡封裝一些耗時的操作也是常見的,當Activity生命週期結束,但Util類裡面卻還存在A的引用 (mContext),這樣Activity佔用的記憶體就一直不能回收,而Activity的物件也不會再被使用.從而造成記憶體洩漏
.
解決問題:
在Activity中,可以用
Util.getInstance(getApplicationContext());
或Util.getInstance(getApplication());
來代替。
因為Application的生命週期是貫穿整個程式的,所以Util類持有它的引用,也不會造成記憶體洩露問題。使用弱引用讓這個引用自動被回收
弱引用也是用來描述非必需物件的,當JVM進行垃圾回收時,無論記憶體是否充足,都會回收被弱引用關聯的物件。
下面程式碼是使用了弱引用之後
public class WeakRUtil {
private static Context mContext;
private static WeakRUtil mInstance;
private WeakRUtil(Context context) {
this.mContext=context;
}
public static WeakRUtil getInstance(Context context) {
if (mInstance == null) {
WeakReference<Context> actWeakRF = new WeakReference<Context>(context);
//通過get來獲取弱引用關聯物件,如果為null 則就是被回收了
mContext = actWeakRF.get();
synchronized (WeakRUtil.class) {
if (mInstance == null) {
mInstance = new WeakRUtil(mContext);
}
}
}
return mInstance;
}
public void test() {
System.out.println("util_test");
}
}
在java中,用java.lang.ref.WeakReference
類來表示。
當呼叫了System.gc(); 則即使記憶體足夠,該引用內的資料都會被回收;
好了,我們繼續總結下:
- 單例的優點就是提供了對唯一例項的受控訪問,減少記憶體分配,提高系統效能,也因為這個優點所以我們要避免單例被惡意的破壞掉了其結構.
- 單例在實際開發中經常使用到,而使用方法也是各種各種,為了讓程式碼有更好的健壯性,因此一些開發中的程式設計習慣要養成,避免如oom異常.
相關文章
- 你的Elasticsearch在“裸奔”嗎?Elasticsearch
- 你會單例嗎?單例
- 你真的會寫單例模式嗎——Java實現單例模式Java
- 同學你的單例,夠面試嗎?單例面試
- 在Java中,你真的會日期轉換嗎Java
- 您的單例模式,真的單例嗎?單例模式
- 你瞭解單例模式的最佳實踐嗎?單例模式
- 你真的理解了java單例模式嗎?講別人都忽略的細節!Java單例模式
- Java中的單例模式最全解析Java單例模式
- 單例模式的七種寫法,你都知道嗎?單例模式
- 你的單例模式真的是執行緒安全的嗎?單例模式執行緒
- 八、目前JDK中,單例模式這3種寫法你知道嗎?JDK單例模式
- java設計模式之單例模式你真的會了嗎?(懶漢式篇)Java設計模式單例
- 淺析Java併發中的單例模式Java單例模式
- JAVA中簡單的for迴圈竟有這麼多坑,你踩過嗎Java
- 【設計模式】你的單例模式真的是生產可用的嗎?設計模式單例
- 你還在Java8中使用迴圈語句嗎?Java
- 你在工作的城市中買房了嗎?
- JAVA中的函式介面,你都用過嗎Java函式
- Java中6種單例實現方法Java單例
- java 單例模式Java單例模式
- Java單例模式Java單例模式
- JAVA中實現單例(Singleton)模式的八種方式Java單例模式
- RabbitMq知識整理以及在java語言下的簡單例項MQJava單例
- JVM中java例項物件在記憶體中的佈局JVMJava物件記憶體
- 你知道在springboot中如何使用WebSocket嗎Spring BootWeb
- 別再讓你的web頁面在使用者瀏覽器端裸奔Web瀏覽器
- 【極客思考】設計模式:你確定你真的理解了單例模式嗎?設計模式單例
- Java中的深淺拷貝問題,你清楚嗎?Java
- Flutter 中的單例模式Flutter單例模式
- ABAP和Java單例模式的攻防Java單例模式
- java中的Arrays這個工具類你真的會用嗎Java
- DCL單例模式中的缺陷及單例模式的其他實現單例模式
- JS中的單例模式及單例模式原型類的實現JS單例模式原型
- java-單例詳解Java單例
- Java基礎-單例模式Java單例模式
- java單例設計模式Java單例設計模式
- 淺析單例模式--Java單例模式Java
- 單例模式的 Java 實現與思考單例模式Java