单例模式是设计模式中最常见的一个设计模式,属于创建类模式,它保证一个类在在程序运行过程中只有一个实例化对象,既节省了资源,同时又保护了系统临界资源。比如操作系统对外设的管理,打印机只有一个,一次在任何时刻只能有一个打印机的实例。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
接着,我们以打印机为例通过代码来展示不同的单例实现方式。
1. 懒汉式
所谓懒汉式,这涉及到了一个常见的名词“懒加载”。所谓懒加载,就是指在一个类的实例就只有在使用的时候才会创建,而不用的时候并不会去加载。通俗来说,就是它太懒了。这样的一个好处是避免了资源的占用,相应地提高了资源的利用率。代码如下:
public class Printer{
private static Printer instance;
private Printer(){}
public static Printer getInstance(){
if (instance == null) {
instance = new Printer();
}
return instance;
}
}
但是上面的代码存在一个问题,那就是不支持多线程。试想,同时多个线程懒加载同时调用getInstance方法,那么会产生三个Printer对象,没有真正实现单例模式。为了弥补这个问题,我们通过给getInstance方法加上synchronized关键字来支持多线程。
2. 饿汉式
所谓饿汉式,通俗点讲就是指它太饿了,所以它在一开始的时候就已经实例化,区别于懒汉式的是给对象实例化的时间不同。这个方式是最常用的单例模式,但是缺点是由于一开始就实例化了对象,造成了资源的浪费。代码实现如下:
public class Printer{
private static Printer instance = new Printer();
private Printer(){}
public static Printer getInstance(){
return instance;
}
}
3. 双检锁
通过对比之前的两个实现方式,明显发现采用了懒加载的懒汉式的资源利用效率高,但是,懒汉式的效率并不是如我们想象的那样高。每次当有线程去调用getInstance方法时,都会由于synchronized关键字的缘故而不得不等待正在访问该方法的进程。而事实上这是不必要的。假设该类已经实例化,线程A和线程B来调用getInstance方法,由于并不需要初始化,所以synchronized关键字在此时拉低了程序执行执行的效率,由此我们提出了双检锁的方法来实现。代码如下:
public class Printer{
private static Printer instance ;
private Printer(){}
public static Printer getInstance(){
if (instance == null) {
synchronized(printer.class){
if (instance == null) {
instance = new Printer();
}
}
}
return instance;
}
}
但是,此时的代码还是有问题的。 由于本片博文是用来介绍单例模式的,详细的优化就不介绍了,感兴趣的参考这篇博文,里面详细的讲解了双检锁法的处理优化。
https://www.cnblogs.com/dquery/p/7077154.html
4. 登记式
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。代码如下:
public class Printer{
private static class PrinterHolder{
private static Printer Instance = new Printer();
}
private Printer(){}
public static final Printer getInstance(){
return PrinterHolder.Instance;
}
}
5. 枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。
public enum Printer {
INSTANCE;
public void whateverMethod() {
}
}