java:单例模式的五种实现方式

1. 概述

java单例模式是一种常见的设计模式。

单例模式有以下特点:

 1. 单例类只能有一个实例;
 2. 单例类必须自己创建自己的唯一实例;
 3. 单例类必须给所有其他对象提供这一实例;

2. 优缺点

  优点:由于单例模式只生成了一个实例,所以能够节约系统资源,减少性能开销,提高系统运行效率。
  缺点:因为系统中只有一个实例,导致了单例类的职责过重,违背了“单一职责原则”,同时不利于扩展。

3. 单例模式实现方式

常见的单例模式实现方式有五种:饿汉式、懒汉式、双重检测锁、静态内部类和枚举单例。

3.1 饿汉式

public class SingletonDemoInHunger {
    
    // 私有实例,类初始化就加载
    private static SingletonDemoInHunger instance = new SingletonDemoInHunger();
    
    // 私有构造方法
    private SingletonDemoInHunger() {}
    
    // 公共的、静态的获取实例方法
    public static SingletonDemoInHunger getInstance() {
        return instance;
    }
}

饿汉式:

 1. 类加载时就初始化,浪费内存,不能延迟加载;
 2. 基于 classloader 机制避免了多线程的同步问题,线程安全;
 3. 没有加锁,调用效率高。

3.2 懒汉式

public class SingletonDemoInLazy {
    
    // 私有实例,初始化的时候不加载(延迟加载)
    private static SingletonDemoInLazy instance;
    
    // 私有构造
    private SingletonDemoInLazy() {}
    
    // 公共获取实例方法(线程不安全)
    public static SingletonDemoInLazy getInstance() {
        if(instance == null ) { // 使用的时候加载
            instance = new SingletonDemoInLazy();
        }
        return instance;
    }
}

上面这种写法,是线程不安全的,但是可以做到延迟加载

下面是线程安全的懒汉模式:

public class SingletonDemoInLazy {
    
    // 私有实例,初始化的时候不加载(延迟加载)
    private static SingletonDemoInLazy instance;
    
    // 私有构造
    private SingletonDemoInLazy() {}
    
    // 公共获取实例方法(线程安全,调用效率低)
    public synchronized static SingletonDemoInLazy getInstance() {
        if(instance == null ) {
            instance = new SingletonDemoInLazy();
        }
        return instance;
    }
}

上面代码中,通过关键字synchronized声明公共的获取实例的方法getInstance(),可以确保线程安全,能做到延迟加载,但是效率不高。

3.3 double-checked locking(双重检查锁)

public class SingletonDemoInDoubleCheckLock {
    
    // 私有实例,volatile关键字,禁止指令重排。
    private volatile static SingletonDemoInDoubleCheckLock instance;
    
    // 私有构造
    private SingletonDemoInDoubleCheckLock() {}
        
    // 公共获取实例方法(线程安全)
    public static SingletonDemoInDoubleCheckLock getInstance() {
        if(instance == null ) { // 一重检查
            synchronized (SingletonDemoInDoubleCheckLock.class) {
                if(instance == null) { // 二重检查
                    instance = new SingletonDemoInDoubleCheckLock();
                }
            }
        }
        return instance;
    }
}

在加锁之前判断是否为空,可以确保 instance 不为空的情况下,不用加锁,可以直接返回。

加锁之后,还需要判断 instance 是否为空,是为了防止在多线程并发的情况下,会实例化多个对象。例如:线程 a 和线程 b 同时调用 getInstance 方法,假如同时判断 instance 都为空,这时会同时进行抢锁。假如线程 a 先抢到锁,开始执行 synchronized 关键字包含的代码,此时线程 b 处于等待状态。线程 a 创建完新实例了,释放锁了,此时线程 b 拿到锁,进入 synchronized 关键字包含的代码,如果没有再判断一次 instance 是否为空,则可能会重复创建实例。

双重检查锁:

 1. 双重判断,延迟加载;
 2. 线程安全;
 3. JDK 版本要求1.5起。

3.4 静态内部类

public class SingletonDemoInStaticInnerClass {
    
    // 静态内部类
    private static class InnerClass{
        // 初始化实例
        private final static SingletonDemoInStaticInnerClass INSTANCE = new SingletonDemoInStaticInnerClass();
    }
    
    // 私有构造
    private SingletonDemoInStaticInnerClass() {}
    
    // 公关获取实例方法(线程安全,延迟加载)
    public static SingletonDemoInStaticInnerClass getInstance() {
        return InnerClass.INSTANCE;
    }
}

静态内部类:

 1. 利用了classloader机制来保证初始化 instance 时只有一个线程,线程安全
 2. 只有通过显式调用 getInstance 方法时,才会显式装载静态内部类,从而实例化instance,延迟加载

3.5 枚举

public enum SingletonEnum {
    
    // 枚举元素本身就是单例 
    INSTANCE;
    
    
    // 其他要执行的方法
    public void sayHello() {
        System.out.println("你好");
    }
    ......
}

枚举:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。但是不是延迟加载的。


如何选用哪种方式实现单例模式?
  一般情况下,不建议懒汉式,建议使用饿汉式;只有在要明确实现延迟加载效果时,才会使用静态内部类;如果涉及到反序列化创建对象时,可以尝试使用枚举;如果有其他特殊的需求,可以考虑使用双重检查锁。
————————————————

转载于:https://blog.csdn.net/qq_41863849/article/details/106299726

猜你喜欢

转载自blog.csdn.net/weixin_42602900/article/details/124352745