设计模式-单例模式 实现的基本思路 单例模式的写法 使用场景 备注

特点

  1. 只有一个实例
  2. 必须自己创建自己的唯一实例
  3. 必须给所以其他对象提供这一个实例

实现方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

实现的基本思路

单例模式要求类能够有返回对象的一个引用(并且永远是同一个)和一个获得该实例的方法(必须是静态方法,往往使用getInstance()这个方法)
主要通过以下步骤:

(1)将该类的构造方法定义为私有方法,这样其它的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;

(2)在该类种提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋值给该类保持的引用。

注意事项:单例模式在多线程的环境下必须小心使用,如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被创建了出来,从而违反了单例模式种实例唯一的原则,解决这个问题办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)

单例模式的写法

1、饿汉式(静态常量)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class (){

private Singleton(){}

//将自身的的实例对象设置为一个属性并加上final 和static
private final static Singleton INSTANCE = new Singleton();


//静态方法返回该类的实例
public static Singleton getInstance(){
return INSTANCE;
}
}

优点:写法简单,就是在类加载的时候完成实例化,避免了线程同步问题。

缺点:没有达到懒加载的效果,如果从始至终都未使用过这个实例,会造成内存的浪费。

2、饿汉式(静态代码块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{
// 定义一个Singleton类型的变量(不初始化,注意这里没有使用final关键字)
private static Singleton instance;

static{
instance = new Singleton();
}
//定义一个私有的构造方法
private Singleton(){}
// 定义一个静态的方法
public static Singleton getInstance(){
return instance;
}
}

这种方式跟第一种方式类似,都是在类加载的时候完成的,只不过将实例化的过程放在了静态代码块种,优缺点跟上面一样。

3、懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton{
// 定义一个Singleton类型的变量(不初始化,注意这里没有使用final关键字)
private static Singleton instance;


//定义一个私有的构造方法(防止通过 new Singleton去实例化)
private Singleton(){}
// 定义一个静态的方法(调用时再初始化Singleton,但是多线程访问时,可能造成重复初始化问题)
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}

这种写法在单线程环境下可以使用,但是多线程环境下显然会产生多个实例。

优点:写起来比较简单,当类Singleton被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存;
缺点:并发环境下很可能出现多个Singleton实例。

4、懒汉式(线程安全,同步方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton{
// 定义一个Singleton类型的变量(不初始化,注意这里没有使用final关键字)
private static Singleton instance;

private Singleton(){}
定义一个静态的方法(调用时再初始化Singleton,使用synchronized 避免多线程访问时,可能造成重的复初始化问题)
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}

由于每次去获取实例的时候都会进入synchronized代码块而不管实例是否为null,而其实这个方法只需要执行一次实例化代码就可以,因此这样的开销非常大,所以不推荐使用。
优点是:使用synchronized关键字避免多线程访问时,出现多个Singleton实例。
缺点是:同步方法频繁调用时,效率略低。

5、懒汉式(线程安全,同步代码块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Singleton{
// 定义一个Singleton类型的变量(不初始化,注意这里没有使用final关键字)
private static Singleton instance;

//私有的构造方法
private Singleton(){}

public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}

这种同步并不能起到线程同步的作用,跟第三种方式遇到的情形一致。假如两个线程同时进入了if(instance == null)代码块,第一个线程还未进行处理,另外一个线程也通过了判断,这样会产生多个实例,因此同样不推荐使用。

6、双重检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton{
/定义一个静态私有变量(不进行初始化,使用volatile保证了多线程访问时singleton变量的可见性,避免了singleton初始化时其他变量属性还没赋值完时,被另外线程调用)
private static volatile Singleton singleton;
//私有的构造方法
private Singleton(){}

//定义一个静态的构造方法,返回该类型的实例
public static Singleton getInstance(){
// 对象实例化时与否判断(不使用同步代码块,singleton不等于null时,直接返回对象,提高运行效率)
if(singleton == null){
//同步代码块(对象未初始化时,使用同步代码块,保证多线程访问时对象在第一次创建后,不再重复被创建)
synchronized(Singleton.class){
//未初始化,进行初始化singleton变量
if(singleton == null){
singleton = new Singleton():
}
}
}
return singleton;
}
}

双重检查锁对于多线程开发者来说并不陌生,我们进行了两次if(singleton == null)判断,并通过将实例singleton设置为volatile变量,这样可以实现变量的可见性并且禁止编译器指令重排序造成的其它问题。

优点:线程安全,延迟加载,效率较高。

7、静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
//定义私有的构造方法
private Singleton(){}

private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}

这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading(懒加载)的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

优点:避免了线程不安全,延迟加载,效率高。

8、枚举

1
2
3
4
5
6
public enum Singleton{
INSTANCE;
public void whateverMethod(){

}
}

优点:系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

缺点:当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new,可能会给其他开发人员造成困扰,特别是看不到源码的时候

借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,使用枚举实现单例模式很少出现。

使用场景

  1. 要求生产唯一序列号。
  2. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  3. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
  4. 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
  5. 工具类对象;

    备注

    Spring的controller就是典型的单例模式