单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
1.单例模式的几种实现方式
所谓的单例必须要保证以下几点:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
(1)饿汉式实现单例
代码如下:
package com.dangle.interview;
import java.io.Serializable;
public class SingleOne implements Serializable {
private static SingleOne singleOne=new SingleOne(); //创建私有自身对象
private SingleOne(){ //私有构造方法
}
public static SingleOne getIntanse(){ //提供该实例
return singleOne;
}
private Object readResolve(){ //支持反序列化
return singleOne;
}
}
饿汉式是线程安全的,但是反射是不安全的,反序列化也是不支持的但是我们加上
private Object readResolve(){ //支持反序列化
return singleOne;
}
这些代码就支持反序列化了。
@Test
public void text2() throws Exception{
Class clazz=SingleOne.class;
Constructor declaredConstructor = clazz.getDeclaredConstructor();
declaredConstructor.setAccessible(true); //暴力获取
SingleOne s1=(SingleOne) declaredConstructor.newInstance();
SingleOne s2 = SingleOne.getIntanse();
System.out.println(s1);
System.out.println(s2);
}
结果:很明显不是一个对象。
com.dangle.interview.SingleOne@1060b431
com.dangle.interview.SingleOne@612679d6
(2)登记式实现单例
代码如下:
package com.dangle.interview;
/**
* 登记式
*/
public class SingOneD implements Serializable{
private static class Singoo{ //使用静态内部类的方式,创建一个私有的实例对象
private static SingOneD sing=new SingOneD();
}
private SingOneD(){
system.out.ptintln("加载了") //私有构造方法
}
public static SingOneD getInstance(){ //提供外界获取方法
return Singoo.sing;
}
private Object readResolve(){ //支持反序列化
return singleOne;
}
}
使用内部类创建实例对象的时候会起到延时加载的作用,在加载SingOneD类的时候并不会创建实例对象,只有我们使用getInstance方法的时候,java才会加载内部类去生成实例对象。
延时加载的好处: 合理可以避免CPU和内存高峰。
登记式是线程安全的,反射是不安全的,但是反射我们可以防止和反序列化一样
private SingOneD() {
system.out.ptintln("加载了") //私有构造方法
if(Singoo.sing!=null){
throw new RuntimeException("非法获取");
}
}
我们可以防止反射,当反射获取时抛出异常。同时饿汉式也可以通过这种方法防止反射。
(3)枚举式实现单例
代码如下:
package com.dangle.interview;
public enum SingEnum {
INSTANCE{ //定义实现方法
@Override
protected void doSomething(){
System.out.println("do something.......");
}
};
protected abstract void doSomething();
}
很明显枚举式防止反射,支持反序列化是非常高效的,但是枚举类不能继承是一个欠缺。
(4)懒汉式实现单例
代码如下:
package com.dangle.interview;
public class SingOneL {
private static SingOneL singOneL=null; //添加volatile关键字防止指令重排
private SingOneL(){
}
public static SingOneL getStance(){
if(singOneL==null){
singOneL=new SingOneL();
}
return singOneL;
}
}
很明显是线程不安全的,在并发问题下可以产生多个实体
修改:
public static SingOneL getStance(){
if(singOneL==null){
synchronized (SingOneL.class){
if(singOneL==null){
singOneL=new SingOneL();
}
}
}
return singOneL;
}
双重验锁,提高性能。
singOneL=new SingOneL()会执行如下操作:
(1)分配对象内存空间
(2)初始化对象
(3)singOneL指向(1)中分配的空间
在默写编译器上会出现指令重排:
(1)分配对象内存空间
(2)singOneL指向(1)中分配的空间(此时对象并没有初始化)
(3)初始化对象
有非常小的概率会使获取对象实例为null,所以当我们添加volatile关键字的时候就会避免这种小概率的产生。