设计模式详细解析(1)单例模式 干货满满

单例模式

这个教程非常详细地阐述了单例模式的所有实现,这些实现并不是孤立的,而是有一定联系的,下面就来探索单例模式的奥秘吧!

引言

在面向对象编程中,我们需要使用很多对象来完成指定任务。使用这些对象的前提是用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;
    }
}

优点:

  1. “new Singleton()” 出现在Singleton类的静态成员域中,这表明Singleton对象的创建在类加载阶段完成,这就避免了线程同步的问题(这里比较难懂,解释一下,当外界使用该对象时,时,是通过Singleton.getInstance()这个静态方法来获取的,JVM加载这句话时,会对Singleton类进行加载,而加载的地方是方法区,方法区是所有线程共享的地方,因此,Singleton类发生的事对所有线程是可见的,避免了线程同步问题)
  2. 写法比较简单

缺点:

  1. 做不到懒加载,也就是做不到运行时加载。因为对象在类加载时就已经创建好了,但有时候使用了某个类,但不一定会用到这个类的静态成员变量,具体点就是,当使用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。
换而言之,不完美的算法不一定没用,根据不同的场景选择不同的算法才是上上策。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值