单例模式
一、代码实例
1、恶汉单例模式
1 /** 2 * 恶汉单例模式 3 * @author abc 4 * 5 */ 6 public class Singleton { 7 /** 8 * 定义一个静态变量存储创建好的实例 9 */ 10 private static Singleton singleton = new Singleton(); 11 12 /** 13 * 私有化构造函数,不让外部构造对象实例 14 */ 15 private Singleton(){} 16 17 /** 18 * 提供外部获取示例的方法 19 * @return 20 */ 21 public static Singleton getInstance() { 22 return singleton; 23 } 24 }
2、懒汉单例模式
1 /** 2 * 懒汉单例模式 3 * @author abc 4 * 5 */ 6 public class Singleton { 7 /** 8 * 定义一个静态变量用来存储单例对象 9 */ 10 private static Singleton singleton = null; 11 12 /** 13 * 私有化构造函数,不让外部构造对象实例 14 */ 15 private Singleton(){} 16 17 /** 18 * 提供外部获取示例的方法 19 * @return 20 */ 21 public static Singleton getInstance() { 22 if (singleton == null) {//判断静态变量是否有值 23 singleton = new Singleton();//没有值,则创建实例 24 } 25 return singleton;//有值,则直接返回 26 } 27 }
二、两种单例模式特点
1.恶汉单例模式
空间换时间: 不管用户使不使用,都先创建对象实例。---节约时间,浪费空间,有可能用户永远都不会使用。创建对象是比较着急,饿了嘛很着急,因此得名恶汉单例模式。
2.懒汉单例模式
时间换空间: 到用户需要时,在创建对象的示例。---节约空间,浪费时间,每次调用 getInstance() 时都需要判断,浪费时间,但是可以实现一种懒加载,只在需要使用时,才创建爱。因此得名为懒汉单例模式。
三、线程安全问题
懒汉单例模式存在线程安全问题,当没有创建Singleton实例时,有多个线程同事调用 getInstance() 这是 singleton都为空,会导致Singleton创建多次。
解决线程安全
1 /** 2 * 懒汉单例模式-线程安全 3 * @author abc 4 * 5 */ 6 public class Singleton { 7 8 /** 9 * 对保存实例的变量添加volatile的修饰 10 */ 11 private volatile static Singleton instance = null; 12 13 /** 14 * 私有化构造方法 15 */ 16 private Singleton() { 17 18 } 19 20 public static Singleton getInstance() { 21 //先检查实例是否存在,如果不存在才进入下面的同步快 22 if (instance == null) { 23 //同步快,线程安全的创建实例 24 synchronized(Singleton.class) { 25 //再次检查实例是否存在,如果不存在才真正的创建实例 26 if (instance == null) { 27 instance = new Singleton(); 28 } 29 } 30 } 31 32 return instance; 33 } 34 }
先判断实例是否存在,再使用同步代码块,可以节约时间,只用在第一次调用实例是,进行同步操作,在创建完实例后,就只需判断实例是否创建,无需考虑同步。从而加快了运行的速度。
四、在java中一种更好的单例实现方式
常见的两种单例模式实现方都存在小小的缺陷,那么有没有一种方案,既能实现延迟加载,又能够实现线程安全呢?
Lazy initialization holder class模式,这个模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同事实现了延迟加载和线程安全。
1.相应的基础知识
先简单地看看类级内部类相关的知识
a.什么是类级内部来?
简单点说,类级内部类指的是,有static修饰的成员内部类。如果没有static修饰的成员内部类被称为对象级内部类。
b.类级内部类相当于其外部类的static成分,他的对象,与外部类对象间不存在依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
c.类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
d.类级内部类相当于其外部类的成员,只有在第一次被使用时才会被装载。
再来看看多线程缺省同步锁的知识
大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用 synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己来进行同步控制了。这些情况包括:
a.有静态初始化器(在静态字段上或 static{} 快中的初始化器)初始化数据时
b.访问final字段时
c.在创建线程之前创建对象是
d.线程可以看见它将要处理的对象时
2.解决方案的思路
想要很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的恶汉式实现方式。但是这样一来,不是会浪费一定的空间吗?
因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
如果现在有一种方法能够让类装载的时候就初始化对象,那不就解决问题了?一种可行的方式就是采用内级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类,那就不会创建对象的实例,从而同事实现延迟加载和线程安全。
代码示例:
1 public class Singleton { 2 3 /** 4 * 类级内部类,就业是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系 5 * 而且只有被调用到时才会装载,从而实现了延迟加载 6 * @author abc 7 * 8 */ 9 private static class SingletonHolder { 10 /** 11 * 静态初始化器,由JVM来保证线程安全 12 */ 13 private static Singleton instance = new Singleton(); 14 } 15 16 /** 17 * 私有化构造方法 18 */ 19 private Singleton() {} 20 21 /** 22 * 获取方法 23 * @return 24 */ 25 private static Singleton getInstance() { 26 return SingletonHolder.instance; 27 } 28 29 }
当 getInstance 方法第一次被调用的时候,他第一次读取 SingletonHolder.instance, 大致SingletonHolder 类得到初始化,而这个类在装载并被初始化的时候,会初始化的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证他的线程安全性。
这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。