单例模式
这个教程非常详细地阐述了单例模式的所有实现,这些实现并不是孤立的,而是有一定联系的,下面就来探索单例模式的奥秘吧!
引言
在面向对象编程中,我们需要使用很多对象来完成指定任务。使用这些对象的前提是用new关键字新建一个对象。然而,有些对象我们只需要一个,新建完毕后反复使用,而不是每当需要它的时候又重新new一个,比如,缓存对象、线程池对象、打印机对象等等。显然这里用到了单例模式。
宗旨
构造方法私有化,对外提供唯一对象
根据宗旨可以毫不费劲的写出单例模式的具体实现
8种单例模式的实现
这8种实现方式都有它们的优劣性,根据不同的情况使用不同的实现方式。
最下面会有对这8中实现的总结,可以选择先读总结再反过来看这。
1.饿汉式(静态变量)
//饿汉式(静态变量)
class Singleton {
//1.构造器私有化
private Singleton() {}
//2.唯一本类实例
private final static Singleton INSTANCE = new Singleton();
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
- “new Singleton()” 出现在Singleton类的静态成员域中,这表明Singleton对象的创建在类加载阶段完成,这就避免了线程同步的问题(这里比较难懂,解释一下,当外界使用该对象时,时,是通过Singleton.getInstance()这个静态方法来获取的,JVM加载这句话时,会对Singleton类进行加载,而加载的地方是方法区,方法区是所有线程共享的地方,因此,Singleton类发生的事对所有线程是可见的,避免了线程同步问题)
- 写法比较简单
缺点:
- 做不到懒加载,也就是做不到运行时加载。因为对象在类加载时就已经创建好了,但有时候使用了某个类,但不一定会用到这个类的静态成员变量,具体点就是,当使用Singleton类时,不一定会使用到INSTANCE这个变量。这就造成了内存资源的浪费。(如果这个对象很大,内存浪费会很严重)
2.饿汉式(静态代码块)
//饿汉式(静态代码块)
class Singleton {
//1.构造器私有化
private Singleton() {}
//2.唯一本类实例,在静态代码块中new出
private final static Singleton INSTANCE;
//3.静态代码块
static {
INSTANCE = new Singleton();
}
//3.提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return INSTANCE;
}
}
优缺点:与第一种类似,在此不再累赘
3.懒汉式(线程不安全)
class Singleton {
private static Singleton instance;
private Singleton(){}
//提供外部静态方法,当外部调用该方法时,才创建instance对象
//懒汉式(但线程不安全)
//为什么?多个线程可能同时进入了if,造成对象不唯一
public static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
优点:实现了懒加载,即当需要该对象时,才会创建对象(第一次调用getInstance()方法时,会创建对象)
缺点:存在线程安全问题,getInstance()方法中,假如有多个线程同时进入了if语句内部,就会创建多个对象,造成对象不唯一。(多线程知识,1号线程执行完判断语句后正要执行if语句内部时,2号线程突然抢占了cpu,显然,它也可以进入if语句内部,毕竟此时instance对象还是null嘛)
4.懒汉式(线程安全)
class Singleton {
private static Singleton instance;
private Singleton(){}
//提供外部静态方法,当外部调用该方法时,才创建instance对象
//懒汉式(线程安全)
public synchronized static Singleton getInstance() {
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
优点:用synchronized关键字修饰getInstance()方法后,该方法就是线程安全的,只有一个线程可以进入该方法内部。
缺点:这个确定也是同步方法的缺点,某些情况下会造成时间的浪费,当有很多线程时,线程会被同步方法挡在外头,排队进入。
针对这个问题,衍生出第5种实现
5.懒汉式(效率高,但线程不安全)
class Singleton {
private static Singleton instance;
private Singleton(){
}
//解决效率低的问题,但是线程安全的问题有暴露了(造成对象不唯一)
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
优点:效率高,线程不需要逐一排队地进入getInstance()。
缺点:线程不安全,与第3种实现类似,多个线程可能同时通过if条件而进入到if语句体。
相信大家看到这会不耐烦,我当时也是一样,这和第3种实现有啥区别啊,在原地踏步?
但是经过仔细观察发现,第3第5虽然是完全一样的,但是他们的产生原因却不一样:第3种实现是为了实现懒加载,而第5种实现是为了减少时间耗时。
这说明什么?看似原地踏步,其实可能已经在前进了!
(是不是和最近很火的想见你很贴切呢,哈哈哈哈)
有了第5种实现,下面开始的实现都是比较完美的实现了,因此没什么缺点可言
6.双重检查
class Singleton {
//volatile:jvm提供的轻量级同步机制
//防止jvm对instance的初始化操作进行重排序
private static volatile Singleton instance;
private Singleton(){}
//双重检查(解决线程安全问题和内存资源浪费问题)
//第一个if保证对象唯一
//第二个if保证线程安全
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){ //只有一个线程可以进来这里,同时入了第一个if的线程在这里需要排队
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
优点:几乎解决了上述提到的所有问题,而且还解决前面所忽视的问题——指令重排序。volatile关键字是JVM提供的轻量级线程同步机制,它可以放置非原子操作在多线程环境下的重排序问题。这里的 instance = new Singleton(); 就不是一个原子性操作,在JVM角度,这个语句可以看作由3个操作共同完成的,如果不加volatile关键字来修饰instance变量,这三个操作可能会因为jvm内部原因而重排序,从而操作线程安全问题
缺点:代码比较复杂(憋出来的缺点…)
7.静态内部类形式
class Singleton { //静态内部类形式
private Singleton(){}
//静态内部类成员里面装着对象的初始化的操作
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
说明:静态类内部类中的内容不会在类加载的时候被加载,而是是调用getInstance()方法时才执行静态内部类里面的语句,从而实现懒加载。
优点:充分利用Java中静态内部类的特性,避免了线程安全问题,也实现了懒加载,且比普通懒加载效率更高。
8.枚举类方式
enum Singleton { //枚举实现单例模式
INSTANCE;
}
优点:利用枚举类的特性,解决了上述提到的问题,且代码十分简短。
这是Effective Java书中提倡的方式哦
总结
1、2是饿汉式,所谓饿汉式就是在类加载时就创建好了单例对象。
3、4、5是懒汉式,所谓懒汉式就是当对象需要被使用时才去创建。
6、7、8都比较完美的实现方式,也是比较推荐的实现方式
当然,不是说前5中不完美的方式就没用,大家可以去看看Runtime类,它是Java提供的系统类,容易发现,它用了单例模式中的饿汉式来实现。
再举个例子,关于死锁的解决算法又很多,其中有一种不完美,甚至有些荒谬的方法——鸵鸟算法:遇到死锁时,对系统进行手动干预——重启。这个算法正在被三大操作系统所使用,分别是Windows、Linux、Unix。
换而言之,不完美的算法不一定没用,根据不同的场景选择不同的算法才是上上策。