1,单例模式
1.1,单例的理解
单例模式是一种常用的软件设计模式,其定义的单例对象的类只能允许一个实例存在;
该类负责创建自己的对象,同时确保只有一个对象被创建;
一般常用在工具类的实现或创建对象需要消耗资源的业务场景。
1.2,单例的特点
类构造器私有;
持有自己类的引用;
对外提供获取实例的静态方法。
1.3,常用单例模式
饿汉模式:好处:没有线程安全;坏处:浪费内存空间。好处:没有线程安全问题
// 坏处:浪费内存空间好处好处:没有线程安全问题;坏处:浪费内存空间
懒汉模式:好处:没有内存空间的浪费;坏处:控制不好就是单例模式,比如多个线程执行时。
双重检查锁:既保证了线程安全(synchronized),又比直接上锁提高了执行效率(第一层校验),还节省了内存空间。
静态内部类:外呼类加载时不需要加载 静态内部类,不加载则不占用内存;当外部类调用 getInstance 方法时,才加载静态内部类(延迟加载),静态属性保证了全局唯一,静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字(JVM保证了一个类的 初始化在多线程下被同步加锁)。
2,饿汉模式
public class SimpleSingleton {private static final SimpleSingleton INSTANCE = new SimpleSingleton();private SimpleSingleton(){}public static SimpleSingleton getInstance(){return INSTANCE;}public static void main(String[] args) {// 单例模式-饿汉模式// 实例在初始化的时候就已经建好了,不管是否使用到,都已经实例化好了。// 好处:没有线程安全问题// 坏处:浪费内存空间System.out.println(SimpleSingleton.getInstance().hashCode());System.out.println(SimpleSingleton.getInstance().hashCode());// 2084435065// 2084435065}}
3,懒汉模式
public class SimpleSingleton2 {// 懒汉模式private static SimpleSingleton2 INSTANCE;private SimpleSingleton2(){}public static SimpleSingleton2 getInstance(){if(null == INSTANCE){return new SimpleSingleton2();}return INSTANCE;}public static void main(String[] args) {// 懒汉模式// 就是实例在用到的时候才创建,用的时候才去检查有无实例,有则返回,反之新建。// 优点:没有内存空间的浪费// 缺点:控制不好就不是单例模式,比如多个线程执行时System.out.println(SimpleSingleton2.getInstance().hashCode());System.out.println(SimpleSingleton2.getInstance().hashCode());// 2084435065// 1896277646}}
优化处理:
public class SimpleSingleton3 {private static SimpleSingleton3 INSTANCE;private SimpleSingleton3(){}public synchronized static SimpleSingleton3 getInstance(){if(null == INSTANCE){return new SimpleSingleton3();}return INSTANCE;}public static void main(String[] args) {// 改进懒汉模式,加 synchronized 关键字// 使用 synchronized 关键字会消耗性能,我们应该判断INSTANCE为空时才加锁,// 而不为空不应该加锁,需要直接返回。这就需要使用双重检查锁。System.out.println(SimpleSingleton3.getInstance().hashCode());System.out.println(SimpleSingleton3.getInstance().hashCode());// 2084435065// 1896277646}}
4,双重检查锁
public class SimpleSingleton4 {private static SimpleSingleton4 INSTANCE;private SimpleSingleton4(){}// 双重检查锁public static SimpleSingleton4 getInstance(){if(null == INSTANCE){synchronized (SimpleSingleton4.class){if(null == INSTANCE){INSTANCE = new SimpleSingleton4();}}}return INSTANCE;}public static void main(String[] args) {// 双重检查锁System.out.println(SimpleSingleton4.getInstance().hashCode());System.out.println(SimpleSingleton4.getInstance().hashCode());// 2084435065// 2084435065}}
优化处理-双重检查锁-volatile:
public class SimpleSingleton5 {// JVM虚拟机可能会重排 getInstance() 方法,所以需要添加关键字 volatile// volatile 关键字可以保证多个线程的可见性,但不能保证原子性,同时它也能禁止指令重排。// 可见性:即表现为 多个线程可同时读取同一变量时,且数据同步,共享结果// 原子性:即表现为 该操作不可拆分,同一时刻只能有一个线程对它进行操作,不会被其他调度器所打断。private volatile static SimpleSingleton5 INSTANCE;private SimpleSingleton5(){}public static SimpleSingleton5 getInstance(){if(null == INSTANCE){synchronized(SimpleSingleton5.class){if(null == INSTANCE){INSTANCE = new SimpleSingleton5();}}}return INSTANCE;}public static void main(String[] args) {// 双重检查锁的机制 既保证了线程安全(synchronized),又比直接上锁提高了执行效率(第一层校验),还节省了内存空间。System.out.println(SimpleSingleton5.getInstance().hashCode());System.out.println(SimpleSingleton5.getInstance().hashCode());// 2084435065// 2084435065}}
5,静态内部类
public class SimpleSingleton6{// 构造器私有private SimpleSingleton6(){}// 提供静态方法获取实例对象public static SimpleSingleton6 getInstance(){return Inner.INSTANCE;}// 保证单例对象,且静态变量保证唯一,线程安全// 只有外呼类调用 getInstance 方法时,静态内部类才被加载,初始化变量,不占用内存private static class Inner{private static final SimpleSingleton6 INSTANCE = new SimpleSingleton6();}public static void main(String[] args){System.out.println(SimpleSingleton6.getInstance().hashCode());System.out.println(SimpleSingleton6.getInstance().hashCode());// 2084435065// 2084435065}}
示例:
public class SimpleSingleton6 {private SimpleSingleton6(){}public static SimpleSingleton6 getInstance(){return Inner.INSTANCE;}private static class Inner{private static final SimpleSingleton6 INSTANCE = new SimpleSingleton6();}public static void main(String[] args) {// 静态内部类// 在类中我们定义了一个静态的内部类 Inner,该类的 getInstance 方法返回的内部类 Inner 的实例 INSTANCE.// 只有第一次调用 getInstance 方法时,虚拟机才加载 Inner 类并初始化 INSTANCE,// 只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。System.out.println(SimpleSingleton6.getInstance().hashCode());System.out.println(SimpleSingleton6.getInstance().hashCode());// 2084435065// 2084435065// 静态内部类的特点:// 外部类加载时不需要加载 静态内部类,不被加载则不占用内存;// 当外部类调用 getInstance 方法时,才加载静态内部类[延迟加载],静态属性保证了全局唯一,// 静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字(JVM保证了一个类的 初始化在多线程下被同步加锁)}}
