单例模式的实现
-
java单例模式的实现分为五种:
- 饿汉模式(线程安全)
- 懒汉模式(线程不安全,但是可以修改,不建议使用,延迟加载)
- DCL双检查锁机制(线程安全,延迟加载)
- 静态内部类实现(线程安全,但是遇见序列化对象时会出现多实例情况,延迟加载)
- 枚举类实现(线程安全,不会出现序列化带来的问题,延迟加载)
1. 饿汉模式
- 优点:单例在类被加载的时候被创建,线程安全,可以用于多线程环境
- 缺点:如果单例未被使用,那么同样会被创建,会造成内存浪费
/**
* 饿汉模式
* 在调用前就已经创建,可能会浪费内存
*/
public class Singleton1 {
//设置为私有 防止其他对象调用
private Singleton1 (){};
private static Singleton1 instance = new Singleton1();
public static Singleton1 getInstance(){
return instance;
}
}
2. 懒汉模式
- 优点:在需要被使用的时候才被创建,不会造成资源浪费
- 缺点:线程不安全,getInstance方法没有同步,在多线程的情况下会造成创建的实例不同
- 线程安全的修改方法:
- 使用synchronized关键字同步getInstance方法
- 使用synchronized关键字同步代码块
但是这两种修改方法会使代码的效率变的很低,所以不推荐使用
/**
* 懒汉模式 非线程安全 通过添加synchronized关键字来同步getInstance方法
* 解决懒汉模式:同步getInstance方法 同步代码块
*/
public class Singleton2 {
private Singleton2(){};
private static Singleton2 instance = null;
public static Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
}
3. DCL双检查锁机制
-
线程安全,延时加载
-
双检查锁机制就是进行第二次检查,也就是代码中两次判断null的操作,但是为什么要这样呢?
-
特殊情况的出现:
- 线程ThreadA,ThreadB,如果threadA执行到了第一个if条件判断,instance = null;ThreadB也执行到了if条件判断instance = null,所以A和B会依次执行同步代码块里的代码。为了避免创建两个实例,因此又在同步代码块里添加了if条件进行二重检验。
-
但是由于会出现指令重排序的问题,这样还会导致线程不安全的情况,所以添加了volatile 关键字
-
volatile 关键字作用:
- 禁止指令重排序
- 同步内存,每次修改值之后必须立即刷新到主存,线程取的时候也是从主存取
/**
- 双重锁机制
- 要注意判断两次null
*/
public class Singleton3 {
private Singleton3(){};
public volatile static Singleton3 instance = null;
public static Singleton3 getInstance(){
if(instance == null){
synchronized (Singleton3.class){
if(instance == null){
instance = new Singleton3();
}
}
}
return instance;
}
}
4. 静态内部类实现
- 线程安全,延时加载
- 由于内部类是隐藏的,所以只要不使用内部类,那么JVM就不会将其加载进来,这样就很好的实现了延迟加载
- 缺点:遇到序列化对象的时候,如果还使用默认的运行方式,就会出现多实例的情况,解决方法是在反序列化中使用readResolve方法
- 为什么是线程安全的:虚拟机会保证一个类的构造器方法在多线程环境中被正确地加载,同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的
public class Singleton4 {
private Singleton4(){};
private static class SingleHolder{
private static Singleton4 instance = new Singleton4();
}
public static Singleton4 getInstance(){
return SingleHolder.instance;
}
}
5. 枚举类实现
- 由于很不常见,所以了解即可
public enum ExSingleton {
INSTANCE;
public void someMethod() {
}
}
6.总结
-
总的来说双重锁机制和静态内部类机制是推荐使用的,双重锁机制是一般多线程情况下的实现选择
-
在面试的时候可以选择写静态内部类实现和DCL双重锁机制实现,但是书写的过程都是类似的:
-
DCL双重锁:
- 首先声明私有的构造函数
- 再定义私有的静态实例,并且使用 volatile 关键字修饰
- 实现getInstance静态方法,先进行第一次检查,之后实现synchronized同步代码块,在其中实现第二次检查,进行instance赋值
- 最后return instance
-
静态内部类:
- 首先声明私有构造函数
- 再定义静态内部类,其中定义私有静态实例并创建
- 实现getInstance静态方法,返回静态内部类中的实例
-