设计模式-单例模式

一、单例模式介绍

1. 定义: 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法。(静态方法)

2. 使用场景: 需要频繁创建销毁的对象、创建对象时耗时过多耗费资源过多(即:重量级对象),但又经常使用到的对象、工具类对象、频繁访问数据库或文件的对象(如:数据源、session工厂等。)

3. 注意事项及说明:

  • 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  • 想要实例化一个单例类时,必须要记住对应获取对象的方法,而是使用new

二、单例模式常见写法

方式一:*饿汉式(静态常量写法)

/* *
 * 饿汉式(静态常量写法)
 */
    
public class Singleton {

    /* *
     * 1. 构造器私有,外部不能new
     */
    
    private Singleton() {
    }

    /* *
     * 2. 内部创建实例
     */
    
    private static final Singleton INSTANCE = new Singleton();

    /* *
     * 3. 公有静态方法:返回实例对象
     */
    
    public static Singleton getInstance() {
        return INSTANCE;
    }

}


class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();

        System.out.println(instance == instance1);
        // true
        
        System.out.println("instance hashCode:" + instance.hashCode());
        // instance hashCode:1239731077
        
        System.out.println("instance1 hashCode:" + instance1.hashCode());
        // instance1 hashCode:1239731077
    }
}
  • 方式说明:
  • 优点: 在类装载的时候就完成实例化,写法简单,也避免了线程同步问题

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

  • 结论:单线程可用,但是可能造成内存浪费。

方式二:*饿汉式(静态代码块写法)

/* *
 * 饿汉式(静态代码块写法)
 */

public class Singleton {

    /* *
     * 1. 构造器私有,外部不能new
     */

    private Singleton() {
    }

    /* *
     * 2. 定义一个静态实例变量,在静态代码块中创建实例对象
     */

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    /* *
     * 3. 公有静态方法:返回实例对象
     */

    public static Singleton getInstance() {
        return instance;
    }

}
  • 方式说明:
  • 优点缺点: 方式类似,优缺点同上一致。

  • 结论: 单线程可用,但是可能造成内存浪费。

方式三:懒汉式(线程不安全写法)

/* *
 * 懒汉式(线程不安全写法)
 */

public class Singleton {

    /* *
     * 1. 定义一个静态实例变量
     */

    private static Singleton instance;

    /* *
     * 2. 构造器私有,外部不能new
     */

    private Singleton() {
    }


    public static Singleton getInstance() {
        // 3. 实例为空则进行创建,并返回
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }

}
  • 方式说明:
  • 优点: 起到了Lazy Loading的效果,但是只能在单线程下使用。

  • 缺点: 如果多线程下,一个线程进入到了判断语句块,未来得及创建实例。而另一个线程也进入了判断,则此时会创建多个实例。所有多线程环境下不可使用这种方式。

  • 结论: 在实际开发中,不要使用这种方式。

方式四:懒汉式(线程安全,同步写法)

/* *
 * 懒汉式(线程安全,同步写法)
 */

public class Singleton {

    /* *
     * 1. 定义一个静态实例变量
     */

    private static Singleton instance;

    /* *
     * 2. 构造器私有,外部不能new
     */

    private Singleton() {
    }


    /* *
     * 3. 增加synchronized关键字,使其同步,避免线程不安全问题
     */

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

}
  • 方式说明:
  • 优点: 解决了线程不安全问题。

  • 缺点: 效率太低,每次想要获得实例的时候,都需要进行同步。而其实只用执行一次实例化代码就够了,后面想获得该实例,直接return就行了。

  • 结论: 在实际开发中,不推荐使用这种方式。

方式五:*双重检查机制

/* *
 * 双重检查机制写法
 */

public class Singleton {

    /* *
     * 1. 定义一个用volatile修饰的静态同步实例变量
     */

    private static volatile Singleton instance;

    /* *
     * 2. 构造器私有,外部不能new
     */

    private Singleton() {
    }


    /* *
     * 3. 加入双重判断,并进行同步。解决线程不安全及效率问题
     */

    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
}
  • 逻辑示意图:

  • 方式说明:
  • 特点:

    • Double-Check概念是多线程常用的。进行两次(null == instance)检查,保证了线程安全

    • 这样实例化代码只用执行一次,后面再次访问,直接返回实例对象,避免反复进行方法同步

    • 线程安全,延迟加载,效率较高

  • 结论: 在实际开发中,推荐使用这种模式。

方式六:*静态内部类

/* *
 * 静态内部类
 */

public class Singleton {

    /* *
     * 1. 构造器私有,外部不能new
     */

    private Singleton() {
    }


    /* *
     * 2. 定义一个静态内部类,该类有静态属性Singleton
     */

    private static class SingletonInstance {

        private static final Singleton INSTANCE = new Singleton();
    }

    /* *
     * 3. 提供公有方法,调用时返回实例
     */

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
  • 方式说明:
  • 特点:

    • 采用了类装载机制保证了初始化实例只有一个线程。

    • 只有在调用getInstance()时,才会装载SingletonInstance类,从而完成new Singleton()操作。

    • 类的静态属性只会在第一次加载类的时候初始化JVM帮助我们提供了线程的安全性,在类进行初始化的时候,别的线程是无法进入的。

    • 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

  • 结论:推荐使用

方式七:*枚举

/* *
 * 使用枚举方式实现单例
 */

public class SingletonTest {

    enum Singleton {
        // 属性
        INSTANCE;

        /* *
         *  枚举中可以定义方法,并通过属性实例调用
         */

        public void sayOk() {
            System.out.println("ok");
        }
    }

    public static void main(String[] args) {
        Singleton instance = Singleton.INSTANCE;
        Singleton instance1 = Singleton.INSTANCE;

        instance.sayOk();
        instance1.sayOk();
        // ok


        System.out.println(instance == instance1);
        // true
        System.out.println(instance.hashCode());
        // 1239731077
        System.out.println(instance1.hashCode());
        // 1239731077
    }
}
  • 方式说明:
  • 特点:

    • 借助JDK1.5中添加的枚举来实现单例模式。

    • 不仅能避免多线程问题,而且还能防止反序列化重新创建新的对象

    • 这种方式是《Effective Java》作者(美国)Joshua Bloch提倡的方式。

    • 结论:推荐使用

三、该模式在JDK中的应用

java.lang包下的Runtime类 :

采用了饿汉式的方式实现单例。

四、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值