枚举与单例模式

枚举

枚举的用法:

enum Weekday {
    MON("1",1){
        @Override
        public String toString() {
            return super.toString();
        }
        // 注意这里重写了
        @Override
        public boolean isOrdered() {
            return true;
        }
    },STA("2",2){
        @Override
        public String toString() {
            return super.toString();
        }
    };

    public boolean isOrdered() {
        return false;
    }

    public final String dayValue;
    public int num;

    private Weekday(String dayValue,int num) {
        this.dayValue = dayValue;
        this.num = num;
    }
}

public static void main(String[] args) {
    Weekday weekday = Weekday.MON;
    System.out.println(weekday.isOrdered());
    Weekday sta = Weekday.STA;
    System.out.println(sta.isOrdered());
    // 可以像下面这样进行比较,因为jvm中,仅仅存在单个实例
    Weekday weekday = Weekday.MON;
    assert weekday == Weekday.MON;
}

在enum中可以定义自己的变量和方法。并且每一个实例都可以自己去重写定义在enum中的方法。

在枚举中可以很方便的使用 == 来比较两个枚举是否相等。因为在jvm中,保证了枚举实例的唯一性。它是唯一存在的所以可以直接使用 == 来进行比较。

并且枚举可以说是实现单例模式最好的模式。先复习一下其它的方式:

  • 静态内部类

    利用一个特性:静态内部类在没有访问它的成员属性或者成员方法的时候,static代码块或者static变量是不会被初始化的,那么就可以做到懒加载。(只有主动去访问了,才会加载)并且能够做到线程安全。代码如下:

public class Singleton {  
	// 静态内部类
    private static class SingletonHolder {  
    	private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
	
    public static final Singleton getInstance() { 
        // 只有调用了本方法才会进行静态内部类的初始化
    	return SingletonHolder.INSTANCE;  
    }  
}
  • 饿汉
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    	return instance;  
    }  
}  

这种方式的缺点就是不管用不用都已经被加载了。如果我们希望延迟初始化这个单例对象,就不能使用上述的“饿汉式”实现。为啥这个能够保证线程的安全性呢?因为在类的生命周期内,静态变量只会被初始化一次。那么这个就从JVM的角度来保证了线程的安全性。下图可以看出来静态变量是在什么时候初始化的。
在这里插入图片描述
可以发现非final类型的变量是和类的初始化是相同的。那么类的初始化是在什么时候呢?可以去搜一下类的初始化时机。主要是主动引用就会初始化类(比如说new一个对象,或者反射创建,或者反序列化创创建…),那么在初始化类的时候就会去初始化static变量了。所以static修饰的变量只会被初始化一次。

更加详细的可以看这里.

  • DCL双重校验锁
//实现双重校验锁实现单例模式
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        
        return instance;
    }
}

这里第一点是volatile关键字。这里使用这个原因是禁止指令的重排序。如果没有用这个修饰的话,那么指令可能会被重排。出现这个现象的原因就是,初始化一个对象在CPU中并不是一个原子性的操作。对应着几条的字节码,比如说分配内存,将引用赋值给instance等。那么如果被重排序的话,将最后一句给instance赋值的指令排到最前面的话,那么其它的线程就可能判断到instance不为空,这样就得到了一个还未初始化完全的对象。
第二点是为啥要进行第二次的 instance == null 的判断。因为如果有两个线程都通过了第一个if,被阻塞在同步代码块外面,当一个线程进了同步代码块执行了初始化的操作之后,然后退出了同步代码块,第二个线程进入同步代码块,此时因为第一个线程已经初始化好了,所以如果不再进行一次判空的话就又会进行一次初始化了。

  • 枚举实现单例
    上面的实现都是把构造方法设置成私有的,然后暴露一个方法来获取instance。这样的话可以使用反射去获取所有的构造器,然后newinstance()来获取新的对象。或者是通过序列化反序列化来获取新的对象。都是不安全的。枚举实现单例的代码参考如下:
     public class InstanceDemo {
    
        /**
         * 构造方法私有化
         */
        private InstanceDemo(){
    
        }
    
        /**
         * 返回实例
         * @return
         */
        public static InstanceDemo getInstance() {
            return Singleton.INSTANCE.getInstance();
        }
    
        /**
         * 使用枚举方法实现单利模式
         */
        private enum Singleton {//内部枚举类
            INSTANCE;
    
            private InstanceDemo instance;
    
            /**
             * JVM保证这个方法绝对只调用一次
             */
            Singleton() {
                instance = new InstanceDemo();
            }
    
            public InstanceDemo getInstance() {
                return instance;
            }
        }
    }
    
    获取资源的方式很简单,只要 SomeThing.INSTANCE.getInstance() 即可获得所要实例。并且enum是实现了Serializable接口,就提供了序列化的机制。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值