23种设计模式 Java 单例模式(八种)

八种单例模式

饿汉式 静态常量(调用:静态方法)
饿汉式 静态代码块
懒汉式 线程不安全
懒汉式 线程安全,同步方法
懒汉式 线程安全,同步代码块
双重检查
静态内部类
枚举

前言

首先我们先熟悉一个概念:懒加载 Lazy loading
懒加载:其实就是延时加载,即当对象需要用到的时候再去加载。

1、饿汉式 (静态常量)√

这种方式基于ClassLoader机制避免了多线程的同步问题。不过 instance在类装载是就实例化,在单例模式中大多数都是调用 getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方法(或者其他的静态方法)导致类装载,这时候初始化instance就没达到 lazy loading(懒加载) 的效果

优点与缺点:

  • 优点:写法简单,就是在类装载的时候就完成实例化。避免了线程同步问题(没有多线程问题,早于线程创建)。
    (为什么会是类装载就可以完成实例化?–传送门)

  • 缺点:在类装载的时候就完成实例化,没有达到Lazy loading 的效果。从头到尾没有使用这个实例的话,会对内存造成浪费

  • 结论:这种单例模式可用,但是造成内存的浪费

↓ 代码实现:

/*
饿汉式 (饥渴,上来就干)
技术:静态常量
 */
public class StaticFinal {
    // 单例第一步:构造器私有化,外面不能new
    private StaticFinal() {
        System.out.println("实例化");
    }

    // 2:本类内部创建 静态常量 显示赋值
    private static final StaticFinal instance = new StaticFinal();

    // 3:提供一个静态方法,返回实例对象
    public static StaticFinal getInstance() {
        return instance;
    }

}

//测试类
public static void main(String[] args) {
    StaticFinal.getInstance();
}

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

  • 优点、缺点如上
  • 结论:这种单例模式可用,但是造成内存的浪费

↓ 代码实现:

public class StaticBlock {

    private StaticBlock() {
        System.out.println("实例化");
    }

    private static StaticBlock instance;

    static {
        instance = new StaticBlock();
        System.out.println("静态代码块");
    }

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

//测试类
public static void main(String[] args) {
    StaticFinal.getInstance();
}

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

  • 优点:起到了Lazy loading 的效果,但是只能在单线程下使用
  • 缺点:在多线程下,一个线程进入了 if条件的判断代码块中,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以多线程下这种方式不可用
  • 结论:在实际开发中,不要使用这种方式

↓ 代码实现:

public class ThreadUnsafe {
    // 1、首先接口私有化
    private ThreadUnsafe() {
    }

    //2、创建私有静态变量
    private static ThreadUnsafe instance;

    // 提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    // 懒汉式
    public static ThreadUnsafe getInstance() {
        if (instance == null) {
            instance = new ThreadUnsafe();
        }
        return instance;
    }
}

//测试类
public static void main(String[] args) {
  for (int i = 0; i < 10; i++) {
     new Thread(()->{
         ThreadUnsafe.getInstance();
     }).start();
  }
}

4、 懒汉式(线程安全、同步方法)

  • 优点:解决了线程不安全的问题
  • 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了,方法进行同步效率太低了。
  • 结论:在实际开发中,不要使用这种方式
public class StaticSafe {
    private StaticSafe(){}

    private static StaticSafe instance ;

    public synchronized static StaticSafe getInstance(){
        if(instance==null){
            instance = new StaticSafe();
            System.out.println(Thread.currentThread().getName() +"创建实例中。。。");
        }
        return instance;
    }
}

// 测试
public static void main(String[] args) {
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            StaticSafe.getInstance();
        }
    }).start();
}

5、 懒汉式(线程不安全、同步代码块)

  • 优点:本想是滴第四种(上面)实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的代码块
  • 缺点:但这种同步并不能起到线程同步的作用。跟第三种实现方式遇到的情形一致,加入一个线程进入了 if (instance == null) 判断语句块,那未来得及往下执行,另一个线程也通过这个判断语句,这时便会产生多个实例
  • 结论:在实际开发中,不要使用这种方式

↓ 代码实现:

public class ThreadUnsafe {
    // 1、首先接口私有化
    private ThreadUnsafe() {
    }

    //2、创建私有静态变量
    private static ThreadUnsafe instance;

    // 提供一个静态的公有方法,当使用到该方法时,才去创建 instance
    // 懒汉式
    public  static ThreadUnsafe getInstance() {
        if (instance == null) {
            synchronized (ThreadUnsafe.getInstance()){
                instance = new ThreadUnsafe();
            }
        }
        return instance;
    }
}
// 测试
public static void main(String[] args) {
    new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            StaticSafe.getInstance();
        }
    }).start();
}

6、 双重检查(测)DCL(double check lock)

优点:

  • Double-Check 概念是多线程开发中常使用到,如代码中所示,我们进行了俩次 if(instance==null) 检查,这样就保证线程安全了
  • 这样,实例化代码只用执行一次,后面再次访问时,判断 if(instance==null),直接return 实例化对象,也避免反复进行方法同步
  • 线程安全;延迟加载;效率高
  • 结论:在实际开发中,推荐使用这种单例设计模式

↓ 代码实现:

public class DoubleCheck {
    private DoubleCheck() {
        System.out.println(Thread.currentThread().getName());
    }

    private static volatile DoubleCheck INSTANCE;

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

    // 测试
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                DoubleCheck.getInstance();
            }).start();
        }
    }
}

7、静态内部类 √

  • 这种方式采用了类装载的机制来保证初始化实例时只有一个线程
  • 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程安全性,在类进行初始化时,别的线程是无法进入的
  • 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
  • 结论:在实际开发中,推荐使用
public class StaticInside {
    // 1.私有构造函数
    private StaticInside(){}


    //2.静态内部类,该类中有一个静态属性
    private static class SingletonInstance{
        private static final  StaticInside INSTANCE = new StaticInside();
    }

    // 3.提供一个静态的公有方法,直接返回
    public static StaticInside getInstance(){
        // 调用静态内部类中的 静态变量
        return SingletonInstance.INSTANCE;
    }
}

8、枚举

/*
* 不进可以解决线程同步,还可以防止反序列化
* */
public enum  Enum1 {
    INSTANCE;
    public void m(){
        // 业务方法
    }
}

// 测试
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        new Thread(()->{
            System.out.println(Enum1.INSTANCE.hashCode());
        }).start();
    }
}

你可能感兴趣的:(java,设计模式)