不得不学的设计模式 - 单例模式

78 阅读7分钟

前言

设计模式其实很早就学到了,但是一直没有重视起来,直到最近,遇到了一个问题,用设计模式实现完(这篇不讲这个问题,因为用的是其他模式),才发现了设计模式的魅力,然后决定把设计模式重新刷一遍,研究研究

 介绍

单例模式在设计模式中可以说是最简单的了,就是某个类只能有一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法),这就是单例模式的核心思想了,是不是很简单(^_−)☆

单例模式的优缺点:

  • 只有一个对象,内存开支少、性能好,重复创建对象那多么浪费资源呀
  • 避免对资源的多重占用
  • 在系统设置全局访问点,优化和共享资源访问

饿汉式和懒汉式概述:

  • 饿汉式:类装载的时候就初始化
  • 懒汉式:需要使用这个实例的时候进行初始化

实现单例模式的七种方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 双重检查(双检锁)
  6. 静态内部类
  7. 枚举

一、饿汉式(静态常量)

介绍

就像是一个很久没有吃饭的人,有吃的立马就吃掉(类加载完成后,就立马创建对象)

实现步骤:

  1. 构造器私有化(防止通过 new 创建对象)
  2. 类的内部创建对象
  3. 对外公布一个静态的公共方法

示例:

package singleton;

/**
 * @author zheng
 */
public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }

    // 类加载完成立马创建对象
    private final static Singleton singleton = new Singleton();

    public static Singleton getSingleton() {
        return singleton;
    }
}

输出:

true
1239731077
1239731077

优缺点:

  • 优点:这种写法简单,在类装载的时候就完成了实例化,避免了线程同步问题
  • 缺点:在类装载的时候就完成了实例化,没有达到懒加载的效果,如果不是用这个实例就造成了内存浪费

二、饿汉式(静态代码块)

介绍:

这种方法和上面那种基本一致,只不过把类实例化的过程放到静态代码块当中

示例:

package singleton;

/**
 * @author zheng
 */
public class SingletonTest {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }

    private static Singleton singleton;

    static {
        singleton = new Singleton();
    }

    public static Singleton getSingleton() {
        return singleton;
    }
}

三、懒汉式(线程不安全)

介绍:

这是一个很懒的人,只有当你叫他干活的时候,他才会干活(使用时创建实例)

示例:

package singleton;

/**
 * @author zheng
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }

    private static Singleton singleton;

    public static Singleton getSingleton() {
        // 这个有线程安全问题
        if (singleton == null) {
            // 线程1、线程2可能同时进入到这里,创建实例,造成内存浪费
            singleton = new Singleton();
        }
        return singleton;
    }
}

优缺点:

  • 优点:起到了懒加载的作用
  • 缺点:只能在单线程的环境下使用,一个线程进入 if (singleton == null)  判断语句块,还没来的及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例

不推荐这种方式

四、懒汉式(线程不安全)

package singleton;

/**
 * @author zheng
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }

    private static Singleton singleton;

    // synchronized 关键字是方法变成同步的,解决线程安全问题
    public static synchronized Singleton getSingleton() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

优缺点:

  • 优点:解决了线程安全问题
  • 缺点:由于方法被 synchronized 关键字修饰,使方法变为同步,导致效率大幅度降低

不推荐使用这种方式

五、双重检查

package singleton;

/**
 * @author zheng
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }

    private static volatile Singleton singleton;
    
    // 提供了一个静态的公有方法,加入双重检查代码,解决线程安全问题,同时解决懒加载问题,并且保证了效率
    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

优缺点:

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

在实际开发中,推荐这种方式实现单例模式

六、静态内部类

示例:

/**
 * @author zheng
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());
    }
}

class Singleton {
    // 私有化后,无法通过new创建对象
    private Singleton() {
    }
    
    /**
     * 静态内部类在 Singleton 被装载时并不会立即实例化,而是在使用的时候才会实例化
     * 当调用 getSingleton() 方法时才会装在 SingletonInstance 这个静态内部类
     */
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getSingleton() {
        return SingletonInstance.INSTANCE;
    }
}

优缺点:

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

推荐使用这种方式

七、枚举

示例:

/**
 * @author zheng
 */
public class SingletonTest1 {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();

        // 比较对象是否相等,这里比较的是地址值
        System.out.println(singleton1 == singleton2);
        // Singleton 的 hashcode 值
        System.out.println(singleton1.hashCode());
        System.out.println(singleton2.hashCode());

    }
}

class Singleton {
    public Singleton() {
    }

    /**
     * 枚举类型时线程安全的并且只会装载一次
     */
    private enum SingletonEnum {
        INSTANCE;
        private final Singleton instance;

        SingletonEnum() {
            instance = new Singleton();
        }

        private Singleton getInstance() {
            return instance;
        }
    }

    /**
     * 枚举类实现单例模式是 effective java 作者极力推荐的单例实现模式
     * 因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式
     * 枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
     */
    public static Singleton getInstance() {
        return SingletonEnum.INSTANCE.getInstance();
    }
}

优缺点:

  • 优点:避免了多线程同步问题,而且还能防止反序列化重新创建新的对象

非常推荐

结尾

我们在哪里用到了单例模式呢?

其实单例设计模式我们在开发中常使用的 Spring 框架中就使用了,Spring 依赖注入的 bean 就是默认就是单例的,它是通过双检锁(双重检查)实现的。其实还有很多,但是说起来就很繁琐,就靠大家自己去发现吧。。绝不是我懒┗( ▔, ▔ )┛

为了不让大标题太短,说一说我对设计模式的感受吧

设计模式这个东西,不用一下,是不知道多好用的,其实我用的设计模式并不是很多,只有个别几种,但是使用的时候,无疑都让我感叹妙呀(✪ω✪),不同的模式有不同的作用,这个后面慢慢说,立个flag,研究23种设计模式,并出博客。\

说说好处吧,这玩意真的可以使代码的可读性变高,前一段时间,就用到了状态机模式,维护几个状态类,不同的条件走不同的逻辑,比一堆if-else好看多了,而且当需要改指定某一段逻辑的时候,修改也比较方便,深受其利 d=====( ̄▽ ̄)。\

当然也不要为了使用设计模式,在业务中硬套业务逻辑,怎么简单怎么来就完事了(当然也是在长远考虑的前提下这样干,可不要因为眼前的简单,导致以后流泪(╥╯^╰╥))。

设计模式肯定是得学的,不管是开发中还是面试都会用的到,主要还是设计模式这个是硬通货,用任何语言开发,都可以用到。

012E694C.gif 本篇文章就先分享到这里了,如果有问题欢迎评论区留言提问!

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿