设计模式-单例模式,介绍设计模式的几种实现方式及优缺点。
简介
确保一个类只有一个实例对象,并提供该实例给全局访问。
代码实现
懒汉式线程不安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class LazyUnsafeSingleton extends Singleton{
private static Singleton instance;
private LazyUnsafeSingleton() { }
public static Singleton getInstance() {
if (instance == null) { instance = new Singleton(); } return instance; } }
|
优点
静态变量instance被延迟实例化,没有用到该类,那么就不会被实例化,从而节约资源。
缺点
在多线程的环境下是不安全的,如果多个线程同时进入,会实例化多个对象。
饿汉式线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
public class HungrySafeSingleton extends Singleton{
private static Singleton instance = new Singleton();
private HungrySafeSingleton() { }
}
|
优点
解决了线程安全问题,多线程访问也会获得同一个实例对象
缺点
直接实例化丢失了延迟实例化带来的节约资源的好处。
懒汉式线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class LazySafeSingleton extends Singleton{
private static Singleton instance;
private LazySafeSingleton() { }
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
|
优点
对getInstance()方法加锁,那么同一时间就只能有一个线程能够进入该方法,从而避免了多次实例化。
缺点
多线程情况下,当一个线程进入该方法后,其他试图进入该方法的线程都必须等待,即使instance已经被实例化了。这会让线程阻塞时间过长出现性能问题。不推荐使用。
双重校验锁线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class DoubleCheckSafeSingleton extends Singleton{
private volatile static Singleton instance;
private DoubleCheckSafeSingleton() { }
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
|
优点
如果只有一个if,在instance==null的情况下,如果两个线程同时执行了if语句,那么两个线程都会进入if的语句块中,虽然if块中有加锁操作,但是两个线程都会执行instance = new Singleton();只是先后执行的问题,那么就会实例化俩次。使用双重校验锁就解决了这种问题。
同时volatile 也是必要的,instance = new Singleton();这段代码其实是分三步执行的:
1.为instance分配内存空间
2.初始化instance
3.将instance指向分配的内存地址
由于JVM具有指令重排的特性,在多线程环境下执行顺序可能会变成1 3 2,使用volatile会禁止指令重排,保证了线程安全
缺点
保证线程安全的同时,不可避免的影响性能。
静态内部类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class StaticInnerClassSingleton extends Singleton{
private StaticInnerClassSingleton() { }
private static class SingletonHolder { private static final Singleton instance = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.instance; } }
|
优点
当Singleton类被加载时,静态内部类SingletonHolder没有被加载到内存,只有当调用getUniqueInstance方法时才会触发静态内部类的初始化,JVM确保只被实例化了一次。这种方式不仅具有延迟初始化的好处,并且由JVM提供了对线程的支持
枚举实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
|
public enum EnumSingleton {
INSTANCE;
private String objName;
public String getObjName() { return objName; }
public void setObjName(String objName) { this.objName = objName; }
public static void main(String[] args) {
EnumSingleton firstSingleton = EnumSingleton.INSTANCE; firstSingleton.setObjName("firstName"); System.out.println(firstSingleton.getObjName()); EnumSingleton secondSingleton = EnumSingleton.INSTANCE; secondSingleton.setObjName("secondName"); System.out.println(firstSingleton.getObjName()); System.out.println(secondSingleton.getObjName());
try { EnumSingleton[] enumConstants = EnumSingleton.class.getEnumConstants(); for (EnumSingleton enumConstant : enumConstants) { System.out.println(enumConstant.getObjName()); } } catch (Exception e) { e.printStackTrace(); } } }
|
firstName
secondName
secondName
secondName
优点
这种实现可以防止反射攻击,在其他实现中,通过setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。
该实现在多次序列化和序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。
经验之谈
一般情况下,不建议使用第 1 种和第 3种懒汉方式,建议使用第 2 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种静态内部类的方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。