欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android中單例模式的一些坑小結(jié)

 更新時(shí)間:2019年02月24日 15:00:28   作者:DK_BurNIng  
這篇文章主要給大家介紹了關(guān)于Android中單例模式的一些坑,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

前言

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

而我對單例的理解是,在可控的范圍內(nèi)充當(dāng)全局變量的作用,就相當(dāng)于C語言中一個(gè)全局結(jié)構(gòu)體。

首先來看這樣一個(gè)單例,稍微有點(diǎn)經(jīng)驗(yàn)的同學(xué)可能都會說,這樣的單例是非線程安全的。要加個(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;
  }
 }

但是你要是問他,為什么是非線程安全的單例就答不出來了。搞清楚這個(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è)值的。

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

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

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

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

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

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

但是,虛擬機(jī)會有一個(gè)指令重排序的概念。當(dāng)虛擬機(jī)發(fā)現(xiàn)單線程下 指令的順序變更不會導(dǎo)致結(jié)果異常的時(shí)候
就會觸發(fā)指令重排序的機(jī)制, 他會導(dǎo)致上述的 123順序發(fā)生變更,比如我們把順序改成132 你就會發(fā)現(xiàn) 結(jié)果還是一樣的。

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

如果發(fā)生132的執(zhí)行順序 會發(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;
  }
 }

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

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

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

也就是說 132的執(zhí)行順序依舊會發(fā)生,只不過 當(dāng)執(zhí)行完13 而2沒有執(zhí)行的時(shí)候,volatile修飾過的這個(gè)變量,所有對他的讀操作
都會暫時(shí)屏蔽,等待2操作執(zhí)行完以后,才會進(jìn)行讀操作。

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

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

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

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

缺點(diǎn)就是他會一直占用內(nèi)存。餓漢寫法很多,我寫個(gè)最簡單的:

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

  private Singleton() {
  }

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

 }
}

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

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

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

解決這個(gè)問題也不難,

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

  }

項(xiàng)目中的單例太多,如何有效管理?

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

 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中使用單例還要注意些什么?

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

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

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

相關(guān)文章

  • Android 修改adb端口的方法

    Android 修改adb端口的方法

    今天小編就為大家分享一篇Android 修改adb端口的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • Android studio實(shí)現(xiàn)左右滑動切換圖片

    Android studio實(shí)現(xiàn)左右滑動切換圖片

    這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)左右滑動切換圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • Android開發(fā)之微信底部菜單欄實(shí)現(xiàn)的幾種方法匯總

    Android開發(fā)之微信底部菜單欄實(shí)現(xiàn)的幾種方法匯總

    這篇文章主要介紹了Android開發(fā)之微信底部菜單欄實(shí)現(xiàn)的幾種方法,下面小編把每種方法通過實(shí)例逐一給大家介紹,需要的朋友可以參考下
    2016-09-09
  • Flutter實(shí)現(xiàn)二維碼掃描

    Flutter實(shí)現(xiàn)二維碼掃描

    這篇文章主要為大家詳細(xì)介紹了Flutter實(shí)現(xiàn)二維碼掃描,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • 利用Android中的TextView實(shí)現(xiàn)逐字顯示動畫

    利用Android中的TextView實(shí)現(xiàn)逐字顯示動畫

    在安卓程序啟動的時(shí)候,想逐字顯示一段話,每個(gè)字都有一個(gè)從透明到不透明的漸變動畫。那如何顯示這個(gè)效果,下面一起來看看。
    2016-08-08
  • Android 分享控件的實(shí)現(xiàn)代碼

    Android 分享控件的實(shí)現(xiàn)代碼

    這篇文章主要介紹了Android 分享控件的實(shí)現(xiàn)代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03
  • Android中常用的三個(gè)Dialog彈窗總結(jié)解析

    Android中常用的三個(gè)Dialog彈窗總結(jié)解析

    自己雖然一直使用過dialog,但是一直都是復(fù)制、粘貼;不清楚dialog的具體用途,這次趁著有時(shí)間,總結(jié)一下具體用法,感興趣的朋友跟著小編來看看吧
    2021-10-10
  • Android使用RecyclerView實(shí)現(xiàn)今日頭條頻道管理功能

    Android使用RecyclerView實(shí)現(xiàn)今日頭條頻道管理功能

    這篇文章主要為大家詳細(xì)介紹了Android使用RecyclerView實(shí)現(xiàn)今日頭條頻道管理功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Android10開發(fā)者常見問題(小結(jié))

    Android10開發(fā)者常見問題(小結(jié))

    這篇文章主要介紹了Android10開發(fā)者常見問題(小結(jié)),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Android編程實(shí)現(xiàn)圖標(biāo)拖動效果的方法

    Android編程實(shí)現(xiàn)圖標(biāo)拖動效果的方法

    這篇文章主要介紹了Android編程實(shí)現(xiàn)圖標(biāo)拖動效果的方法,涉及Android事件響應(yīng)及圖標(biāo)變換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-11-11

最新評論