设计模式之单例模式
1、定义:保证每个类仅有一个实例,并给外部提供一个访问它的全局访问点。
(1)设计思路:如果一个类能够被创建多个实例,那么这个类的构造方法肯定是公开的。若把创建实例对象的权限收回来,让类自身负责创建实例,然后由类本身来提供外部访问这个类的实例的方法,就实现了单例模式。
(2)实现单例模式的方法,懒汉式和饿汉式。区别在于具体创建对象实例的处理上
(3)懒汉式
1 1 /** 2 2 * 懒汉式 3 3 */ 4 4 public class Singleton { 5 5 6 6 //存储创建好的实例对象 7 7 private static Singleton uniqueInstance = null; 8 8 9 9 //私有化构造方法 10 10 private Singleton(){} 11 11 12 12 //为外部提供类实例 13 13 public static synchronized Singleton getInstance(){ 14 14 //判断变量是否有值 15 15 if (uniqueInstance == null){ 16 16 //没有值,就创建对象并赋值 17 17 uniqueInstance = new Singleton(); 18 18 } 19 19 //有值就直接使用 20 20 return uniqueInstance; 21 21 } 22 22 }
1)懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间,由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式实现呢?
2)双重检查加锁机制:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
3)“双重检查加锁”机制的实现主要在于关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
4)代码如下:
1 public class Singleton { 2 private volatile static Singleton instance = null; 3 private Singleton(){} 4 public static Singleton getInstance(){ 5 //先检查实例是否存在,如果不存在才进入下面的同步块 6 if(instance == null){ 7 //同步块,线程安全的创建实例 8 synchronized (Singleton.class) { 9 //再次检查实例是否存在,如果不存在才真正的创建实例 10 if(instance == null){ 11 instance = new Singleton(); 12 } 13 } 14 } 15 return instance; 16 } 17 }
(4)饿汉式
1 /** 2 * 饿汉式 3 */ 4 public class Singleton { 5 6 private static Singleton uniqueInstance = new Singleton(); 7 8 //私有化构造方法 9 private Singleton(){} 10 11 //定义一个方法来为客户端提供类实例 12 public static Singleton getInstance(){ 13 return uniqueInstance; 14 } 15 }
2、单例模式详解:
保证类在运行期间只会创建一个实例,并提供了一个全局唯一的访问这个类的访问点,就是代码中的getInstance()方法。不管懒汉式还是饿汉式,这个访问点是一样的。对单例模式本身而言,它只关心类实例的创建问题,并不关心具体的业务问题。
目前Java里面实现的单例是一个ClassLoader及其子ClassLoader的范围,因为一个ClassLoader在装载饿汉式单例的时候,就会创建一个类的实例。如果一个虚拟机里面有多个ClassLoader,而这些ClassLoader都装载某个类的话,就算这个类是单例类,也会产生很多个实例。如果一个机器上有多个虚拟机,那么每个虚拟机里都应该至少有一个这个类的实例,整个机器上有多个实例,就不再是单例了。
注意:不加同步的懒汉式是线程不安全,饿汉式是线程安全的,因为虚拟机保证了只会装载一次,在装载类的时候是不会发生并发的。