之前去4399面试,面试官问我会不会单例模式,不知道怎么描述,于是我直接就在纸上写出来,结果好像在调用的地方写错了.实在不能忍,绝不能再出现第二次错误.
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。IO处理、数据库操作等这些需要占用重要的系统资源的操作都需要用到单例模式。
- 单例模式(Singleton)结构图
1.饿汉式单例模式:使用静态初始化的方式,在自己被加载时就将自己实例化。这是最简单的实现。
/**
*
* @author yifei
* 饿汉式单例模式
*
*/
public class Singleton1 {
private static Singleton1 singleton1 = new Singleton1();
private Singleton1() {
System.out.println("Singleton1: 饿汉式单例模式");
}
public static Singleton1 getInstance() {
return singleton1;
}
}
2.
懒汉式单例模式:在第一次被引用时,才会将自己实例化。
/**
*
* @author yifei
* 懒汉式单例模式
*
*/
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2() {
System.out.println("Singleton2: 懒汉式单例模式");
}
public static Singleton2 getInstance() {
if(singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
涉及到线程安全问题,可将代码修改为:
/**
*
* @author yifei
* 懒汉式单例模式
*
*/
public class Singleton2 {
private static Singleton2 singleton2;
private Singleton2() {
System.out.println("Singleton2: 懒汉式单例模式");
}
public synchronized static Singleton2 getInstance() {
if(singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}
一个线程必须等待另外一个线程创建完成后才能调用getInstance()方法。虽然解决了线程安全的问题,但是每个线程调用getInstance()时都要加锁。
3.静态内部类单例模式
/**
*
* @author yifei
* 静态内部类单例模式
*/
public class Singleton3 {
private static class SingletonHolder {
private static Singleton3 INSTANCE = new Singleton3();
}
private Singleton3() {
System.out.println("Singleton3: 静态内部类单例模式");
}
public static Singleton3 getInstance() {
return SingletonHolder.INSTANCE;
}
}
4.枚举式单例模式:不仅可以避免多线程同步问题,而且还能防止反序列化重新创建新的对象。但很少被运用。
/**
*
* @author yifei
* 枚举式单例模式
*/
public enum Singleton4 {
INSTANCE;
public void getInstance() {
System.out.println("Singleton4: 枚举式单例模式");
}
}
5.双重锁定单例模式(Double-Check-Locking):不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。当singleton为null并且同时有两个线程调用getInstance()方法时,它们将都可以通过第一重singleton == null 的判断。由于synchronized机制,这两个线程则只有一个进入,另一个在外排队等候,必须要其中的一个进入并出来后,另一个才能进入。而此时如果没有了第二重的singleton是否为null的判断,则第一个线程创建了实例,而第二个线程还是可以继续再创建新的实例,就没有达到单例的目的。
/**
*
* @author yifei
* 双重锁定单例模式(Double-check-Locking)
*/
public class Singleton5 {
private volatile static Singleton5 singleton5 = null;
private Singleton5() {
System.out.println("Singleton5: 双重锁定单例模式");
}
public static Singleton5 getInstance() {
if(singleton5 == null) {
synchronized (Singleton5.class) {
if(singleton5 == null) {
singleton5 = new Singleton5();
}
}
}
return singleton5;
}
}
(1)给sinleton分配内存
(2)调用Singleton的构造函数来初始化成员变量,形成实例
(3)将single同对象指向分配的内存空间
但是在JVM的即时编辑器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的。
volatile
(1)不会在多线程中存在复本,直接从内存读取。
(2)禁止指令重排序优化。
个人理解:
- 单例模式实质上就是禁止多次创建对象
- 在代码中的显示就是将构造方法私有化,创建一个public的方法用来创建对象,仅此一个接口
- 所以在调用的时候,创建出来的都是同一个对象,返回true.
Singleton1 s1 = Singleton1.getInstance();
Singleton1 s2 = Singleton1.getInstance();
System.out.println(s1 == s2);
面试的时候一般都不会涉及到线程安全问题
.