概念

在整个系统中,某个类只能存在一个实例对象,并且只提供一个获取其实例对象的方法。

实现方式的思考

  1. 为了确保只有一个实例对象,就需要调用者不能自己去创建该对象,而是该类提供一个创建对象的方法
  2. 该类需要static 关键字修饰创建出来的对象,才能确保在内存中只有一个对象
  3. 所以该类的构造器只能是private
  4. 提供一个创建对象的方法,让调用者去调用,因为构造器的权限修饰符是private ,所以创建该对象的方法不能用实例对象调用,只能通过类调用,所以该方法只能是static 关键字修饰的方法

实现方式

饿汉式

记忆小妙招: 我饿了,要吃饭,所以饭(对象)要先做(创建)出来

class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }

    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single = new Singleton();

    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        return single;
    }
}

懒汉式

记忆小妙招: 我比较懒,啥时候吃饭(需要对象)啥时候做饭(创建对象)

形式一:普通方式

  • 方式一:有线程安全问题
class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }
    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single;
    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        //A和B两个线程,A线程先进去,但是没有创建好对象,B线程进来也会去创建对象,就会引起线程安全问题
        if(single == null) {
            single = new Singleton();
        }
        return single;
    }
}
  • 方式二:使用同步方法
class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }
    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single;
    // 3.提供公共的静态的方法并且使用synchronized修饰,返回当前类的对象
    public static synchronized Singleton getInstance() {
        if(single == null) {
            single = new Singleton();
        }        
        return single;
    }
}
  • 方式三:使用同步方法,效率较差:对象创建之后,后续不需要创建对象了,调用该方法还是需要线程等待
class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }
    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化
    private static Singleton single;
    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        //使用synchronized修饰来避免线程安全问题
        synchronized(Singleton.class){
            if(single == null) {
                single = new Singleton();
            }
            return single;
        }
    }
}
  • 方式四:双检法,但是会出现指令重排现象(可以通过添加volatile来避免)
class Singleton {
    // 1.私有化构造器
    private Singleton() {
    }
    // 2.内部提供一个当前类的实例
    // 4.此实例也必须静态化,使用volatile关键字来避免指令重排现象
    private static volatile Singleton single;
    // 3.提供公共的静态的方法,返回当前类的对象
    public static Singleton getInstance() {
        if(single == null) {
            //使用synchronized修饰来避免线程安全问题
            synchronized(Singleton.class){
                if(single == null) {
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

形式二:使用内部类

特点:

  • 内部类只有在外部类被调用才加载,产生 INSTANCE 实例;又不用加锁。
  • 此模式具有之前两个模式的优点,同时屏蔽了它们的缺点,是最好的单例模式。
  • 此时的内部类,使用 enum 进行定义,也是可以的。
class Singleton {
 
     private Singleton(){}
 
     public static Singleton getInstance(){
         return Inner.INSTANCE;
     }
 
     private static class Inner{
         static final Singleton INSTANCE = new Singleton();
     }
}

饿汉式和懒汉式的对比

  1. 复杂度:饿汉式 实现简单;懒汉式 实现稍复杂
  2. 类的加载时机:饿汉式 在类加载的时候,对象已经实例化了,对象在内存中占用时间较长;懒汉式 需要的时候才去实例话对象,对象在内存中占用时间较短
  3. 线程安全:饿汉式线程安全,懒汉式 有一层判断,会引起线程不安全

开发中遇到的单例模式

  • Windows的Task Manager (任务管理器)就是很典型的单例模式

  • Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  • Application 也是单例的典型应用

  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只

    能有一个实例去操作,否则内容不好追加。

  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。