每天一个设计模式:单例模式 - Sanarous的博客

每天一个设计模式:单例模式

一、单例模式

在标准的23种设计模式种,单例设计模式在应用中是非常常见的,而我们在学习单例模式中,一定要考虑到和多线程结合起来时可能存在的各种问题以及其解决办法,这样我们才能写出一个在多线程环境下安全、正确的单例模式。

单例模式常见的有八种写法(更多时候分为六种,此处更加细分了):

  • 饿汉式(静态常量)
  • 饿汉式(静态代码块)
  • 懒汉式(线程不安全)
  • 懒汉式(线程安全,同步方法)
  • 懒汉式(线程安全,同步代码块)
  • 双重检查锁
  • 静态内部类
  • 枚举

二、单例模式的基本实现思路:

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

单例模式的实现主要通过以下步骤:

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

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

注意事项:单例模式在多线程的环境下必须小心使用,如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被创建了出来,从而违反了单例模式种实例唯一的原则,解决的办法显而易见是加锁。

三、单例模式的八种写法

1、饿汉式(静态常量)

1
2
3
4
5
6
7
8
9
10
public class singleton(){
private final static Singleton INSTANCE = new Singleton();

//注意构造方法必须私有
private Singleton(){}

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton{
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
public class Singleton{
private static Singleton instance;

private Singleton(){}

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton{
private static Singleton instance;

private Singleton(){}

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

这种方法是对上面的线程不安全的懒汉式的改进。

由于每次去获取实例的时候都会进入synchronized代码块而不管实例是否为null,而其实这个方法只需要执行一次实例化代码就可以,因此这样的开销非常大,所以不推荐使用。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton{
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
public class Singleton{
private static volatile Singleton singleton;

private Singleton(){}

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

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

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

7、静态内部类

1
2
3
4
5
6
7
8
9
10
11
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(){

}
}

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

如果这篇文章对您很有帮助,不妨
-------------    本文结束  感谢您的阅读    -------------
0%