设计模式——单例模式
原文链接:单例模式详解
什么是单例模式?
保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式
为什么要使用单例模式?
1、单例模式节省公共资源。比如说大家都要喝水,但没必要每家每户都打一口井。
2、单例模式方便控制。比如日志管理,如果多个人同时来写日志,日志整合就会是一个难题,而单例模式只有一个人来向日志里写入信息方便控制,避免了这种多人干扰的问题出现。
单例模式限制了类实例化对象的个数只能是一个,这样不仅可以节省公共资源还能方便控制管理。比如日志管理、线程池、数据库连接池等都需要使用单例模式。
实现单例模式的方法
饿汉模式
public class Singleton { //先把对象创建好 private static final Singleton singleton = new Singleton(); private Singleton() {} //其他人来拿的时候直接返回已创建好的对象 public static Singleton getInstance() { return singleton; } }
优点:简便,且线程安全
缺点:类的实例化对象如果一直不被调用就会造成资源的浪费
懒汉模式
public class Singleton { private static Singleton singleton = null; private Singleton() { } //获取对象的时候再进行实例化 public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
优点:避免了资源的浪费
缺点:线程不安全,并发模式下不同线程同一时间调用的时候会创建多个类的实例。需要上锁才能实现单例。
解决方案1:对整个判断过程加锁
public class Singleton { private static Singleton singleton = null; private Singleton() {} //获取对象的时候再进行实例化 public static Singleton getInstance() { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } return singleton; } }
优点:避免资源浪费,线程安全
缺点:加锁之后严重影响了性能。
解决方案2:双重检查加锁(DCL,Double Check Lock),即判断对象为null的时候才加锁
public static Singleton getInstance() { if (singleton == null) {//先验证对象是否创建 synchronized (Singleton.class) {//只有当对象未创建的时候才上锁 if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
优点:避免资源浪费,线程安全,高性能.
缺点:Jvm指令乱序导致多线程下可能出错。
情况分析:
线程1调用getInstance 获取对象实例,因为对象还是空未进行初始化,此时线程1会执行new Singleton()进行对象实例化,而当线程1的进行new Singleton()的时候JVM会生成三个指令。
指令1:分配对象内存。
指令2:调用构造器,初始化对象属性。
指令3:构建对象引用指向内存。
因为编译器会自作聪明的对指令进行优化, 指令优化后顺序会变成这样:
1、执行指令1:分配对象内存,
2、执行指令3:构建对象引用指向内存。
3、然后正好这个时候CPU 切到了线程2工作,而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了),那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。
所以在这种情况下,双检测锁定的方式会出现DCL失效的问题
补充参考:双重检查锁(DCL)的原理与失效原因
解决方案3:静态内部类实现懒汉模式
class Singleton { private Singleton() { } public static Singleton getInstance() { return SingletonHoler.singleton; } //定义静态内部类 private static class SingletonHoler { //当内部类第一次访问时,创建对象实例 private static Singleton singleton = new Singleton(); } //测试 public static void main(String[] args) { if (Singleton.getInstance() == Singleton.getInstance()) { System.out.println("same"); } } }
静态内部类原理:
当外部类被访问时,并不会加载内部类,所以只要不访问SingletonHoler 这个内部类, private static Singleton singleton = new Singleton() 不会实例化,这就相当于实现懒加载的效果,只有当SingletonHoler.singleton 被调用访问内部类的属性,此时才会将对象进行实例化,这样既解决了恶汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。

浙公网安备 33010602011771号