摘要
在标准的23种设计模式种,单例设计模式在应用中是非常常见的,而我们在学习单例模式中,一定要考虑到和多线程结合起来时可能存在的各种问题以及其解决办法,这样我们才能写出一个在多线程环境下安全、正确的单例模式。
单例模式常见的写法:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查锁
- 静态内部类
- 枚举
实现思路
单例模式要求类能够有返回对象的一个引用(并且永远是同一个)和一个获得该实例的方法(必须是静态方法,往往使用getInstance()
这个方法)。单例模式的实现主要通过以下步骤:
- 将该类的构造方法定义为私有方法,这样其它的代码就无法通过调用该类的构造方法来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例;
- 在该类种提供一个静态方法,当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋值给该类保持的引用。
注意事项:单例模式在多线程的环境下必须小心使用,如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被创建了出来,从而违反了单例模式种实例唯一的原则,解决的办法显而易见是加锁。
八种写法
先简单介绍下什么是饿汉式,什么是懒汉式。顾名思义,饿汉式即需求欲望很大,无论怎么样,必须要有这个对象。而懒汉式——即无所谓,等到真正需要时再去创建对象。
饿汉式(静态常量)
1 | public class singleton(){ |
- 优点:写法简单,就是在类加载的时候完成实例化,避免了线程同步问题。
- 缺点:没有达到懒加载的效果,如果从始至终都未使用过这个实例,会造成内存的浪费。
饿汉式(静态代码块)
1 | public class Singleton{ |
- 这种方式跟第一种方式类似,静态代码块构造对象会在类加载的时候完成的,优缺点跟上面一样。
懒汉式(线程不安全)
1 | public class Singleton{ |
- 这种写法在单线程环境下可以使用,但是多线程环境下,由于同一时间可能会有多个线程判断到
instance
为null
,显然会产生多个实例。
懒汉式(线程安全,同步方法)
1 | public class Singleton{ |
- 这种方法是对上面的线程不安全的懒汉式的改进。由于每次去获取实例的时候都会进入
synchronized
代码块而不管实例是否为null,而其实这个方法只需要执行一次实例化代码就可以,因此这样的开销非常大,所以不推荐使用。
懒汉式(线程安全,同步代码块)
1 | public class Singleton{ |
- 并不能起到线程同步的作用,跟第三种方式遇到的情形一致。假如两个线程同时进入了
if(instance == null)
代码块,那么还是会产生多个实例,因此同样不推荐使用。
双重检查锁
1 | public class Singleton{ |
- 双重检查锁对于多线程开发者来说并不陌生,我们进行了两次
if(singleton == null)
判断,并通过将实例singleton设置为volatile
变量,这样可以实现变量的可见性并且禁止编译器指令重排序造成的其它问题。 - 优点:线程安全,延迟加载,效率较高。
静态内部类
1 | public class Singleton{ |
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要
Singleton
类被装载就会实例化,没有Lazy-Loading
(懒加载)的作用,而静态内部类方式在Singleton
类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance
方法,才会装载SingletonInstance
类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM
帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。优点:避免了线程不安全,延迟加载,效率高。
枚举
1 | public enum Singleton{ |
- 借助
JDK1.5
中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5
中才添加,所以在实际项目开发中,使用枚举实现单例模式很少出现。