概念
在整个系统中,某个类只能存在一个实例对象,并且只提供一个获取其实例对象的方法。
实现方式的思考
- 为了确保只有一个实例对象,就需要调用者不能自己去创建该对象,而是该类提供一个创建对象的方法
- 该类需要
static
关键字修饰创建出来的对象,才能确保在内存中只有一个对象 - 所以该类的构造器只能是
private
- 提供一个创建对象的方法,让调用者去调用,因为构造器的权限修饰符是
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();
}
}
饿汉式和懒汉式的对比
- 复杂度:
饿汉式
实现简单;懒汉式
实现稍复杂 - 类的加载时机:
饿汉式
在类加载的时候,对象已经实例化了,对象在内存中占用时间较长;懒汉式
需要的时候才去实例话对象,对象在内存中占用时间较短 - 线程安全:
饿汉式
线程安全,懒汉式
有一层判断,会引起线程不安全
开发中遇到的单例模式
-
Windows的Task Manager (任务管理器)就是很典型的单例模式
-
Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
-
Application 也是单例的典型应用
-
应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只
能有一个实例去操作,否则内容不好追加。
-
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。