单例模式的两种写法与枚举使用

单例模式的五种写法

概念

单例模式的定义就是确保某一个类只有一个实例,并且提供一个全局访问点
属于设计模式三大类中的创建型模式。
单例模式具有典型的三个特点:

只有一个实例。
自我实例化。
提供全局访问点。

其UML结构图非常简单,就只有一个类,如下图:
在这里插入图片描述

优点与缺点

优点:只生成了一个实例,节约系统资源,同时也能够严格控制客户对它的访问。
缺点:因为系统中只有一个实例,这样就导致了单例类的职责过重,违背了“单一职责原则”,同时也没有抽象类,扩展困难。

墙裂注意
注意单例模式所属类的构造方法是私有的,所以单例类是不能被继承的。

实现方式

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

饿汉式

饿汉式:线程安全,调用效率高。但是不能延时加载。
饿汉式是静态加载的时候实,不需要担心线程安全问题。
该模式在加载类的时候对象就已经创建了,所以加载类的速度比较慢,但是获取对象的速度比较快,且是线程安全的。

@AllArgsConstructor
@Data
public class SingletonHungry {

        private Integer initA;
        private Integer count;

        //线程安全的
        //类初始化时,立即加载这个对象
        private static SingletonHungry instanceHungry = new SingletonHungry();

        private SingletonHungry() {
                initA=0;
                count=0;
        }


        //方法没有加同步块,所以它效率高
        //静态代码块尽量不要处理变量等逻辑,每次得到实例对象,都会走一遍,可有一些计数,共用了几次
        public static SingletonHungry getInstanceHungry() {
                instanceHungry.setCount(instanceHungry.getCount()+1);
            return instanceHungry;
        }

}

测试:

public class Test01 {
    public static void main(String[] args) {

        SingletonHungry singletonHungry = SingletonHungry.getInstanceHungry();
        singletonHungry.setInitA(singletonHungry.getInitA()+1);
        //1
        System.out.println(singletonHungry.getInitA());

        SingletonHungry singletonHungry2 = SingletonHungry.getInstanceHungry();
        singletonHungry2.setInitA(singletonHungry.getInitA()+1);
        //2
        System.out.println(singletonHungry2.getInitA());

        SingletonHungry singletonHungry3 = SingletonHungry.getInstanceHungry();
        singletonHungry3.setInitA(singletonHungry.getInitA()+1);
        //3
        System.out.println(singletonHungry3.getInitA());

        //始终只有一个实例对象
        singletonHungry2.setInitA(100);
        //100
        System.out.println(singletonHungry3.getInitA());

        //3
        System.out.println(singletonHungry3.getCount());

    }
}

变种

public class Singleton {
    private static Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton() { }
    public static Singleton getInstance() {
        return this.instance; 
    }
}

懒汉式

懒汉式:线程不安全。
在多线程环境下,但A.B两个线程都进入 if(instanceLazy == null),此时有可能会生成两个对象,所以是线程不安全的

案例:
由于该模式是在运行时加载对象的,所以加载类比较快,但是对象的获取速度相对较慢,且线程不安全。如果想要线程安全的话可以加上synchronized关键字,但是这样会付出惨重的效率代价。

public class SingletonLazy {

    //线程不安全的

    private static SingletonLazy instanceLazy = null;

    private SingletonLazy() {
    }

    //运行时加载对象
    public static SingletonLazy getInstance() {
        if (instanceLazy == null) {
            instanceLazy = new SingletonLazy();
        }
        return instanceLazy;
    }
}
解决方案:
1

加上synchronized关键字,并发的时候也只能一个一个排队进行getInstance()方法访问。影响性能。

public static synchronized Singleton getInstance() {  
    if (single == null) {    
        single = new Singleton();  
    }    
    return single;  
}  
2

懒汉式(双重同步锁):
一般的解决方案是使用双重同步锁机制,例子如下:
锁机制:保证同一时刻只有一个线程访问(即进行实例化)
在并发量高的情况下,不需要排队进getInstance()方法合理利用系统资源

public class SingletonLazySynchronized {
    private static volatile SingletonLazySynchronized instanceLazySynchronized = null;

    private SingletonLazySynchronized() {
    }

    //运行时加载对象
    //静态的工厂方法
    public static SingletonLazySynchronized getInstance() {
        if (instanceLazySynchronized == null) {
            synchronized(SingletonLazySynchronized.class){
                if(instanceLazySynchronized == null){
                    instanceLazySynchronized = new SingletonLazySynchronized();
                }
            }
        }
        return instanceLazySynchronized;
    }
}

**volatile**关键字说明:禁止重排序的功能
没有此关键字时虽然加了锁 和双重判断,但是其实这个类是 线程不安全的

原因是:

这个要从cpu的指令开始说起

当我们执行

instanceLazySynchronized = new SingletonLazySynchronized();时 要执行什么操作呢

主要分三步

1.分配对象内存空间 memory = allocate()分配对象内存空间

2.ctorInstance()初始化对象

3.instance = memory 设置instance指向刚分配的内存

但是由于存在指令重排的情况(单线程情况下无影响。多线程下会有影响)

由于2 和3 没有顺序的必然关系

也就可能会出现

1.分配对象内存空间 memory = allocate()分配对象内存空间

3.instance = memory 设置instance指向刚分配的内存

2.ctorInstance()初始化对象

此时我们假设有两个线程A和B进入

1.A 先执行了 3.instance = memory 设置instance指向刚分配的内存这个操作,但是还未来得及初始化对象。

2.B 判断 if(instance == null) 时 则会返回true 然后instance, 这时返回值就会出现问题 。

解决方案:

此时使用volatile关键字 则可以解决这个问题 volatile 关键字 有禁止重排序的功能

3

私有静态内部类实现单例模式,这种方式优于上面两种方式,他即实现了线程安全,又省去了null的判断,性能优于上面两种。

public class SingletonLazyStatic {
    private static class LazyHolder {
        private static final SingletonLazyStatic INSTANCE = new SingletonLazyStatic();
    }
    private SingletonLazyStatic (){}
    public static final SingletonLazyStatic getInstance() {
        return LazyHolder.INSTANCE;
    }
}

常见应用场景

网站计数器。
项目中用于读取配置文件的类。
数据库连接池。因为数据库连接池是一种数据库资源。
Spring中,每个Bean默认都是单例的,这样便于Spring容器进行管理。
Servlet中Application
Windows中任务管理器,回收站。
等等。

思考:

此时这个类则是线程安全的,当然如果要使用单例模式,推荐使用的还是枚举方法

枚举单例模式

为什么使用单例?

  1. 私有化构造器并不保险
    《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
  2. 序列化问题,任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve

枚举示例

public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

完整的枚举单例:

public class User {
    private String lastName;
    private Integer numbers;
    //私有化构造函数 初始化
    private User(){
        lastName="zhangsan";
        numbers=0;
    }

    //定义一个静态枚举类
    static enum SingletonEnum{
        //创建一个枚举对象,该对象天生为单例
        INSTANCE;
        private User user;
        //私有化枚举的构造函数,初始化一个对象实例
        private SingletonEnum(){
            user=new User();
        }
        //公开获取初始化的对象实例
        public User getInstnce(){
            return user;
        }
    }

    //对外暴露一个获取User对象的静态方法
    public static User getInstance(){
        return SingletonEnum.INSTANCE.getInstnce();
    }
}

测试:

System.out.println(User.getInstance());
System.out.println(User.getInstance());
System.out.println(User.getInstance()==User.getInstance());

结语

单元素的枚举类型已经成为实现Singleton的最佳方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值