单例模式的目的:一个类只能存在一个实例对象。
单例模式的基本原理:该类的构造函数为私有函数(不允许通过new的方式来创建对象),并且该类只提供一个取得其对象实例的静态方法(可以作为创建对象的唯一入口)。
单例模式的实现按照大类分为5种方式,分别是饿汉式、懒汉式、双重校验锁(DCL,即double-checked locking)、静态内部类(登记式)、枚举。一般推荐饿汉式,如果明确需要lazy loading,则推荐静态内部类和双重校验锁,如果涉及反序列化创建对象,则推荐枚举方式。
除了枚举式,我们的调用方法是相同的,这里的instance1和isntance2指向的是一个对象。可以通过instance1 == instance2进行判断,也可以通过打印instance1.hashCode()和instance2.hashCode(),判断两者相同。
public class SingletonTest {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
System.out.println("instance1.hashCode: " + instance1.hashCode());
System.out.println("instance2.hashCode: " + instance2.hashCode());
}
}
- 饿汉式
按照基本原理创建Singleton类。私有构造函数,唯一个静态方法来获取。
之所以称为饿汉式,是因为Singleton类自第一次使用的时候,就会初始化instance。就像是饿汉一样,一出现就创建instance(吃)。public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
优点:写法简单,就是在类装载的时候就完成了instance的实例化,能够避免线程同步问题。
缺点:在类装载的时候完成instance的实例化,没有lazy loading的效果,因为导致类装载的原因有很多种,可能不是调用getInstance()(例如其它的静态方法)引起的类装载,这个时候创建的instance就会占用内存,并且与我们使用时期望的instance并不相同。 - 懒汉式
- 看到饿汉式的缺点,就要对它进行改进。思路就是在调用getInstance()的时候,再去创建instance。为了避免每次调用,都去创建instance,就对instance进行检查,只有在instance为空时,才去创建。
这里之所以称为懒汉式,就是因为满足了lazy loading。public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { if ( instance == null ) { instance = new Singleton(); } return instance; } }
优点:起到了lazy loading的效果
缺点:线程不安全,只能在单线程下使用。因为在多线程下,一个线程进入了if ( instance == null ) 判断语句块,还未创建instance的时候,另一个线程也进入了这个判断语句块,这就会造成多个实例的创建,所以不是严格的单例模式。 - 为了保证线程安全,可以采用synchronized进行方法同步。
优点:synchronized保证了线程安全public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if ( instance == null ) { instance = new Singleton(); } return instance; } }
缺点:多线程情况下,每个线程在调用这个方法时,都要等待其它线程运行完这个方法,那么效率不高。 - 错误的待改进想法 我在最初的时候想,既然方法同步效率不高,那么就使用代码块同步,这样就能提高效率了。可是在考虑代码块同步的时候,需要这样写:
我们可以看到getInstance()方法本来就没几句,除了return语句,全在同步语句块中,效率并没有怎么提高,反而在代码的可读性上差了很多。这个时候或许又有人疑问了,为什么不能把synchronized放在if ( instance == null ) 判断语句内呢?如果把同步语句块放在 if 判断语句内,那么懒汉式方法1中的问题就是一样的,仍然是线程不安全的,所谓的同步没有作用。那有没有办法进行改进呢?有,就是双重检查式。public class Singleton { private static Singleton instance; private Singleton (){} public static Singleton getInstance() { synchronized (Singleton.class) { if ( instance == null ) { instance = new Singleton(); } } return instance; } }
- 看到饿汉式的缺点,就要对它进行改进。思路就是在调用getInstance()的时候,再去创建instance。为了避免每次调用,都去创建instance,就对instance进行检查,只有在instance为空时,才去创建。
- 双重检查式
其实懒汉式方法3的想法是不错的,只是需要改进。改进如下:
Double-Check概念是多线程中常常使用的,其中的instance实例化代码只会执行一次。当一个线程执行完instance的实例化,另外一个线程进入同步代码块,由于instance的 volatile 属性,这个时候第二次 if ( instance == null ) 判断语句为false,则会跳过instance的实例化代码。以后的线程在第一个 if ( instance == null ) 判断语句的结果就是false,则无需等待同步代码块,直接返回instance。public class Singleton { private volatile static Singleton instance; private Singleton (){} public static Singleton getInstance() { if ( instance == null ) { synchronized (Singleton.class) { if ( instance == null ) { instance = new Singleton(); } } } return instance; } }
优点:线程安全,效率高
缺点:非要说缺点的话,实现较为复杂,并且也是JDK1.5之后才能这么写。因为volatile是在JDK1.5加入的。 - 静态内部类
利用类装载机制,当外部类进行类装载时,其内部的静态类并不会被装载。同时只有在调用getInstance()时才会进行该静态内部类的装载。而类装载机制在JVM中是线程安全的,所以可以保证该方式的线程安全。public class Singleton { private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static Singleton getInstance() { return SingletonInstance.INSTANCE; } } - 枚举式
枚举类型在JDK1.5中添加的,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,同时可以防止放射调用构造器。这种方式也是Effective Java作者Josh Bloch所提倡的方式,因为前四种方式都会有反序列化重新创建新对象和使用反射强行调用私有构造器的问题。虽然目前使用这种方式的很少,但是大家可以去尝试一下。enum Singleton { INSTANCE; public void callMethod() { System.out.println(" call this method"); } } public static void main(String[] args) { Singleton instance1 = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance1==instance2); System.out.println("instance1.hashCode: " + instance1.hashCode()); System.out.println("instance2.hashCode: " + instance2.hashCode()); instance1.callMethod(); }
1592

被折叠的 条评论
为什么被折叠?



