单例模式是开发中经常用到的一个设计模式,也是最容易理解的设计模式,它的主要目的是限制类的实例化,保证该类在Java虚拟机中只有一个实例存在。虽然单例模式很简单,但是实现单例模式的方式有很多,而且不同的实现方式有各自的优缺点,下面就介绍一些常见的实现单例模式的方法。
饿汉模式在该类加载时就创建实例,优点是实现非常简单,但是如果该单例前期不用的话,提前实例化就占用了资源,特别需要注意如果单例资源比较重的话,应该考虑到这一点,另外一个缺点是这种方式没有做异常处理。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return instance;
}
}
这种方式和饿汉模式一样,也是在该类加载时就创建实例,也会浪费资源,但是它比饿汉模式好的是有异常处理。
1public class Singleton {
2
3 private static Singleton instance;
4
5 private Singleton() { }
6
7 static {
8 try {
9 instance = new Singleton();
10 } catch (Exception e) {
11 //handle exception
12 }
13 }
14
15 public static Singleton getInstance() { return instance; }
16}
懒汉模式只会在需要使用单例时才会实例化,避免了浪费资源的问题。
1public class Singleton {
2
3 private static Singleton instance;
4
5 private Singleton() { }
6
7 public static Singleton getInstance() {
8 if (instance == null) {
9 instance = new Singleton();
10 }
11 return instance;
12 }
13}
懒汉模式虽然避免了资源浪费,但是它存在线程安全问题,在单线程的环境中使用没问题,但是多线程环境下,可能会出现多个实例的情况。
要保证线程安全,我们可以给获取单例的方法加上锁。
1public class Singleton {
2
3 private static Singleton instance;
4
5 private Singleton() { }
6
7 public static synchronized Singleton getInstance() {
8 if (instance == null) {
9 instance = new Singleton();
10 }
11 return instance;
12 }
13}
但是这样又带来了一个小问题,就是性能问题,因为每次获取单例都需要获取同步锁,而我们其实只有在初始化单例的时候需要保证同步,为了解决这个问题,就出现了双重检查锁的实现方式。
这样就只有在初始化单例的时候才需要获取同步锁来保证线程,在初始化完成之后,就不需要再获取同步锁,减少了对性能的影响。
1public class Singleton {
2
3 private static volatile Singleton instance;
4
5 private Singleton() { }
6
7 public static synchronized Singleton getInstance() {
8 if (instance == null) {
9 synchronized (Singleton.class) {
10 if (instance == null) {
11 instance = new Singleton();
12 }
13 }
14 }
15 return instance;
16 }
17}
首先我们要知道,静态内部类默认是不会跟随外部类一起加载的,只有在使用到了静态内部类时,才会加载它,而且这个过程是线程安全的。
1public class Singleton {
2
3 private Singleton() { }
4
5 private static final class InstanceHolder {
6 private static final Singleton instance = new Singleton();
7 }
8
9 public static synchronized Singleton getInstance() {
10 return InstanceHolder.instance;
11 }
12}
这样就只有在调用到getInstance时,才会加载InstanceHolder类,然后初始化单例。
枚举类的构造方法是私有的,而且Java虚拟机保证枚举类的值只会初始化一次,使用非常简单,但是它的缺点是无法懒加载,只要加载了枚举类就会实例化单例。
public enum Singleton {
INSTANCE;
public Singleton getInstance() {
return INSTANCE;
}
}