確保某個類只有一個實例,並且自行實例化並向整個系統提供這個實例java
聲明靜態時已經初始化,在獲取對象以前就初始化android
public class EagerSingleton {
//餓漢單例模式
//在類加載時就完成了初始化,因此類加載較慢,但獲取對象的速度快
private static EagerSingleton instance = new EagerSingleton();//靜態私有成員,已初始化
private EagerSingleton() {
//私有構造函數
}
public static EagerSingleton getInstance() //靜態,不用同步(類加載時已初始化,不會有多線程的問題) {
return instance;
}
}
複製代碼
synchronized同步鎖: 多線程下保證單例對象惟一性shell
//懶漢式單例模式
//比較懶,在類加載時,不建立實例,所以類加載速度快,但運行時獲取對象的速度慢
private static LazySingleton intance = null;//靜態私用成員,沒有初始化
private LazySingleton() {
//私有構造函數
}
public static synchronized LazySingleton getInstance() //靜態,同步,公開訪問點 {
if(intance == null)
{
intance = new LazySingleton();
}
return intance;
}
}
複製代碼
(雙重鎖定體如今兩次判空)數據庫
代碼示例:設計模式
public class SingletonKerriganD {
/** * 單例對象實例 */
private volatile static SingletonKerriganD instance = null;//這裏加volatitle是爲了不DCL失效
//DCL對instance進行了兩次null判斷
//第一層判斷主要是爲了不沒必要要的同步
//第二層的判斷則是爲了在null的狀況下建立實例。
public static SingletonKerriganD getInstance() {
if (instance == null) {
synchronized (SingletonKerriganD.class) {
if (instance == null) {
instance = new SingletonKerriganD();
}
}
return instance;
}
private SingletonKerriganD() {
//私有構造函數
}
}
複製代碼
假如線程A執行到instance = new SingletonKerriganD(),大體作了以下三件事:安全
若是執行順序是1-3-2,那多線程下,A線程先執行3,2還沒執行的時候,此時instance!=null,這時候,B線程直接取走instance ,使用會出錯,難以追蹤。JDK1.5及以後的volatile 解決了DCL失效問題(雙重鎖定失效)bash
在調用 SingletonHolder.instance 的時候,纔會對單例進行初始化,多線程
(綜合來看,私覺得這種方式是最好的單例模式)併發
public class SingletonInner {
private static class SingletonHolder{
private final static SingletonInner instance=new SingletonInner();
}
public static SingletonInner getInstance(){
return SingletonHolder.instance;
}
private SingletonInner() {
//私有構造函數
}
}
複製代碼
當getInstance方法第一次被調用的時候,它第一次讀取SingletonHolder.instance,內部類SingletonHolder類獲得初始化;而這個類在裝載並被初始化的時候,會初始化它的靜態域,從而建立Singleton的實例,因爲是靜態的域,所以只會在虛擬機裝載類的時候初始化一次,並由虛擬機來保證它的線程安全性。 這個模式的優點在於,getInstance方法並無被同步,而且只是執行一個域的訪問,所以延遲初始化並無增長任何訪問成本。jvm
答案是:不能。網上不少介紹到靜態內部類的單例模式的優勢會提到「經過反射,是不能從外部類獲取內部類的屬性的。 因此這種形式,很好的避免了反射入侵」,這是錯誤的,反射是能夠獲取內部類的屬性(想了解更多反射的知識請看 java反射全解),入侵單例模式根本不在話下,直接看下面的例子:
單例類以下:
package eft.reflex;
public class Singleton {
private int a;
private Singleton(){
a=123;
}
private static class SingletonHolder{
private final static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
public int getTest(){
return a;
}
}
複製代碼
入侵與測試代碼以下:
public static void main(String[] args) throws Exception {
//經過反射獲取內部類SingletonHolder的instance實例fInstance
Class cInner=Class.forName("eft.reflex.Singleton$SingletonHolder");
Field fInstance=cInner.getDeclaredField("instance");
//將此域的final修飾符去掉
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(fInstance, fInstance.getModifiers() & ~Modifier.FINAL);
//打印單例的某個屬性,接下來要經過反射去篡改這個值
System.out.println("a="+ Singleton.getInstance().getTest());
//獲取該單例的a屬性fieldA
fInstance.setAccessible(true);
Field fieldA=Singleton.class.getDeclaredField("a");
//經過反射類構造器建立新的實例newSingleton(這裏由於無參構造函數是私有的,不能經過Class.newInstance建立實例)
Constructor constructor=Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newSingleton= (Singleton) constructor.newInstance();
//讓fInstance指向新的實例newSingleton,此時咱們的單例已經被偷樑換柱了!
fInstance.set(null,newSingleton);
//爲盜版的單例的屬性a設置新的值
fieldA.setAccessible(true);
fieldA.set(newSingleton,888);
//測試是否成功入侵
System.out.println("被反射入侵後:a="+ Singleton.getInstance().getTest());
fieldA.set(newSingleton,777);
System.out.println("被反射入侵後:a="+ Singleton.getInstance().getTest());
}
複製代碼
輸出結果:
a=123
被反射入侵後:a=888
被反射入侵後:a=777
複製代碼
注意: 上述四種方法要杜絕在被反序列化時從新聲明對象,須要加入以下方法:
private Object readResolve() throws ObjectStreamException{
return sInstance;
}
複製代碼
爲何呢?由於當JVM從內存中反序列化地"組裝"一個新對象時,自動調用 readResolve方法來返回咱們指定好的對象
public enum SingletonEnum {
instance;
public void doThing(){
}
}
複製代碼
只要 SingletonEnum.INSTANCE 便可得到所要實例。
首先,在枚舉中咱們明確了構造方法限制爲私有,在咱們訪問枚舉實例時會執行構造方法,同時每一個枚舉實例都是static final類型的,也就代表只能被實例化一次。在調用構造方法時,咱們的單例被實例化。 也就是說,由於enum中的實例被保證只會被實例化一次,因此咱們的INSTANCE也被保證明例化一次。
上面示例中生成的字節碼文件對instance的描述以下:
...
public static final eft.reflex.SingletonEnum instance;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
...
複製代碼
能夠看出,會自動生成 ACC_STATIC, ACC_FINAL這兩個修飾符
咱們定義的一個枚舉,在第一次被真正用到的時候,會被虛擬機加載並初始化,而這個初始化過程是線程安全的。而咱們知道,解決單例的併發問題,主要解決的就是初始化過程當中的線程安全問題。因此,因爲枚舉的以上特性,枚舉實現的單例是天生線程安全的。
這裏咱們從字節碼的角度分析,並對比靜態內部類的方式來講明 首先看下靜態內部類單例生成的字節碼:
Classfile /G:/demo/reflexDemo/out/production/reflexDemo/eft/reflex/SingletonInner.class
Last modified 2019-8-8; size 500 bytes
MD5 checksum c69eb5edd5eec02d87359065d8650f02
Compiled from "SingletonInner.java"
public class eft.reflex.SingletonInner
SourceFile: "SingletonInner.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#19 // java/lang/Object."<init>":()V
#2 = Methodref #5.#20 // eft/reflex/SingletonInner$SingletonHolder.access$000:()Left/reflex/SingletonInner;
#3 = Class #21 // eft/reflex/SingletonInner
#4 = Class #22 // java/lang/Object
#5 = Class #23 // eft/reflex/SingletonInner$SingletonHolder
#6 = Utf8 SingletonHolder
#7 = Utf8 InnerClasses
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Left/reflex/SingletonInner;
#15 = Utf8 getInstance
#16 = Utf8 ()Left/reflex/SingletonInner;
#17 = Utf8 SourceFile
#18 = Utf8 SingletonInner.java
#19 = NameAndType #8:#9 // "<init>":()V
#20 = NameAndType #24:#16 // access$000:()Left/reflex/SingletonInner;
#21 = Utf8 eft/reflex/SingletonInner
#22 = Utf8 java/lang/Object
#23 = Utf8 eft/reflex/SingletonInner$SingletonHolder
#24 = Utf8 access$000
{
public eft.reflex.SingletonInner();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
line 4: 4
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Left/reflex/SingletonInner;
public static eft.reflex.SingletonInner getInstance();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: invokestatic #2 // Method eft/reflex/SingletonInner$SingletonHolder.access$000:()Left/reflex/SingletonInner;
3: areturn
LineNumberTable:
line 9: 0
}
複製代碼
再看枚舉單例生成的字節碼:
Classfile /G:/demo/reflexDemo/out/production/reflexDemo/eft/reflex/SingletonEnum.class
Last modified 2019-8-9; size 989 bytes
MD5 checksum b97cfb98be4e5ce15fd85e934cc9a75c
Compiled from "SingletonEnum.java"
public final class eft.reflex.SingletonEnum extends java.lang.Enum<eft.reflex.SingletonEnum>
Signature: #31 // Ljava/lang/Enum<Left/reflex/SingletonEnum;>;
SourceFile: "SingletonEnum.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#34 // eft/reflex/SingletonEnum.$VALUES:[Left/reflex/SingletonEnum;
#2 = Methodref #35.#36 // "[Left/reflex/SingletonEnum;".clone:()Ljava/lang/Object;
#3 = Class #14 // "[Left/reflex/SingletonEnum;"
#4 = Class #37 // eft/reflex/SingletonEnum
#5 = Methodref #10.#38 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #10.#39 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = String #11 // instance
#8 = Methodref #4.#40 // eft/reflex/SingletonEnum."<init>":(Ljava/lang/String;I)V
#9 = Fieldref #4.#41 // eft/reflex/SingletonEnum.instance:Left/reflex/SingletonEnum;
#10 = Class #42 // java/lang/Enum
#11 = Utf8 instance
#12 = Utf8 Left/reflex/SingletonEnum;
#13 = Utf8 $VALUES
#14 = Utf8 [Left/reflex/SingletonEnum;
#15 = Utf8 values
#16 = Utf8 ()[Left/reflex/SingletonEnum;
#17 = Utf8 Code
#18 = Utf8 LineNumberTable
#19 = Utf8 valueOf
#20 = Utf8 (Ljava/lang/String;)Left/reflex/SingletonEnum;
#21 = Utf8 LocalVariableTable
#22 = Utf8 name
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 <init>
#25 = Utf8 (Ljava/lang/String;I)V
#26 = Utf8 this
#27 = Utf8 Signature
#28 = Utf8 ()V
#29 = Utf8 doThing
#30 = Utf8 <clinit>
#31 = Utf8 Ljava/lang/Enum<Left/reflex/SingletonEnum;>;
#32 = Utf8 SourceFile
#33 = Utf8 SingletonEnum.java
#34 = NameAndType #13:#14 // $VALUES:[Left/reflex/SingletonEnum;
#35 = Class #14 // "[Left/reflex/SingletonEnum;"
#36 = NameAndType #43:#44 // clone:()Ljava/lang/Object;
#37 = Utf8 eft/reflex/SingletonEnum
#38 = NameAndType #19:#45 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#39 = NameAndType #24:#25 // "<init>":(Ljava/lang/String;I)V
#40 = NameAndType #24:#25 // "<init>":(Ljava/lang/String;I)V
#41 = NameAndType #11:#12 // instance:Left/reflex/SingletonEnum;
#42 = Utf8 java/lang/Enum
#43 = Utf8 clone
#44 = Utf8 ()Ljava/lang/Object;
#45 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
public static final eft.reflex.SingletonEnum instance;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
public static eft.reflex.SingletonEnum[] values();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Left/reflex/SingletonEnum;
3: invokevirtual #2 // Method "[Left/reflex/SingletonEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Left/reflex/SingletonEnum;"
9: areturn
LineNumberTable:
line 3: 0
public static eft.reflex.SingletonEnum valueOf(java.lang.String);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc_w #4 // class eft/reflex/SingletonEnum
3: aload_0
4: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
7: checkcast #4 // class eft/reflex/SingletonEnum
10: areturn
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 name Ljava/lang/String;
public void doThing();
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Left/reflex/SingletonEnum;
static {};
flags: ACC_STATIC
Code:
stack=4, locals=0, args_size=0
0: new #4 // class eft/reflex/SingletonEnum
3: dup
4: ldc #7 // String instance
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field instance:Left/reflex/SingletonEnum;
13: iconst_1
14: anewarray #4 // class eft/reflex/SingletonEnum
17: dup
18: iconst_0
19: getstatic #9 // Field instance:Left/reflex/SingletonEnum;
22: aastore
23: putstatic #1 // Field $VALUES:[Left/reflex/SingletonEnum;
26: return
LineNumberTable:
line 4: 0
line 3: 13
}
複製代碼
靜態對比: 能夠看出枚舉類默認繼承java.lang.Enum 對比兩個字節碼的常量池(Constant pool)個數,SingletonInner.class 24個,SingletonEnum.class 45個 對比兩個字節碼文件大小,SingletonInner.class 500字節,SingletonEnum.class 989字節,差了將近兩倍,咱們知道jvm虛擬機會將class文件中的常量池載入到內存中,並保存在方法區,因此單從這點看,枚舉會更耗內存(雖然這並不表明實際運行起來就所耗內存的差異),等有了更有說服力的證據再來更新~
經過上面的字節碼,咱們能夠看出枚舉類默認繼承java.lang.Enum(而不是java.lang.Object),看下Enum類源碼:
/** * prevent default deserialization--阻止默認反序列化 */
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
複製代碼
咱們知道,之前的全部的單例模式都有一個比較大的問題,就是一旦實現了Serializable接口以後,就再也不是單例得了,由於,每次調用 readObject()方法返回的都是一個新建立出來的對象,有一種解決辦法就是使用readResolve()方法來避免此事發生。可是,爲了保證枚舉類型像Java規範中所說的那樣,每個枚舉類型極其定義的枚舉變量在JVM中都是惟一的,在枚舉類型的序列化和反序列化上,Java作了特殊的規定,原文不貼了,大概意思就是說,在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是經過java.lang.Enum的valueOf方法來根據名字查找枚舉對象。同時,編譯器是不容許任何對這種序列化機制的定製的,所以禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。
在序列化過程當中,若是被序列化的類中定義了了writeObject 和 readObject 方法,虛擬機會試圖調用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化。若是沒有這樣的方法,則默認調⽤用是 ObjectOutputStream 的 defaultWriteObject 方法以及ObjectInputStream 的 defaultReadObject 方法
在程序的初始化,將多個單例類型注入到一個統一管理的類中,使用時經過key來獲取對應類型的對象,這種方式使得咱們能夠管理多種類型的單例,而且在使用時能夠經過統一的接口進行操做。 這種方式是利用了Map的key惟一性來保證單例。
public class SingletonManager {
private static Map<String,Object> map=new HashMap<String, Object>();
private SingletonManager(){}
public static void registerService(String key,Object instance){
if (!map.containsKey(key)){
map.put(key,instance);
}
}
public static Object getService(String key){
return map.get(key);
}
}
複製代碼
全部單例模式須要處理得問題都是:
推薦使用:DCL、靜態內部類、枚舉
單例模式應用普遍,根據實際業務需求來,這裏只引出源碼中個別場景,再也不詳解,有興趣的讀者能夠深刻查看源碼
在平時的Android開發中,咱們常常會經過Context來獲取系統服務,好比ActivityManagerService,AccountManagerService等系統服務,實際上ContextImpl也是經過SystemServiceRegistry.getSystemService來獲取具體的服務,SystemServiceRegistry是個final類型的類。這裏使用容器實現單例模式
SystemServiceRegistry 部分代碼:
final class SystemServiceRegistry {
private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES = new HashMap<Class<?>, String>();
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<String, ServiceFetcher<?>>();
private SystemServiceRegistry() { }
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
registerService(Context.ACTIVITY_SERVICE, ActivityManager.class,
new CachedServiceFetcher<ActivityManager>() {
@Override
public ActivityManager createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
.......
}
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
......
}
複製代碼
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
複製代碼