单例模式-详解

单例模式

 

什么是单例模式(Singleton Pattern)

单例模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式的必备条件

  • 构造方法私有化

  • 全局提供一个获取对象的入口

  • 若不想被反射出来,可在构造方法中抛出异常

常见的单例模式

就目前而言比较常见的有如下几种:

  • 饿汉式

  • 懒汉式

  • 双重检查

  • 静态内部类

  • 枚举

  • 序列化

  • ThreadLocal

各种单例模式的写法

1.饿汉式

/**
 * 好处  能够实现实现安全
 *
 * 坏处 不需要的时候也加载,浪费资源
 */
public class HungrySingle {
​
    private static final HungrySingle hungrySingle = new HungrySingle();
    //构造方法私有化
    //构造方法私有化
     private HungrySingle() {
        /**
         * 阻止了 反射获取单例模式
         */
        if (HungrySingle.hungrySingle != null) {
            throw new RuntimeException("不允许反射创建对象");
        }
    }
​
    public static HungrySingle getHungrySingle() {
        return hungrySingle;
    }
}

饿汉式:就是不管有没有用到这个对象,我都先给你创建好了,用的时候随便用。优点:是一种线程安全的操作,缺点:可能这个对象用不到,但是我们也已经创建出来了,浪费资源。

2.懒汉式

public class LazySingle {
​
​
    public static LazySingle lazySingle = null;
​
      //构造方法私有化
     private LazySingle() {
        /**
         * 阻止了 反射获取单例模式
         */
        if (LazySingle.lazySingle != null) {
            throw new RuntimeException("不允许反射创建对象");
        }
    }
    // 线程不安全的情况下的使用方法 ,没有 synchronized 关键字
    public static final LazySingle getInstance1(){
        if (lazySingle == null) {
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
    
    //线程安全的一种写法,有了 synchronized
    //这种synchronized 锁住 static 方法,是一种类锁,运行太慢,线程多的时候会响应性能
    public synchronized static final LazySingle getInstance2() { 
        if (null == lazySingle) {
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

懒汉式:懒汉式分为线程安全和线程不安全的两种情况,无非就是有没有 synchronized 关键字。它的解释:刚开始我什么都不敢,用的时候才创建对象。

3.双重检查机制

public class LazySingle {
​
​
    public static LazySingle lazySingle = null;
​
    //构造方法私有化
     private LazySingle() {
        /**
         * 阻止了 反射获取单例模式
         */
        if (LazySingle.lazySingle != null) {
            throw new RuntimeException("不允许反射创建对象");
        }
    }
  /**
     * 为什么要有 双重的检查机制
     * 但是 在内部的 sychronized 依旧可能会有多个线程 ,
     * @return
     */
    public static final LazySingle getInstance3() {
        // 在这个地方,如果同时进来多个线程,会在这里等待,上一个线程执行完了,下个线程执行的时候,lazySingle就       // 会被重新开辟空间
        if (null == lazySingle) {
            synchronized (LazySingle.class) {  //这里会有多个线程同时等待的可能
                lazySingle = new LazySingle();
            }
        }
        return lazySingle;
    }
    
    /**
     * 这才是真正的双重检查机制
     * 双重检查机制
     * @return
     */
    public static final LazySingle getInstance4() {
        if (null == lazySingle) {
            //这里纵然有了多个线程在等待,也不会重复开辟空间
            synchronized (LazySingle.class) {
                //会再次校验 lazySingle 是否为 null
                if (null == lazySingle) {
                    lazySingle = new LazySingle();
                }
            }
        }
​
        return lazySingle;
    }
}
双重检锁机制:是一种懒汉式的加强版,线程更加安全。

4.静态内部类

public class InnerSingle {
​
    private InnerSingle() {
        /**
         * 阻止了 反射获取单例模式
         */
        if (InnerClassSingle.innerSingle != null) {
            throw new RuntimeException("不允许反射创建对象");
        }
    }
    private static class InnerClassSingle {
        private static  InnerSingle innerSingle = new InnerSingle();
    }
    /**
     * 巧妙地运用了内部类的执行 过程, 性能优秀
     * @param args
     */
    public static final InnerSingle getInstance() {
        return InnerClassSingle.innerSingle;
    }   
}
内部类创建单例:这种写法的好处是充分利用了静态内部类的特点,它的初始化操作跟外部类是分开的。在没有调用 getInstance() 方法之前,静态内部类不会进行初始化,在第一次调用该方法后就生成了唯一一个实例。

5.枚举方式

public enum  EnumSingle {
​
    INNERSINGLE;
​
    public static EnumSingle getEnumSingle() {
        return INNERSINGLE;
    }
}

枚举:枚举是一种JDK种天然的单例模式,在后面我们来 介绍一下源码种是怎样设计的。

6.序列化方法构建

//要实现 Serializable 接口
public class SerialSingle implements Serializable {
​
    public static final SerialSingle serialSingle = new SerialSingle();
​
    private SerialSingle() {
​
    }
    /**
     * 反序列化 不被破环的关键
     * @return
     *
     * 重写这个方法 只不过是反序列化出来的对象被 反射的对象代替了
     * 在 jvM层面是 进行了两次对象的创建,最终返回的 反射的出来的
     * 之前被被序列化的 被GC回收掉了
     */
    public Object readResolve() {
        return serialSingle;
    }
    
     public static void main(String[] args) {
​
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(new File("serail.obj")));
            ObjectInputStream is = new ObjectInputStream(new FileInputStream(new File("serail.obj")));
            os.writeObject(SerialSingle.serialSingle);
            os.flush();
            os.close();
            //从这里我们进去看下源码的实现吧
            SerialSingle serialSingle = (SerialSingle)is.readObject();
            is.close();
            System.out.println(serialSingle);
            System.out.println(SerialSingle.serialSingle);
​
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
​
    }
}
​

单例使用反序列创建单例:必须要实现 readResolve() 方法,下面是解析为什么要实现这个方法的源码过程

7.ThreadLocal 各线程的单例

public class ThreadLocalSingle {
​
    public static ThreadLocal threadLocal = new ThreadLocal();
​
​
    public static void main(String[] args) {
        threadLocal.set("1");
        System.out.println(threadLocal.get());
    }
}

ThreadLocal:这个只能保证 各自线程之间的线程安全,其实就是把值 放入到了一个全局的Map中,key是线程的名字,值就是我们set的值,这种像是 一种伪单例。

反射为什么是天然的单例

1.直接从代码层面进入 

  public static void main(String[] args) {

        try {
            Class clazz = EnumSingle.class;
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            //从这里进去看 源码
            // 从这里可以看出从 jdk层面上我们就 已经限制了这个 枚举不能够进行反射
            //所以说 枚举 是天然的 单例模式,这里会抛出异常
            Object o = constructor.newInstance();
            System.out.println(o);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

代码层面查看原因

 

反序列化是如何实现单例的、

1.同样通过代码的层面看源码

其实它反射调用 readResolve方法返回的就是我们早已经创建出来的对象我们同过Debug的方式查看是如何创建并返回对象的,如下图所示:

我们对使用反序列化方式获取单例的总结就是:在我们反序列化的时候创建了一个对象①,然后,会继续判断是否有readResolve方法,若有,则通过反射获取对象,之前被反序列化创建的对象会①被GC掉,所以,才实现了单例。

 

今天的单例模式就讲到这里,如果感到有点用处,或者想要了解更多的知识可关注公众号:

谢谢大家~

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值