单例模式

介绍

1、提供了一种创建对象的最佳方式

2、涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建

3、类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

4、单例模式采取一定的方法保证在整个的软件系统中,对某一个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)

5、在 JDK 的应用:java.lang.Runtime,使用饿汉式(静态常量)

 

事项

1、保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

2、实例化一个单例类的时,使用相应的获取对象的方法,而不是使用 new

3、应用场景

(1)需要频繁的进行创建和销毁的对象

(2)创建对象时耗时过多或耗费资源过多,即重量级对象

(2)经常使用的对象、工具类对象、频繁访问数据库或文件的对象(如数据源、session 工厂等)

 

八种方式

1.可用

(1)饿汉式(静态常量)

(2)饿汉式(静态代码块)

(3)双重检查

(4)懒汉式(静态内部类)

(5)枚举

2.不可用

(1)懒汉式(线程不安全)

(2)懒汉式(线程安全,同步方法)

(3)懒汉式(线程不安全,同步代码块)

 

饿汉式

1、优点:写法比较简单,在类装载的时候就完成实例化,避免了线程同步问题

2、缺点:在类装载的时候就完成实例化,不能达到延迟加载的效果,如果程序从未使用过这个实例,则会造成内存的浪费

3、总结

(1)基于 classloder 机制避免了多线程的同步问题,instance 在类装载时就实例化

(2)在单例模式中大多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载

(3)这种单例模式可用,可能造成内存浪费,且没有达到 Lazy Loading 的效果

 

饿汉式(静态常量)

1、步骤

(1)类的内部创建私有、静态、常量对象

(2)构造器私有化,防止外部直接 new

(3)向外暴露一个静态的公共方法,返回对象实例

2、代码实现

public class Singleton {
    private static final Singleton instance = new Singleton();//本类内部创建静态常量对象

    private Singleton() {//构造器私有化,防止外部直接new
    }

    public static Singleton getInstance() {//提供一个公有的静态方法,返回实例对象
        return instance;
    }
}

 

饿汉式(静态代码块)

1、步骤

(1)类的内部定义私有、静态、常量对象

(2)在静态代码块中创建对象

(3)构造器私有化,防止外部直接 new

(4)向外暴露一个静态的公共方法,返回对象实例

2、代码实现

public class Singleton {
    private static final Singleton instance;//类的内部定义私有、静态、常量对象

    static {//在静态代码块中创建对象
        instance = new Singleton();
    }

    private Singleton() {//构造器私有化
    }

    public static Singleton getInstance() {//提供一个静态的公共方法,返回对象实例
        return instance;
    }
}

 

懒汉式(线程不安全)

1、优点:有 Lazy Loading 的效果,避免内存浪费

2、缺点:只能在单线程下使用,在多线程下,一个线程进入了判断语句块,还没来得及往下执行,另一个线程也通过了这个判断语句,会产生多个实例,所以在多线程环境下不可使用这种方式

3、总结:在实际开发中不使用

4、代码实现

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公共方法,当使用该方法时,才去创建instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

5、单重检查

(1)非单例模式

(2)应用:对于可以接受重复初始化的实例域

(3)代码实现

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公共方法,当使用该方法时,才去创建instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

懒汉式(线程安全,同步方法)

1、优点:有 Lazy Loading 的效果,避免内存浪费,解决了线程安全问题

2、缺点:效率低,每个线程在想获得类的实例时,执行getInstance方法都要进行同步,而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接 return

3、总结:在实际开发中不推荐使用

4、代码实现

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公共方法,加入同步处理的代码,解决线程安全问题
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 

懒汉式(线程不安全,同步代码块)

1、优点:有 Lazy Loading 的效果,避免内存浪费,解决了线程安全问题

2、缺点:本意是对懒汉式(线程安全,同步方法)的改进,同步方法效率太低,改为同步产生实例化的代码块,但是这种同步并不能起到线程同步的作用,问题与懒汉式(线程不安全)一致

3、总结:在实际开发中不使用

4、代码实现

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公共方法,加入同步处理的代码,解决线程安全问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

 

双重检查

1、优点:解决线程安全问题,解决懒加载问题,同时保证了效率

2、在实际开发中推荐使用

3、懒汉式的改进

4、应用:对实例域的延迟初始化

5、代码实现

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    //提供一个静态的公共方法,加入双重检查
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 

分析

1、创建一个对象实例,分三步

(1)分配对象内存

(2)调用构造器方法,执行初始化

(3)将对象引用赋值给变量

2、虚拟机实际运行时,以上指令可能发生重排序:(2)(3)可能发生重排序,但是并不会重排序(1)的顺序,因为(1) 这个指令都需要先执行,(2)(3) 指令需要依托(1)指令执行结果

3、指令重排序:CPU 采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理

4、Java 语言规规定了线程执行程序时需要遵守 intra-thread semantics,保证重排序不会改变单线程内的程序执行结果,这个重排序在没有改变单线程程序的执行结果的前提下,可以提高程序的执行性能

5、重排序并不影响单线程内的执行结果,但是在多线程的环境就带来一些问题

6、缺少 volatile 时

(1)首先判断变量是否被初始化,没有被初始化,再去获取锁,获取锁之后,再次判断变量是否被初始化(第二次判断目的在于有可能其他线程获取过锁,已经初始化该变量),第二次检查还未通过,才会真正初始化变量

(2)如果线程 1 获取到锁进入创建对象实例,发生了指令重排序,当线程 1 执行到(3),线程 2 刚好进入,此时对象已经不为 null,线程 2 可以自由访问该对象,但该对象还未初始化,所以线程 2 访问时将会发生异常

7、必须两次 if 判断 instance 是否为 null

(1)有两个线程同时调用 getInstance 方法,由于 instance 是 null ,因此两个线程都可以通过第一次 if 判断

(2)然后由于锁机制的存在,会有一个线程先进入同步语句,并进入第二次 if 判断 ,而另外的一个线程就会在外面等待

(3)不过,当第一个线程执行完 new Singleton() 语句后,就会退出 synchronized 保护的区域,这时如果没有第二次 if 判断的话,那么第二个线程也会创建一个实例,此时就破坏了单例

(4)而对于第一重 if 而言,如果去掉它,那么所有线程都会串行执行,效率低下,所以两个 check 都需要保留

 

volatile

1、介绍

(1)Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程,当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序,volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值

(2)在访问 volatile 变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile 变量是一种比 sychronized 关键字更轻量级的同步机制

(3)当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个 CPU,每个线程可能在不同的 CPU 上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中,而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步

2、变量特性

(1)保证此变量对所有的线程的可见性(一个线程修改的状态对另一个线程是可见的),当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新

(2)禁止指令重排序优化,有 volatile 修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),所以其他线程不会访问到一个未初始化的对象,只有一个 CPU 访问内存时,并不需要内存屏障

3、性能:volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行

 

静态内部类

1、利用

(1)外部类的加载不会导致内部类的加载,实现延迟加载:外部类被装载时并不会立即实例化,而在需要实例化时,调用 getInstance 方法,才会装载,从而完成外部类的实例化

(2)类装载的机制来保证初始化实例时只有一个线程:类的静态属性只会在第一次加载类的时候初始化,JVM 保证了线程的安全性,在类进行初始化时,其他线程无法进入

2、优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高

3、总结:开发中推荐使用

4、应用:出于性能考虑而需要对静态域使用延迟初始化

5、类初始化是懒加载的

(1)初始化时机:首次访问类的静态变量 / 静态方法时

(2)使用 SingletonInstance,才加载

(3)static final 修饰的引用类型不会在准备阶段赋值,而是在初始化阶段赋值

6、类的初始化方法:<clinit>()

(1)虚拟机会保证一个类的 <clinit>() 在多线程环境中被正确地加锁、同步

(2)如果多个线程同时去初始化一个类,则只会有一个线程去执行这个类的 <clinit>(),其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 完毕

(3)如果之前的线程成功加载了类,则等在队列中的线程,就没有机会再执行 <clinit>(),当需要使用这个类时,虚拟机会直接返回给它已经准备好的信息

7、代码实现

public class Singleton {
    
    private Singleton() {
    }

    //静态内部类,该类中有一个静态属性Singleton
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    //提供一个静态的公共方法,直接返回SingletonInstance.INSTANCE
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

 

枚举

1、优点:避免多线程同步问题,防止反序列化重新创建新的对象

2、总结:开发中推荐使用

3、属于饿汉式

4、代码实现

public enum SingletonEnum {
    INSTANCE;
    
    private final SingletonClass singletonInstance;

    //构造器默认为private,且只能为private
    SingletonEnum() {
        singletonInstance = new SingletonClass();
    }

    //可以通过SingletonEnum.INSTANCE.getInstance() 来获取 SingletonClass 的唯一实例
    public SingletonClass getInstance() {
        return singletonInstance;
    }
}

 

posted @   半条咸鱼  阅读(35)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示