乐趣区

关于程序员:五种方式实现-Java-单例模式

前言

单例模式(Singleton Pattern)是 Java 中最简略的设计模式之一。这种类型的设计模式属于创立型模式,它提供了一种创建对象的最佳形式。

这种模式波及到一个繁多的类,该类负责创立本人的对象,同时确保只有单个对象被创立。这个类提供了一种拜访其惟一的对象的形式,能够间接拜访,不须要实例化该类的对象。

饿汉单例

是否多线程平安:是

是否懒加载:否

正如名字含意,饿汉须要间接创立实例。

public class EhSingleton {private static EhSingleton ehSingleton = new EhSingleton();

    private EhSingleton() {}

    public static EhSingleton getInstance(){return ehSingleton;}
}

毛病:类加载就初始化,节约内存
长处:没有加锁,执行效率高。还是线程平安的实例。

懒汉单例

懒汉单例,在类初始化不会创立实例,只有被调用时才会创立实例。

非线程平安的懒汉单例

是否多线程平安:否

是否懒加载:是

public class LazySingleton {

    private static LazySingleton ehSingleton;

    private LazySingleton() {}

    public static LazySingleton getInstance() {if (ehSingleton == null) {ehSingleton = new LazySingleton();
        }
        return ehSingleton;

    }

}

实例在调用 getInstance 才会创立实例,这样的长处是不占内存,在单线程模式下,是平安的。然而多线程模式下,多个线程同时执行 if (ehSingleton == null) 后果都为 true,会创立多个实例,所以下面的懒汉单例是一个线程不平安的实例。

加同步锁的懒汉单例

是否多线程平安:是

是否懒加载:是

为了解决多个线程同时执行 if (ehSingleton == null) 的问题,getInstance 办法增加同步锁,这样就保障了一个线程进入了 getInstance 办法,别的线程就无奈进入该办法,只有执行结束之后,其余线程能力进入该办法,同一时间只有一个线程能力进入该办法。

public class LazySingletonSync {

    private static LazySingletonSync lazySingletonSync;

    private LazySingletonSync() {}

    public static synchronized LazySingletonSync getInstance() {if (lazySingletonSync == null) {lazySingletonSync =new LazySingletonSync();
        }
        return lazySingletonSync;
    }

}

这样配置尽管保障了线程的安全性,然而效率低,只有在第一次调用初始化之后,才须要同步,初始化之后都不须要进行同步。锁的粒度太大,影响了程序的执行效率。

双重测验懒汉单例

是否多线程平安:是

是否懒加载:是

应用 synchronized 申明的办法,在多个线程拜访,比方 A 线程拜访时,其余线程必须期待 A 线程执行结束之后能力拜访,大大的升高的程序的运行效率。这个时候应用 synchronized 代码块优化执行工夫,缩小锁的粒度

双重测验首先判断实例是否为空,而后应用 synchronized (LazySingletonDoubleCheck.class) 应用 类锁,锁住整个类,执行完代码块的代码之后,新建了实例,其余代码都不走 if (lazySingletonDoubleCheck == null) 外面,只会在最开始的时候效率变慢。而 synchronized 外面还须要判断是因为可能同时有多个线程都执行到 synchronized (LazySingletonDoubleCheck.class),如果有一个线程线程新建实例,其余线程就能获取到 lazySingletonDoubleCheck 不为空,就不会再创立实例了。

public class LazySingletonDoubleCheck {

    private static LazySingletonDoubleCheck lazySingletonDoubleCheck;

    private LazySingletonDoubleCheck() {}

    public static LazySingletonDoubleCheck getInstance() {if (lazySingletonDoubleCheck == null) {synchronized (LazySingletonDoubleCheck.class) {if (lazySingletonDoubleCheck == null) {lazySingletonDoubleCheck = new LazySingletonDoubleCheck();
                }
            }
        }
        return lazySingletonDoubleCheck;
    }
}

动态外部类

是否多线程平安:是

是否懒加载:是

外部类加载时,并不会加载外部类,也就不会执行 new SingletonHolder(),这属于懒加载。只有第一次调用 getInstance() 办法时才会加载 SingletonHolder 类。而动态外部类是线程平安的。

动态外部类为什么是线程平安

动态外部类利用了类加载机制的初始化阶段 <clinit> 办法,动态外部类的 动态变量赋值操作,理论就是一个 <clinit> 办法,当执行 getInstance() 办法时,虚拟机才会加载 SingletonHolder 动态外部类,

而后在加载动态外部类,该外部类有动态变量,JVM 会改外部生成 <clinit> 办法,而后在初始化执行 <clinit> 办法 —— 即执行动态变量的赋值动作。

虚构机会保障 <clinit> 办法在 多线程环境下应用加锁同步,只会执行一次 <clinit> 办法。

这种形式不仅实现提早加载,也保障线程平安。

public class StaticClass {private StaticClass() {}

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

    public static final SingletonHolder getInstance() {return SingletonHolder.INSTANCE;}
}

总结

  • 饿汉单例类加载就初始化,在没有加锁的状况下实现了线程平安,执行效率高。然而无论有没有调用实例都会被创立,比拟节约内存。
  • 为了解决内存的节约,应用了懒汉单例,然而懒汉单例在多线程下会引发线程不平安的问题。
  • 不平安的懒汉单例,应用 synchronized 申明同步办法,获取实例就是平安了。
  • synchronized 申明办法每次线程调用办法,其它线程只能期待,升高了程序的运行效率。
  • 为了缩小锁的粒度,应用 synchronized 代码块,因为只有大量的线程获取实例,实例是 null,创立实例之后,后续的线程都能获取到线程,也就无需应用锁了。可能多个线程执行到 synchronized,所以同步代码块还须要再次判断一次。
  • 动态外部类赋值理论是调用 <clinit> 办法,而虚拟机保障 <clinit> 办法应用锁,保障线程平安。
退出移动版