出处:http://cantellow.iteye.com/blog/838473
单例模式有一下特点:
1、单例类只能有一个实例。
2、单例类必须自己给自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头
第一种(懒汉,线程不安全):
1 import java.util.*; 2 class Singleton 3 { 4 private Singleton(){ } 5 6 public static Singleton getInstance(){ 7 if (instance == null) //1 8 instance = new Singleton(); //2 9 return instance; //3 10 } 11 }
这种写法lazy loading很明显,但是致命的是在多线程不能正常工作。当引入多线程时,就必须通过同步来保护 getInstance()
方法。如果不保护 getInstance()
方法,则可能返回Singleton
对象的两个不同的实例。
假设两个线程并发调用 getInstance()
方法并且按以下顺序执行调用:
1. 线程 1 调用 getInstance()
方法并决定 instance
在 //1 处为 null
。
线程 1 进入 if
代码块,但在执行 //2 处的代码行时被线程 2 预占。
2. 线程 2 调用 getInstance()
方法并在 //1 处决定 instance
为 null
。
线程 2 进入 if
代码块并创建一个新的 Singleton
对象并在 //2 处将变量 instance
分配给这个新对象。
线程 2 在 //3 处返回 Singleton
对象引用。
线程 2 被线程 1 预占。
3.线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton
对象。
线程 1 在 //3 处返回 Singleton
对象引用。
结果是 getInstance()
方法创建了两个 Singleton
对象,而它本该只创建一个对象。
第二种(懒汉,线程安全):
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 public static synchronized Singleton getInstance() { 5 if (instance == null) { //1 6 instance = new Singleton(); //2 7 } 8 return instance; //3 9 } 10 }
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
效率很低为什么?
只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 //2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定 instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。
为使此方法更为有效,一个被称为双重检查锁定的习语就应运而生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。同步的代价在不同的 JVM 间是不同的。在早期,代价相当高。随着更高级的 JVM 的出现,同步的代价降低了,但出入synchronized 方法或块仍然有性能损失。不考虑 JVM 技术的进步,程序员们绝不想不必要地浪费处理时间。
第三种(双重校验锁):
可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?
所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。
public class Singleton { private volatile static Singleton instance = null; private Singleton(){} public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized (Singleton.class) {//1 //再次检查实例是否存在,如果不存在才真正的创建实例 if(instance == null){//2 instance = new Singleton();//3 } } } return instance; } }
双重检查锁定背后的理论是:在 //2 处的第二次检查使(如清单 3 中那样)创建两个不同的 Singleton
对象成为不可能。假设有下列事件序列:
1.线程 1 进入 getInstance()
方法。
由于 instance
为 null
,线程 1 在 //1 处进入 synchronized
块。
线程 1 被线程 2 预占。
2. 线程 2 进入 getInstance()
方法。
由于 instance
仍旧为 null
,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
线程 2 被线程 1 预占。
3.线程 1 执行,由于在 //2 处实例仍旧为 null
,线程 1 还创建一个 Singleton
对象并将其引用赋值给 instance
。
线程 1 退出 synchronized
块并从 getInstance()
方法返回实例。
线程 1 被线程 2 预占。
4. 线程 2 获取 //1 处的锁并检查 instance
是否为 null
。
由于 instance
是非 null
的,并没有创建第二个 Singleton
对象,由线程 1 创建的对象被返回。
总结:双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。双重检查锁定失败的问题并不归咎于JVM中的实现bug,而是归咎于Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。
第四种(饿汉):
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 private Singleton (){} 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
android中很多地方都用到了单例模式如果各种manager
@Override public Object getSystemService(String name) { if (WINDOW_SERVICE.equals(name)) { return WindowManagerImpl.getDefault(); } else if (LAYOUT_INFLATER_SERVICE.equals(name)) { synchronized (mSync) { LayoutInflater inflater = mLayoutInflater; if (inflater != null) { return inflater; } mLayoutInflater = inflater = PolicyManager.makeNewLayoutInflater(getOuterContext()); return inflater; } } else if (ACTIVITY_SERVICE.equals(name)) { return getActivityManager(); } else if (INPUT_METHOD_SERVICE.equals(name)) { return InputMethodManager.getInstance(this); } else if (ALARM_SERVICE.equals(name)) { return getAlarmManager(); } else if (ACCOUNT_SERVICE.equals(name)) { return getAccountManager(); } else if (POWER_SERVICE.equals(name)) { return getPowerManager(); } else if (CONNECTIVITY_SERVICE.equals(name)) { return getConnectivityManager(); } else if (THROTTLE_SERVICE.equals(name)) { return getThrottleManager(); } else if (WIFI_SERVICE.equals(name)) { return getWifiManager(); } else if (NOTIFICATION_SERVICE.equals(name)) { return getNotificationManager(); } else if (KEYGUARD_SERVICE.equals(name)) { return new KeyguardManager(); } else if (ACCESSIBILITY_SERVICE.equals(name)) { return AccessibilityManager.getInstance(this); } else if (LOCATION_SERVICE.equals(name)) { return getLocationManager(); } else if (SEARCH_SERVICE.equals(name)) { return getSearchManager(); } else if (SENSOR_SERVICE.equals(name)) { return getSensorManager(); } else if (STORAGE_SERVICE.equals(name)) { return getStorageManager(); } else if (VIBRATOR_SERVICE.equals(name)) { return getVibrator(); } else if (STATUS_BAR_SERVICE.equals(name)) { synchronized (mSync) { if (mStatusBarManager == null) { mStatusBarManager = new StatusBarManager(getOuterContext()); } return mStatusBarManager; } } else if (AUDIO_SERVICE.equals(name)) { return getAudioManager(); } else if (TELEPHONY_SERVICE.equals(name)) { return getTelephonyManager(); } else if (CLIPBOARD_SERVICE.equals(name)) { return getClipboardManager(); } else if (WALLPAPER_SERVICE.equals(name)) { return getWallpaperManager(); } else if (DROPBOX_SERVICE.equals(name)) { return getDropBoxManager(); } else if (DEVICE_POLICY_SERVICE.equals(name)) { return getDevicePolicyManager(); } else if (UI_MODE_SERVICE.equals(name)) { return getUiModeManager(); } else if (DOWNLOAD_SERVICE.equals(name)) { return getDownloadManager(); } else if (NFC_SERVICE.equals(name)) { return getNfcManager(); } return null; }