91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android中單例模式的一些坑小結

發布時間:2020-09-25 23:52:32 來源:腳本之家 閱讀:134 作者:DK_BurNIng 欄目:移動開發

前言

單例模式最初的定義出現于《設計模式》(艾迪生維斯理, 1994):“保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。”

而我對單例的理解是,在可控的范圍內充當全局變量的作用,就相當于C語言中一個全局結構體。

首先來看這樣一個單例,稍微有點經驗的同學可能都會說,這樣的單例是非線程安全的。要加個volatile關鍵字才可以。

 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;
  }
 }

但是你要是問他,為什么是非線程安全的單例就答不出來了。搞清楚這個問題其實 對我們的多線程理解是很有好處的。
我們首先明確一下對于jvm來說,完成對一個變量的寫操作 到底是如何進行的。

寫操作:

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

注意啊,對于一個寫操作來說,這個(1)(2) 可不是原子操作,很有可能(1)執行完畢以后,cpu又去干了其他事情,
并沒有第一時間把cache的值 寫入到ram中。而我們讀操作,都是從ram中去讀取一個值的。

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

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

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

1.申請一段堆內存空間

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

3.把singleton這個引用指向我們的堆內存空間地址。

但是坑爹就坑爹在,虛擬機會有一個指令重排序的概念。當虛擬機發現單線程下 指令的順序變更不會導致結果異常的時候
就會觸發指令重排序的機制, 他會導致上述的 123順序發生變更,比如我們把順序改成132 你就會發現 結果還是一樣的。

(指令重排序的觸發機制準確的來說是happens before原則 有興趣的同學可以深挖)

如果發生132的執行順序 會發生什么?

假設線程a 進入到了同步代碼塊中,這個時候觸發了指令重排序,順序變成132,假設cpu這個時候執行了13。然后轉頭
去執行線程b,線程b 進入getInstance方法的時候,他發現singleton 不是null了,于是歡天喜地的return了,
但是要知道這個時候線程a的 2還沒執行,也就是說singleton雖然不是空,但是他指向的地址空間里面啥都沒有,對象還沒有初始化。所以這是一個非常大的隱患,雖然他發生的概率極低,低到我現在都沒有復現過這種現象,但是依舊有概率。

那么正確的寫法:

  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;
  }
 }

有很多人就會說 volatile 這個關鍵字以后,singleton=new Singleton(); 就不會發生指令重排了,所以這么做是正確的。

現在明確的告訴你,上面這個觀點是錯誤的

singleton=new Singleton();  這條語句背后的指令依舊有概率發生指令重排,只不過 volatile修飾過以后,在 這條語句背后的指令完全執行完畢以前,對singleton這個引用的讀操作全部被屏蔽了。

也就是說 132的執行順序依舊會發生,只不過 當執行完13 而2沒有執行的時候,volatile修飾過的這個變量,所有對他的讀操作
都會暫時屏蔽,等待2操作執行完以后,才會進行讀操作。

這才是volatile關鍵字加上去以后的作用。

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

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

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

缺點就是他會一直占用內存。餓漢寫法很多,我寫個最簡單的:

 class Singleton {
  //最簡單的寫法就是這個了,直接public就行
  public static final Singleton instance = new Singleton();

  private Singleton() {
  }

 }

單例序列化會破壞對象唯一性嗎?

答案是會的:

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 {
  //最簡單的寫法就是這個了,直接public就行
  public static final Singleton instance = new Singleton();

  private Singleton() {
  }

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

 }
}

代碼比較簡單,大家可以測試一下,s1和s3就是2個不同的對象,但是如果把注釋掉的readResolve方法放開的話,你就會發現
這個問題解決了,序列化和反序列化是同一個對象了。

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

尤其是對于很多金融安全類的sdk來說,如果你這個里面有單例的話,涉及到安全性要盡可能的不被業務方hook,
其中尤其要注意的就是 有人可能會利用反射來new一個對象,破壞單例

解決這個問題也不難,

 private Singleton() {
   //防止有人利用反射惡意修改
   if (null != instance) {
    throw new RuntimeException("dont construct more!");
   }

  }

項目中的單例太多,如何有效管理?

其實就拿map管理就可以了,android里面的 wms,ams 等等系統單例服務都是這樣的。你傳一個key進去 返回一個單例給你。
這個真的很有用哦,特別是大型工程,可以有效管理單例,文檔輸出就簡單許多。

 static class SingletonManager {
  private static Map<String, Object> 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);
  }

 }

android中使用單例還要注意些什么?

最主要的就是盡量不要利用單例模式存儲傳遞數據,因為app掛在后臺的時候進程會容易被殺掉,如果回到前臺再取這個單例里的數據很容易就取到個null,所以android中寫單例的原則就是:

原則上不允許用單例模式傳遞數據,如果一定要這么做,請考慮數據恢復現場。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

洛南县| 葵青区| 南开区| 长武县| 汾阳市| 武清区| 全州县| 延吉市| 柘荣县| 札达县| 黑龙江省| 莎车县| 抚州市| 西青区| 扎兰屯市| 韶山市| 尉犁县| 孟村| 寿阳县| 平原县| 长垣县| 鄄城县| 蛟河市| 富锦市| 偏关县| 洪洞县| 武山县| 库车县| 城固县| 芜湖市| 容城县| 哈密市| 长沙市| 辽阳市| 临夏市| 南平市| 和林格尔县| 株洲县| 神木县| 阿拉善右旗| 香格里拉县|