美文网首页
单例模式: Singleton

单例模式: Singleton

作者: 顺利_4364 | 来源:发表于2019-02-21 20:36 被阅读0次

时间: 2019-02-21


参考地址: 单例模式的八种写法比较

一、单例模式的实现思路

  • Singleton类只会生成一个实例. 该类定义了static字段(类的成员变量), 并将其初始化为Singleton类的实例.初始化行为仅
    在该类被加载时进行一次.
  • Singleton类的构造函数是private的, 为了禁止从Singleton类外部调用构造函数.

总结:

  1. 定义private static Singleton INSTANCE;的成员变量;
  2. 构造函数私有.
  3. 一个public的静态获取单例的方法.

二、单例模式的八种写法

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

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

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

public class Singleton1 {
    // 1. 定义static成员变量
    private final static Singleton1 singleton1 = new Singleton1();

    // 2. 定义私有构造方法
    private Singleton1() {
    }

    // 3. 静态方法获取单例.
    public static Singleton1 getInstance() {
        return singleton1;
    }
}

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

将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

public class Singleton2 {
    // 1. static 成员变量
    private static Singleton2 singleton2;

    // 2. 初始化
    static {
        singleton2 = new Singleton2();
    }

    // 3. 私有构造函数
    private Singleton2() {
    }

    // 4. 静态方法获取单例
    public static Singleton2 getInstance() {
        return singleton2;
    }
}

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

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

public class Singleton3 {
    // 1. static 成员变量
    private static Singleton3 singleton3;

    // 2. 私有构造方法
    private Singleton3() {
    }

    // 3. 静态方法获取单例.
    public static Singleton3 getInstance() {
        // 这步判断, 如果有2个线程同时进入, 则会产生2个实例, 就不是单例的了. 线程不安全.
        if (null == singleton3) {
            singleton3 = new Singleton3();
        }
        return singleton3;
    }
}

写法四: 懒汉式(线程安全, synchronize同步方法)[效率低]

优点: 解决了线程不安全的问题.
缺点: 同步效率低.

public class Singleton4 {
    // 1. static成员变量
    private static Singleton4 singleton4;

    // 2. 私有构造方法
    private Singleton4() {
    }

    // 3. 静态同步方法获取单例. synchronized修饰, 多线程情形下要排队等待对象锁释放.
    public static synchronized Singleton4 getInstance() {
        if (null == singleton4) {
            singleton4 = new Singleton4();
        }
        return singleton4;
    }
}

写法五: 懒汉式(线程安全, 同步代码块)

要么效率低, 要么不安全

public class Singleton5 {
    // 1. static成员变量
    private static Singleton5 singleton5;

    // 2. 私有构造方法
    private Singleton5() {
    }

    // 3. 静态同步代码块方法获取单例. 效率低. 与用synchronized修饰方法一样.
    public static Singleton5 getInstance1() {
      synchronized (Singleton5.class) {
          if (null == singleton5) {
              return new Singleton5();
          }
          return singleton5;
      }
    }

    // 3. 静态同步方法获取单例. null == sinleton5这一步无法避免会有线程安全问题
    public static Singleton5 getInstance2() {
        if (null == singleton5) { // 线程不安全
            synchronized (Singleton5.class) {
                return new Singleton5();
            }
        }
        return singleton5;
    }
}

写法六: DCL(双重检查锁机制 Double Check Lock)

public class Singleton6 {
    // 1. static成员变量
    private static Singleton6 singleton6;

    // 2. 私有构造方法
    private Singleton6(){

    }

    // 3. 静态方法获取单例. 使用双重检查
    public static Singleton6 getInstance() {
        if (null == singleton6) { // 第一重检查
            synchronized (Singleton6.class) { // 锁Singleton6类.
                if (null == singleton6) { // 第二重检查
                    return new Singleton6();
                }
            }
        }
        return singleton6;
    }
}

写法七: 静态内部类

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

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

public class Singleton7 {
    // 1. 在私有静态内部类中定义static final 成员变量并实例化
    private static class Demo{
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    // 2. 私有构造方法
    private Singleton7() {}

    // 3. 静态方法获取单例
    public static Singleton7 getInstance() {
        return Demo.INSTANCE;
    }
}

写法八: 用枚举实现单例模式

案例1

public enum Singleton8Enum {
    INSTANCE;
    public void getInstance() {
        System.out.println("用枚举实现单例模式");
    }
}

案例2

public enum DemoEnum {
    // 理解: INSTANCE是DemoEnum的实例.
    INSTANCE;

    // 定义INSTANCE包含参数User.
    private User instance;

    // 枚举类的私有构造方法. 默认是private的.
     DemoEnum() {
        instance = new User();
    }

    // 获取枚举类的具体参数user
    public User getInstance() {
        return instance;
    }
}

@Data
@Slf4j
public class User {
    private String name;
    private String address;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    public User(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public static void main(String[] args) {
        // 心得-定义 User类可以用new User();获取很多不同的实例对象. 枚举是列举出有穷序列集.
        // 定义枚举类, 里面只列举出一个值, 此值代表一个User类的实例. 通过getInstance()方法可以获取到INSTANCE的值: 一个User类的实例.
        // Enum: 自由序列化, 线程安全, 保证单例
        // Enum是由class实现的->enum作为一个类来实现单例;
        // Enum是通过继承Enum类实现的, enum不能作为子类继承其他类.也不能被继承, 是final修饰的. 但是可以用来实现接口
        // Enum有且仅有private的构造器. 防止外部的额外构造.
        User user1 = DemoEnum.INSTANCE.getInstance();
        User user2 = DemoEnum.INSTANCE.getInstance();
        User user3 = DemoEnum.INSTANCE.getInstance();
        User user4 = DemoEnum.INSTANCE.getInstance();
        log.info("获取的实例哈希值:{}", user1.hashCode());
        log.info("获取的实例哈希值:{}", user2.hashCode());
        log.info("获取的实例哈希值:{}", user3.hashCode());
        log.info("获取的实例哈希值:{}", user4.hashCode());
    }
}

总结: 使用写法一,二,六,七,八. 以后用写法八或写法七.

相关文章

网友评论

      本文标题:单例模式: Singleton

      本文链接:https://www.haomeiwen.com/subject/goaryqtx.html