定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
单例模式的使用场景
在一个系统中,要求一个类有且仅有一个对象,它的具体使用场景如下:
• 整个项目需要一个共享访问点或共享数据。
• 创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源。
• 工具类对象。
单例模式的几种写法
1.饿汉式
//java
public class Singleton {
private static Singleton instence = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instence;
}
}
这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这种方式基于类加载 机制,避免了多线程的同步问题。在类加载的时候就完成实例化,没有达到懒加载的效果。如果从始至终 未使用过这个实例,则会造成内存的浪费。
//kotlin
object Singleton {}
在Kotlin中类没有静态方法。如果你需要写一个可以无需用一个类的实例来调用,但需要访问类内部的函数(例如,工厂方法,单例等),你可以把该类声明为一个对象。该对象与其他语言的静态成员是类似的。
2.懒汉式(线程不安全)
public class SingletonJava {
public static SingletonJava instence;
private SingletonJava() {
}
public static SingletonJava getInstance() {
if (instence == null) {
instence = new SingletonJava();
}
return instence;
}
}
懒汉模式声明了一个静态对象,在用户第一次调用时初始化。这虽然节约了资源,但第一次加载时需 要实例化,反应稍慢一些,而且在多线程时不能正常工作。
class Singleton private constructor(){
companion object{
private var instence : Singleton ?= null
get() {
if (field == null){
field = Singleton()
}
return field
}
fun get() : Singleton{
return instence!!
}
}
}
3.懒汉模式(线程安全)
public class SingletonJava {
public static SingletonJava instence;
private SingletonJava() {
}
public static synchronized SingletonJava getInstance() {
if (instence == null) {
instence = new SingletonJava();
}
return instence;
}
}
class SingletonKotlin private constructor() {
companion object{
private var instence : SingletonKotlin ?= null
get() {
if (field == null){
field = SingletonKotlin()
}
return field
}
@Synchronized
fun get() : SingletonKotlin{
return instence!!
}
}
}
这种写法能够在多线程中很好地工作,但是每次调用getInstance方法时都需要进行同步。这会造成不必 要的同步开销,而且大部分时候我们是用不到同步的。所以,不建议用这种模式。
4.双重检查模式(DCL)
public class SingletonJava {
private volatile static SingletonJava instence;
private SingletonJava(){
}
public static SingletonJava getInstance(){
if (instence == null){
synchronized (SingletonJava.class){
if (instence==null){
instence = new SingletonJava();
}
}
}
return instence;
}
}
class SingletonDclKotlin private constructor(){
companion object{
val instances : SingletonDclKotlin by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingletonDclKotlin()
}
}
}
这种写法在getSingleton方法中对Singleton进行了两次判空:第一次是为了不必要的同步,第二次是在 Singleton等于null的情况下才创建实例。在这里使用volatile会或多或少地影响性能,但考虑到程序的正确 性,牺牲这点性能还是值得的。DCL的优点是资源利用率高。第一次执行getInstance时单例对象才被实例 化,效率高。其缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷。DCL虽然在一定程 度上解决了资源的消耗和多余的同步、线程安全等问题,但其还是在某些情况会出现失效的问题,也就是 DCL失效。这里建议用静态内部类单例模式来替代DCL。
Lazy是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
5.静态内部类单例模式
public class SingletonJava {
private SingletonJava(){
}
private static class SingletonJavaHolder{
private static final SingletonJava sInstance = new SingletonJava();
}
public static SingletonJava getInstance(){
return SingletonJavaHolder.sInstance;
}
}
class SingletonStaticKotlin private constructor(){
companion object{
val instance = SingletonStaticKotlinHolder.holder
}
private object SingletonStaticKotlinHolder{
val holder = SingletonStaticKotlin()
}
}
第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载 SingletonHolder 并初始化 sInstance。这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。所以,推荐使用静态内部类单例模式。
6.枚举单例
public enum SingletonJava {
INSTENCE;
public void doSomeThing(){
}
}
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。在上面讲的几种单例模式实现中, 有一种情况下其会重新创建对象,那就是反序列化:将一个单例实例对象写到磁盘再读回来,从而获得了 一个实例。
部分内容摘抄自-Android进阶之光