单例设计模式(singleton)最常用、最简单的设计模式。单例模式的目的是保证在整个应用中某一个类有且只有一个实例(一个类在堆内存只存在一个对象)。怎么样让一个类一个类有且只有一个实例呢?最核心的就是一句话就是构造方法私有化。单例模式的编写有很多种写法。比如饿汉式、懒汉式、双重加锁机制静态内部类、枚举。
饿汉式,从名字上理解像是有个人很容易饿,所以他每次不管自己饿不饿,没事就提前把要吃的东西先准备出来,也就是 “比较勤”,所以实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。
[1] 构造方法私有化,防止外界通过构造器创建新的工具类对象;
[2] 必须在该类中,自己先创建出一个对象;
[3] 向外暴露一个公共的静态方法用于返回自身的对象;
// 单例模式(饿汉式)
public class Singleton1 {
// [1]构造方法私有化,防止外界通过构造器创建新的工具类对象
private Singleton1() {
}
// [2] 必须在该类中,自己先创建出一个对象(这行代码在类加载的时候就执行了)
private static Singleton1 instance = new Singleton1();
// [3] 向外暴露一个公共的静态方法用于返回自身的对象
public static Singleton1 getSingleton() {
return instance;
}
}
注意:我们知道,类加载的方式是按需加载,且加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
好处:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
坏处:在类装载的时候就完成实例化,没有达到Lazy Loading(懒加载)的效果。 如果从始至终从未使用过这个实例,则会造成内存的浪费。
懒汉式,顾名思义就像一个人比较懒,平时不爱动,事情火烧眉毛了才迫不得已去做,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。
[1] 构造方法私有化,防止外界通过构造器创建新的工具类对象
[2] 事先创建好当前类的一个私有静态对象
[3] 向外暴露一个公共的静态方法用于返回自身的对象
// 单例模式(懒汉式)
public class Singleton2 {
// [1]私有化构造方法。
private Singleton2() {
}
// [2] 事先创建好当前类的一个私有静态对象
private static Singleton2 instance = null;
// [3] 向外暴露一个公共的静态方法用于返回自身的对象
public static Singleton2 getInstance() {
// 被动创建,在真正需要使用时才去创建
if (null == instance) {
instance= new Singleton2();
}
return instance;
}
}
注意:我们从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
优缺点:这种写法起到了Lazy Loading(懒加载)的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
为了解决上面的问题,最简单的方法是将整个 getInstance() 方法设为同步(synchronized)。
// 单例模式(懒汉式)
public class Singleton2 {
// [1]私有化构造方法。
private Singleton2() {
}
// [2] 事先创建好当前类的一个私有静态对象
private static Singleton2 instance = null;
// [3] 向外暴露一个公共的静态方法用于返回自身的对象
public static synchronized Singleton2 getInstance() {
// 被动创建,在真正需要使用时才去创建
if (null == instance) {
instance= new Singleton2();
}
return instance;
}
}
注意:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时,也就是if条件判断里面的内容。这就引出了双重检验锁。
双重检验锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
// 单例模式(懒汉式)
public class Singleton3 {
private static Singleton3 instance;
private Singleton3 (){}
public static Singleton3 getSingleton() {
if (instance == null) {
synchronized (Singleton3.class) {
if (instance == null) {
instance = new Singleton3();
}
}
}
return instance;
}
}
public class Singleton4 {
private static class SingletonHolder {
private static final Singleton4 INSTANCE = new Singleton4();
}
private Singleton4 (){}
public static final Singleton4 getInstance() {
return SingletonHolder.INSTANCE;
}
}
注意:这种写法仍然使用JVM本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
public enum EasySingleton{
// 定义枚举常量
INSTANCE;
}
使用:我们可以通过EasySingleton.INSTANCE.工具方法() 的方式来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象。
1)网站的计数器,一般也是采用单例模式实现,否则难以同步。
2)应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
3)多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制
4)Windows的(任务管理器)就是很典型的单例模式,他不能打开俩个
5)windows的(回收站)也是典型的单例应用。在整个系统运行过程中,回收站只维护一个实例。