【设计模式】单例模式(Singleton)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27124771/article/details/84641995

思想

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

类图

Singleton 类称为单例类,该类的构造函数是 Private 的,这是为了禁止从 Singleton 类的外部调用构造函数,这就堵死了外界利用 new 创建此类的可能。通过 getInstance 方法获得本类实例的唯一全局访问点。


实现

1、饿汉式 - 线程安全 [可用]

当类被加载时,静态变量 instance 会被初始化,此时类的私有函数会被调用,单例类的唯一实例将被创建。

class Singleton {   
    private static Singleton instance = new Singleton();   
    private Singleton() { }   

    public static Singleton getInstance() {  
        return instance;   
    }     
}

优点: 写法比较简单,在类装载的时候就完成实例化,避免了线程同步问题。

缺点: 在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

2、懒汉式 - 线程不安全 [不可用]

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

3、懒汉式 - 线程安全 同步方法 [不推荐]

虽然解决了线程不安全问题,但是效率很低,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

4、懒汉式 - 线程安全 同步代码块 [不可用]

由于上一种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块,但是这种同步并不能起到线程同步的作用。假如一个线程进入了 if (singleton == null) 判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。

public class Singleton {

    private static Singleton singleton;

    private Singleton() {}

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                singleton = new Singleton();
            }
        }
        return singleton;
    }
}

5、双重校验锁(DCL) - 线程安全 [可用]

单例在第一次调用 getInstance 方法时实例化,在类加载时不自行实例化,即需要的时候再加载实例,为了处理多个线程同时访问的问题,加入双重锁机制。

class LazySingleton {   
    private volatile static LazySingleton instance = null;   
    private LazySingleton() { }   

    public static LazySingleton getInstance() {   
        //第一重判断  
        if (instance == null) {  
            //锁定代码块  
            synchronized (LazySingleton.class) {  
                //第二重判断  
                if (instance == null) {  
                    instance = new LazySingleton(); //创建单例实例  
                }  
            }  
        }  
        return instance;   
    }  
}

6、静态内部类 [推荐]

饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。

IoDH(Initialization Demand Holder) 技术能够克服单例的上述缺点,在单例类中增加一个静态内部类,在该内部类中创建单例对象,再将该单例对象通过 getInstance 方法返回给外部使用。

避免了线程不安全,延迟加载,效率高。

class Singleton {  
    private Singleton() { }  

    private static class HolderClass {  
        private final static Singleton instance = new Singleton();  
    }  

    public static Singleton getInstance() {  
        return HolderClass.instance;  
    }  
}

7、枚举 [推荐]

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化,防止反序列化重新创建新的对象。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() { }  
}

分析

1、优点

系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

2、缺点

当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候。

3、适用环境

  • 需要频繁的进行创建和销毁的对象
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象

4、经验

一般情况下,不建议使用第 2、3、4 种懒汉方式,建议使用第 1 种饿汉方式。只有在要明确实现 Lazy Loading 效果时,才会使用第 6 种静态内部类。如果涉及到反序列化创建对象时,可以尝试使用第 7 种枚举方式。如果有其他特殊的需求,可以考虑使用第 5 种双重校验锁。

5、应用

  • java.lang.Runtime#getRuntime()
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}
  • java.awt.Toolkit#getDefaultToolkit()

不需要事先创建好,只要在第一次真正用到的时候再创建就可以了,所以使用懒汉式[同步方法]。

  • java.awt.Desktop#getDesktop()

参考

猜你喜欢

转载自blog.csdn.net/qq_27124771/article/details/84641995