单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其(唯一)的对象的方式,可以直接访问,不需要实例化该类的对象(不需要每次new)。
主要来解决一个全局使用的类频繁的创建和销毁,当你想控制实例数目,节约系统资源的时候。
判断系统是否有了这个单例,如果有则返回,没有则创建。在实现单例的共同特点就是构造函数是私有的。
把普通模式和单例模式理解为一根火柴(普通)和一个打火机(单例)的区别,火柴的燃烧要燃尽自己为代价,而打火机可以持续的供给,重复的销毁和创建,如果类占用内存空间较大,耗时也长,如果频繁的创建会产生不必要的消耗的时候,我们就可以建一个单例类供我们使用就好了。
一个班级只能有一个班主任,只有当这个班主任走了,才能有新的班主任过来。
一个电脑有两台打印机设备,在输出的时候就不能两台打印机同时打印一个文件。
在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁(比如管理学院首页页面缓存)。
避免对资源的多重占用
没有接口,不能继承,与单一职责原则冲突,只关心内部逻辑,而不管外面怎么实例化。
getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
接下来就来一个个介绍........
直接创建对象,不存在线程安全问题。比较容易实现,类似的思想的很多。
就好比女人最喜欢的双十一,你一下把你家里不管(需要)还是(不需要)的都买了,认为以后会用的到的也买了。这样就会导致占用内存了。
也可以使用枚举式(最简洁)和 静态代码块饿汉式(适合复杂实例化)
//饿汉式 不管需不需要这个对象都创建 public class Singleton { private static Singleton singleton=new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; } }
从上面我们可以了解到饿汉式早早的就把对象创建好了,在执行一些逻辑的时候不存在线程安全问题,但是懒汉式会延迟创建对象可能会导致线程安全问题
//不支持多线程,适用单线程。 //懒汉式:构造器私有化 //静态变量保存实例 //提供一个静态方法,获取这个实例对象 public class Singleton { private static Singleton singleton; private Singleton(){} public static Singleton getInstance() { if (singleton==null) { singleton=new Singleton(); } return singleton; } }
对于这种单线程没问题,但是如果如果两个或多个线程来同时访问instance如果都为null同时申请的时候会对系统数据造成错误
既然上面那种不能实现多线称,那要这个单例干嘛?所以既然不允许多个线程同时访问,我们就给他加个锁(synchronized)不就可以了。
//支持多线程。 //懒汉式:构造器私有化 //静态变量保存实例 //提供一个静态方法,获取这个实例对象 public class Singleton { private static Singleton singleton; private Singleton(){} public static synchronized Singleton getInstance() { if (singleton==null) { singleton=new Singleton(); } return singleton; } }
但是这样有啥问题呢?就是我获取这个单例对象的时候,被上锁了,你气不气?我们就是在创建这个单例时候多线程可能出现点问题,咱么获取的时候不存在线程安全问题啊。。你为啥获取也要锁?? 就好比我们去食堂吃饭,吃饭是不是要一个一个的排队去打饭,但是不妨碍你看着里面的菜流口水《干饭人》
相对来说实现比较复杂,这种方式采用了双锁(volatile 轻量级锁)(synchronized 重量级锁)机制,安全且在多线程情况下能保持高性能。后面我们就顺便把这两种锁扩展了。
getInstance() 的性能对应用程序很关键。
public class Singleton { private volatile static Singleton singleton; private Singleton(){} public static Singleton getInstance() { if (singleton==null) { synchronized (Singleton.class) { if (singleton==null) { singleton=new Singleton(); } } } return singleton; } }
对于上述存在的问题,我们只要双重判定就行了
或许有人会问了两个null,为什么要两个?
singleton
啦!直接不操作就行了。这样就相对完美了
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。利用了 classloader 机制来保证初始化 singleton
时只有一个线程
静态内部类是个好方式。主要是静态内部类和外部类不是一起加载的,并且你去调用它的时候他就会初始化,并且类加载是线程安全的,这个不需要考虑线程安全问题。当加载完之后你就可以直接拿啦。这样也能达到延迟加载的作用。
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE=new Singleton(); } private Singleton(){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。
public enum Singleton { INSTANCE; public void whateverMethod() {} }
这里举个例子加深一下印象:大家平时都玩过游戏和下过游戏吧,就拿下游戏来举例,拿(我的世界)来举例吧。饿汉式就是一次性把所有的模块全部下完,不管你后续玩不玩都下完了,这样子等待和成本太大,懒汉式就是你要玩什么模块就下什么模块,避免过多的消耗。
看完上面是不是对这两个锁有了一些的认识,接下来我们就来扩展一下他们的区别。
如果哪里写的不对的地方,还望指教,感谢支持。