单例模式:即是对于某对象的类只能允许一个实例(该对象)存在,这个对象即是单例。
有时对于整个系统只需要一个全局对象,这样有利于协调系统的整体行为,例如,某服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过单例对象来获取这些配置信息。这样就简化了系统的配置管理。
单例对象之所以能作为一个全局对象存在于系统中,就是利用了jvm垃圾回收机制不会回收单例对象。(只要有引用指向该对象,这就是一个或者的对象,??网上还有一种说法:static 属性不会被回收。不管怎样只要允许单例的存在就不影响我们使用它)
单例模式要求类能够有返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称)。
实现步骤:
1.该类的构造方法私有化。这样其他地方无法通过该类的构造器来实例化一个对象,故而只能通过该类提供的静态方法来获得其对象;
2.该类提供一个静态方法,当调用该方法时,如果类持有的引用不为空,就将其返回,否则,创建一个实例并保留其引用,然后返回。
注意:
单例模式在多线程的应用场合下必须小心使用。要避免两个以上的线程同时操作以免违反单例的遵旨。
一、饿汉式
优点:实现简单,装载时就完成实例化,避免了线程同步问题。
缺点:没有Lazy—loading效果,若是系统用不到则会有资源浪费。
/** 静态常量 */ public class Singleton { private final static Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return INSTANCE; } } /** 静态代码块 */ public class Singleton{ private static Singleton instance; static{ instance = new Singleton(); } private Singleton(){} public static Singleton getInstance(){ return instance; } }
二、懒汉式
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下, 一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。 所以在多线程环境下不可使用这种方式。
public class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
再看这种写法:
public class Singleton{ private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } }
这里用了关键字 synchronized 做了个线程同步,缺点: 效率太低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。 若是引用有对象直接return。
public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
这里为了提高效率将线程锁直接加在了实例化代码,但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这时便会产生多个实例。
public class Singleton { private static volatile Singleton singleton; private Singleton() {} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查, 这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。这里可以这样说 外层 if 语句用来提高效率,里层 if 语句用来确保单例。
说完了饿汉懒汉,还有:
静态内部类
public class Singleton{ private Singleton(){} private static class SingletonInstance{ private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.INSTANCE; } }
这种方式跟饿汉式类似,但又有不同:两者都利用类加载机制来确保实例化时只有一个线程,不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
枚举
public enum Singleton{ INSTANCE; public void whateverMethod() { } }
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。不过实际项目中用的很少。