java 单例模式的8种写法

什么是单例模式

      单例模式是一种常用的软件设计模式。它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。

单例模式的优点

  1. 实例控制 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
  2. 灵活性 因为类控制了实例化过程,所以类可以灵活更改实例化过程。
  3. 减少内存开销
  4. 提供了对唯一实例的受控访问
  5. 允许可变数目的实例
  6. 避免资源多重占用

单例模式的缺点

  1. 没有抽象层,因此单例类的拓展性差
  2. 不适用于变化的对象
  3. 违背了单一职责

应用场景

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

单例模式写法1

饿汉式: 使用静态常量方式创建实例

public class SingletonDemo1 {
    /**
     * 1. 私有构造方法
     */
    private SingletonDemo1() {
    }

    /**
     * 2. 类内部之间创建对象
     */
    private static final SingletonDemo1 SINGLETON_DEMO_1 = new SingletonDemo1();

    /**
     * 3. 提供公有方法,获取实例
     */
    public static SingletonDemo1 getInstance() {
        return SINGLETON_DEMO_1;
    }

}

测试

public static void main(String[] args) {
        SingletonDemo1 instance1 = SingletonDemo1.getInstance();
        SingletonDemo1 instance2 = SingletonDemo1.getInstance();
        System.out.println(instance1==instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }

输出结果

true
1804094807
1804094807

Process finished with exit code 0

优点: 写法简单,类加载时就完成实例化,避免线程同步问题.

缺点: 类加载时完成初始化,没有懒加载.如果从始至终没有用到这个类,会造成 内存浪费.

总结: 在不考虑内存浪费的情况下还是可以使用的,毕竟现在的环境也不缺少这点内存

单例模式写法2

饿汉式: 使用静态代码块实例对象

public class SingletonDemo2 {

    /**
     * 1. 私有构造方法
     */
    private SingletonDemo2() {
    }

    private static SingletonDemo2 singletonDemo2;

    /**
     * 2. 创建实例
     */
    static {
        singletonDemo2 = new SingletonDemo2();
    }

    /**
     * 3. 提供公有方法获取实例
     */
    public static SingletonDemo2 getInstance() {
        return singletonDemo2;
    }
}

优点: 这种方式和第一种类似。写法简单,类加载时就完成实例化,避免线程同步问题.

缺点: 类加载时完成初始化,没有懒加载.如果从始至终没有用到这个类,会造成内存浪费.

总结: 在不考虑内存浪费的情况下可以使用.

单例模式写法3

懒汉式: 线程不安全

public class SingletonDemo3 {

    /**
     * 1. 私有构造方法
     */
    private SingletonDemo3() {
    }

    private static SingletonDemo3 singletonDemo3;

    /**
     * 2. 提供获取实例的公有方法,使用该方法时才会创建实例
     */
    public static SingletonDemo3 getInstance() {
        if (null == singletonDemo3) {
            singletonDemo3 = new SingletonDemo3();
        }
        return singletonDemo3;
    }

}

优点: 起到懒加载效果,节省内存

缺点: 只能在单线程中使用.多线程下如果线程1进入 if 语句中还未完成初始化,线程2又进入 if 中,会产生多个实例.在实际开发中不要使用这种方式.

总结: 不推荐使用

单例模式写法4

懒汉式: 同步方法

public class SingletonDemo4 {
    /**
     * 1. 私有构造方法
     */
    private SingletonDemo4() {
    }

    private static SingletonDemo4 singletonDemo4;

    /**
     * 2. 提供获取实例的公有方法,加入同步代码块,解决线程不安全问题,使用该方法时才会创建实例.
     */
    public static synchronized SingletonDemo4 getInstance() {
        if (null == singletonDemo4) {
            singletonDemo4 = new SingletonDemo4();
        }
        return singletonDemo4;
    }
}

优点: 解决线程不安全问题

缺点: 效率低,每个线程获取实例时,都要进行同步

总结: 不推荐使用,效率太慢

单例模式错误写法

贴出错误代码的原因是如果看到这种写法,能发现错误

public class SingletonDemo5 {
    /**
     * 1. 私有构造方法
     */
    private SingletonDemo5() {
    }

    private static SingletonDemo5 singletonDemo5;

    /**
     * 2. 提供获取实例的公有方法,加入同步代码块,然而并没有什么用,线程依然不安全
     */
    public static SingletonDemo5 getInstance() {
        if (null == singletonDemo5) {
            // 注意这里
            synchronized (SingletonDemo5.class) {
                singletonDemo5 = new SingletonDemo5();
            }

        }
        return singletonDemo5;
    }
}

总结: 看似聪明的加了锁,实则卵用没有. 多个线程进入 if 中, 依然产生不同实例

单例模式写法6

懒加载: 双重检查枷锁、volatile关键字修饰

public class SingletonDemo6 {

    /**
     * 1. 私有构造方法
     */
    private SingletonDemo6() {
    }

    /**
     * 2. volatile 关键字修饰
     */
    private static volatile SingletonDemo6 singletonDemo6;

    /**
     * 3. 提供公共的实例方法,使用双重检查实例对象
     */
    public SingletonDemo6 getInstance() {
        if (null == singletonDemo6) {
            synchronized (SingletonDemo6.class) {
                if (null == singletonDemo6) {
                    singletonDemo6 = new SingletonDemo6();
                }
            }
        }
        return singletonDemo6;
    }
}

优点: 线程安全,延迟加载,同时保证效率 推荐使用

缺点: 写法稍微复杂

总结: volatile 关键字防止指令重排序问题,立即更新到主存.保证一个线程更新instance 其余线程立即可知

类的初始化字节码层步骤:

  1. 分配空间
  2. 初始化
  3. 引用赋值

JITCPU 等会对指令重排: 第2步和第3步顺序随机颠倒,在单线程下对执行结果没有影响

多线程下:
      线程1走到 if (null == singletonDemo6) 为空时继续往下走、进锁、发现对象为空开始初始化

      如果按照 分配空间引用赋值初始化 的顺序执行,假设线程1走到引用赋值时, 线程2进入 getInstance() 方法,发现 singletonDemo6 有值,直接返回实例. 但线程1此时还没有执行 初始化 步骤,将有可能会发生空指针等问题

      因此加入 volatile 关键字防止指令重排

单例模式写法7

使用静态内部类实例对象

public class SingletonDemo7 {

    /**
     * 1. 私有化构造参数
     */
    private SingletonDemo7() {
    }

    /**
     * 2. 通过静态内部类的方式实例对象
     */
    private static class SingletonDemo7Instance {
        private static final SingletonDemo7 SINGLETON_DEMO_7 = new SingletonDemo7();
    }
    
    /**
     * 3. 提供对外的实例方法
     */
    public static SingletonDemo7 getInstance() {
        return SingletonDemo7Instance.SINGLETON_DEMO_7;
    }
}

优点: 采用类装载机制来保证初始化实例时只有一个线程. 利用静态内部类的特点实现懒加载,效率高 推荐使用

缺点: 写法稍微微有点点复杂

总结: 静态内部类特点

  1. SingletonDemo7装载的时候,静态内部类不会装载
  2. 当使用getInstance()时,静态内部类才开始装载,并且只会被装载一次
  3. 当类被装载的时候,线程是安全的

单例模式写法8

枚举实现

public enum SingletonDemo8 {

    INSTANCE
}

测试

    public static void main(String[] args) {
        SingletonDemo8 instance1 = SingletonDemo8.INSTANCE;
        SingletonDemo8 instance2 = SingletonDemo8.INSTANCE;
        System.out.println(instance1 == instance2);
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }

结果

true
1804094807
1804094807

Process finished with exit code 0

优点: 利用枚举的特性,不仅避免了多线程安全的问题,而且还防止反序列化重新创建新的对象. 并且这种方式是写法简单,Effective作者Josh Bloch 提倡的方式

缺点: 你猜

总结: 推荐使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值