关于单例模式:
单例,即单一实例。因为在一些情况下,某些类的对象,我们只需要一个就可以了,所以我们要用到单例模式。
单例模式的目的是使得一个类中的一个静态对象成为系统中的唯一实例,提供一个访问该实例的方法。并阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
单例模式特点:其在整个应用程序的生命周期中只存在一个实例。
单例模式分为懒汉式和饿汉式两种
这里先贴出代码,然后再做解释,便于理解。
懒汉式单例模式核心代码:
1 package com.wxb.singleton; 2 3 public class Singleton_Lazy { 4 /* --创建私有静态的自身实例对象-- */ 5 private static Singleton_Lazy uniqueInstance = null; 6 7 /* --私有的构造方法,避免外界利用构造方法创建多个实例-- */ 8 private Singleton_Lazy() { 9 10 } 11 12 /* --获取静态实例的方法-- 13 * --该方法是静态的,可以通过类名直接调用-- 14 */ 15 public static Singleton_Lazy getUniqueInstance() { 16 if (null == uniqueInstance) { 17 uniqueInstance = new Singleton_Lazy(); 18 } 19 return uniqueInstance; 20 } 21 }
懒汉式单例模式特点:懒汉模式的单例类的唯一实例对象是在第一次使用 getUniqueInstance()时实例化的。如果我们不调用 getUniqueInstance()的话,这个实例是不会存在的,即为 初始值null。可以看出如果不去动它的话,它自己是不会实例化的,所以形象的称之为懒汉模式。
饿汉式单例模式核心代码:
1 package com.wxb.singleton; 2 3 public class Singleton_Hungry { 4 /* 5 * --在初始化时,就实例化了静态的自身实例对象-- --饿汉式的缺点也在于此:浪费内存-- 6 */ 7 private static Singleton_Hungry uniqueInstance = new Singleton_Hungry(); 8 9 /* --私有的构造方法,避免外界利用构造方法创建多个实例-- */ 10 private Singleton_Hungry() { 11 12 } 13 14 /* --获取静态实例的方法-- 15 * --该方法是静态的,可以通过类名直接调用-- 16 */ 17 public static Singleton_Hungry getUniqueInstance() { 18 return uniqueInstance; 19 } 20 }
饿汉单例模式特点:之所以称之为饿汉,是因为肚子饿了,人也会变得主动了,所以饿汉模式下的单例类其自己就会主动实例化该单例类的唯一实例对象。
一般懒汉式单例模式较为常用。
对于唯一实例的验证:
1 package com.wxb.singleton; 2 3 public class SingletonTest { 4 /* --单例模式唯一实例的测试|调用懒汉式单例验证-- */ 5 public static void main(String[] args) { 6 Singleton_Lazy singleton_Lazy1 = Singleton_Lazy.getUniqueInstance(); 7 Singleton_Lazy singleton_Lazy2 = Singleton_Lazy.getUniqueInstance(); 8 if (singleton_Lazy1.equals(singleton_Lazy2)) { 9 System.out.println("Singleton_Lazy1 and singleton_Lazy2 are the same instance"); 10 } 11 else { 12 System.out.println("Singleton_Lazy1 and singleton_Lazy2 are different instance"); 13 } 14 } 15 }
输出结果:
可以看出虽然两次调用getUniqueInstance(),但是访问的是同一个实例。
对于懒汉式单例模式,还存在着多线程安全问题
在多线程中,如果在一开始调用 GetUniqueInstance()时,是由两个线程同时调用的,这样的话,两个线程均会进入GetUniqueInstance(),而后由于是第一次调用 GetUniqueInstance(),所以静态变量 uniqueInstance为 null ,这样的话,就会让两个线程均通过 if 语句的条件判断,然后调用 new GetUniqueInstance()了。
所以问题就出来了,因为有两个线程,所以会创建两个实例,这便违反了单例模式的初衷,使得单例模式在多线程中出现不安全的问题。
这个问题我们可以通过线程锁机制对单例模式做相应的优化,即先将一个线程锁定,然后等这个线程完成以后,再让其他的线程访问GetUniqueInstance()中的 if 段语句。
双重校验锁形式单例模式核心代码:
1 package com.wxb.singleton; 2 3 public class Singleton_Lazy_Safe { 4 /* --创建私有静态的自身实例对象-- */ 5 private static Singleton_Lazy_Safe uniqueInstance = null; 6 7 /* --私有的构造方法,避免外界利用构造方法创建多个实例-- */ 8 private Singleton_Lazy_Safe() { 9 10 } 11 12 /* --获取静态实例的方法-- 13 * --该方法是静态的,可以通过类名直接调用-- 14 */ 15 public static Singleton_Lazy_Safe getUniqueInstance() { 16 /* --双重校验锁-- */ 17 if (null == uniqueInstance) { 18 synchronized (Singleton_Lazy_Safe.class) { 19 if (null == uniqueInstance) { 20 uniqueInstance = new Singleton_Lazy_Safe(); 21 } 22 } 23 } 24 return uniqueInstance; 25 } 26 }
关于使用双重校验的原因:
如果有两个线程同时到达,即同时调用了GetUniqueInstance(),此时由于 uniqueInstance == null ,所以两个线程都可以通过第一重的 singleton == null 校验,
进入第一重 if 语句后,由于存在锁机制,所以会有一个线程进入 lock 语句并进入第二重 uniqueInstance == null ,而另外的一个线程则会在 synchronized 语句的外面等待。
当第一个线程执行完 new Singleton_Lazy_Safe()语句后,便会退出锁定区域,此时,第二个线程便可以进入synchronized语句块,此时,如果没有第二重 uniqueInstance == null 的话,那么第二个线程还会继续调用 new Singleton_Lazy_Safe()语句,这样第二个线程又会创建一个 Singleton 实例,违反了“唯一实例”。
由于饿汉式单例类类被加载的时候,就会自行初始化uniqueInstance这个静态的自身实例对象。而不是在第一次调用GetUniqueInstance()时再来实例化单例类的唯一实例,所以饿汉式单例不需要编写多线程安全代码。