设计模式之单例模式

      在某些情况下,有些对象只需要一个就够了,即每个类只需要一个实例,简单来说单例模式的作用就是保证在整个应用程序的生命周期过程中,任何时刻,单例类的实例都只存在一个,单例模式确保某一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例,单例模式在类中保存了它唯一实例的全局,同时还提供了访问该唯一实例的全局访问点



一、使用场景


     资源共享情况下,避免资源操作时造成的性能损耗,在控制资源的情况下,方便资源之间相互通信,windows 的 Task Manager(任务管理器)就是典型的单例模式,还有我们项目中用到的日志打印,一般也采用单例模式实现,因为只能有一个实例去操作,否则内容不好追加,好了不说废话了,开始演绎


二、饿汉式

/**
 * 单例模式示例(饿汉式)
 */

public class SingletonTest {

    //定义私有构造方法
    private SingletonTest() {

    }

    // 将自身的实例对象设置为一个属性,并加上static和final修饰符
    private static final SingletonTest instance = new SingletonTest();

    // 静态方法返回该类的实例
    public static SingletonTest getInstance() {
        return instance;
    }
}

      这是单例模式实现之饿汉式,大家可以看到首先定义了一个构造函数并且私有化,这样就防止了使用 new 关键字来创建实例,饿汉式写起来比较简单,而且也不存在多线程同步问题,避免了 synchronized 所造成的性能问题,但缺点也很明显,当类 SingletonTest 被加载的时候,会初始化 static 的 instance,静态变量被创建并且分配内存空间,即使你可能还没有用到这个实例,但这以后 static 的 instance 便一直占据这这段内存,只有当类被销毁时这这段内存才会被释放,因此饿汉式相对会比较消费内存

三、懒汉式

/**
 * 单例模式示例(懒汉式)
 */

public class SingletonTest {

    //定义私有构造方法
    private SingletonTest() {

    }

    //定义一个SingletonTest类型的静态instance变量
    private static SingletonTest instance;

    // 静态方法返回该类的实例
    public static SingletonTest getInstance() {

        if (instance == null) {
            instance = new SingletonTest();
        }
        return instance;
    }
}

      在这里我们同样先定义一个私有的构造函数以防止 new 关键字来创建实例,需要注意的是我们在定义 SingletonTest 类型的静态变量 instance 时是没有 final 的,因为我们不需要初始化,写起来也比较简单,当 SingletonTest 类被加载时,静态变量 instance 被创建并且分配内存空间,当 getInstance 方法被第一次调用的时候初始化 instance 变量并分配内存,因此在特定情况下节约了内存,但我们在仔细去思考时会发现,这段代码是存在 bug 的,你因该也想到的,对,就是在多线程中是不安全的,我们来仔细分析一下如下的代码

public static SingletonTest getInstance() {

        if (instance == null) {
            instance = new SingletonTest();
        }
        return instance;
    }

      试想如果现在有两个线程同时在执行 getInstance 方法,第一个线程刚执行完第二行,还没有来得及执行第 3 行,这时候第二个线程也至执行到了第二行,它会发现 instance 还是空,于是也会去创建一个对象,这样就违背我们的初衷只创建一个实例对象,这里就会创建两个对象,那么我们来对代码进行修改如下

   public synchronized static SingletonTest getInstance() {

        if (instance == null) {
            instance = new SingletonTest();
        }
        return instance;
    }

      可以看到我们给方法加上了 synchronized 关键字,这样我们就加上了同步锁,保证只有一个线程来执行 getInstance 里面的方法,这样就解决了线程安全问题,不会再创建多个实例,但我们在仔细看一下这段代码,还是有问题的,当然这并不是 bug,只是性能上的问题,会影响运行效率,试想假如我们的 SingletonTest 实例已经创建,但我们还是要每次都必须去执行同步锁方法,这样显然是影响性能的,这里举个例子:大家可能都做过地铁,特别是身在北京,如果我们身上没有带行李的话是不需要通过安检通道的,可以直接进站乘车,只有我们携带了行李才需要走安检通道,但如果有一天地铁官方突然公布,不管我们是否携带行李都必须通过安检通道,这样显然是不合理的,这样人少的时候还好,当遇到上下班高峰期,是不是得被堵掉,同理到我们的代码里就是不管实例存在还是不存在,只要执行该方法都必须排队通过,这样很显然也是不太合理的,我们修改如下

public static SingletonTest getInstance() {

        if (instance == null) {
            synchronized (SingletonTest.class) {
                if (instance == null) {
                    instance = new SingletonTest();
                }
            }
        }
        return instance;
    }

      这样我们我们在调用 getInstance 方法时就不会受到影响,进入方法后我们再去判断实例是否为空,如果为空再去创建对象,这时候再加同步锁,对性能和效率会更好


四、静态内部类


public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

      这种方式和饿汉式采用的机制类似,但也有不同,两者都是采用了类装载的机制来保证初始化实例时只有一个线程,不同的地方在于饿汉方式是只要 Singleton 类被装载就会实例化,没有懒加载作用,而静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才会装载 SingletonInstance 类,从而完成 Singleton 的实例化,避免了线程不安全,延迟加载,效率高


五、枚举


public enum Singleton {
    INSTANCE;
    public void whateverMethod() {

    }
}

借助 JDK1.5 中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象


最后总结一句,单例模式就是保证一个类在应用中只有一个对象,并且向外提供一个访问它的公共方法,对单例模式的理解就说这些,如果有理解不到位的希望指出


  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值