单例设计模式

单例设计模式

基本介绍

类的单例设计模式,就是采取一定的方式保证在整个的软件系统中,对某个类只能有一个对象实例存在,且类提供一个静态方法,用以获取该对象。
例如Hibernate的SessionFactory,它是sql会话工厂,这个对象一定是很重的(创建需要加载很多资源和时间),一般情况下,只需要一个SessionFactory就够了。

单例模式的八种实现方式

1.饿汉式(静态常量) 2.饿汉式(静态代码块) 3.懒汉式(线程不安全) 4.饿汉式(线程安全,同步方法) 5.饿汉式(线程不安全,同步代码块) 6.双重检查 7.静态内部类 8.枚举类
字体加粗的推荐使用。

饿汉式(静态常量)

实现步骤
1.构造器私有化(杜绝new) 2.在类的内部创建实例 3.在类的内部创建一个静态方法,返回创建的对象。
代码实现

class Person{
	//1.通过静态属性,创建对象
    private final static Person p = new Person();
	//2.构造器私有化
    private Person(){

    }
	//3.提供public 的静态方法,用以返回单例对象
    public static Person getInstance(){
        return p;
    }
}

饿汉式(静态代码块)

代码实现

class Person{

    private final static Person p;
	//1.通过静态代码块,创建单例对象
    static{
        p = new Person();
    }
	//2.构造器私有化
    private Person(){

    }
	//3.提供public 的静态方法,用以返回单例对象
    public static Person getInstance(){
        return p;
    }
}

静态代码块和静态常量,一样效果,只是使用静态代码块,完成单例对象的创建。

饿汉式分析

优点 : 写法简单,在完成类的装载时,完成单例对象的创建,避免了线程同步问题
缺点:在类装载,就完成实例化,没有达到懒加载的效果(懒加载,就是使用时,才创建)。如果项目启动到结束,都没有使用到该单例对象,会造成内存的浪费。
这种方式基于class loader机制避免了多线程的同步问题,不过单例对象在类装载时,就完成了对象的创建。在单例模式中大多数情况下,虽然都是调用getInsatnce()方法,完成对象创建,但是导致类加载的原因有多种,比如其他的静态方法被使用,导致的类加载,这时候就达不到懒加载的效果。
结论:这种单例模式可用,但是可能会造成内存浪费,且达不到懒加载效果。

懒汉式(线程不安全)

代码实现

class Person {
    private static Person p;

    private Person() {
    }
	
    public static Person getInstance() {
        //线程不安全
        if (p == null) {
            p = new Person();
        }
        return p;
    }
}

优缺点说明

  • 1.起到了懒加载的效果,但是只能在单线程模式下使用。
  • 2.如果在多线程下,一个线程进入了if(p == null)的判断,还没来得及往下执行,另一个线程也通过了这个判断语句,这边会产生多个实例。所以多线程环境下不要使用这种方式。
  • 3.结论,开发中,不要使用这种方式。

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

代码实现

class Person {
    private Person() {
    }

    private static Person p;
	//利用synchronized关键字同步方法
    public static synchronized Person getInstance() {
        if (p == null) {
            p = new Person();
        }
        return p;
    }
}

优缺点说明

  • 1.解决了线程不安全的问题
  • 2.效率低,每个线程在获取单例对象时,执行getInstance方法都要进行同步,而其实这个方法只执行一次实例代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低。
  • 3.结论,在实际开发中,不推荐此方式。

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

代码实现

class Person{
    private Person(){}

    private static Person p;

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

优缺点说明

  • 1.这种方式,本意是想对第四种方式的改进,因为前面的同步方法效率太低了,改为同步产生实例化的代码块
  • 2.但是这种方式,并不能起到线程安全的作用,和第三种实现方式一致,假如一个线程进行了if(p == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这样就会产生多个实例
  • 3.结论,实际开发,不推荐使用这种方式。

双重检查

代码实现

class Person{
    private Person(){}

    private static volatile Person p;//使用volatile关键字为了确保多线程环境下的可见性和有序性。

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

优缺点说明

  • 1.Double-Check概念,是多线程开发中常用的。如代码所示,进行两次if(p == null)的检查,就可以保证线程安全了。
  • 2.这样实例化代码只执行一次,后面再次访问,判断if(p == null)直接return实例化对象,也避免了反复进行方法同步。
  • 3.线程安全,延迟加载,效率高。
  • 4.结论,即实现了单例模式,也满足了懒加载,效率高,且线程安全,推荐使用。

volatile关键字

在多线程环境下,每个线程都有自己的工作内存,其中包含了被线程使用的变量的副本。当一个线程修改了变量的值时,这个修改可能不会立即被其他线程看到,而是在某个时刻才会被刷新到主内存中,这就导致了可见性问题。
使用 volatile 关键字修饰一个变量,可以确保变量的修改对其他线程是可见的。当一个线程修改了 volatile 变量的值,会立即将修改后的值刷新到主内存中,其他线程在读取该变量时会从主内存中获取最新的值。
此外,volatile 关键字还可以保证变量的有序性。在多线程环境下,指令重排序可能会导致代码执行顺序与预期不符,使用 volatile 关键字修饰的变量会禁止指令重排序,保证代码的执行顺序与程序中的顺序一致。需要注意的是,volatile 关键字只能保证对单次读/写的原子性,不能保证复合操作的原子性。如果需要保证复合操作的原子性,可以使用锁或者其他同步机制。
在单例模式下的双重检查的实现方式中,需要对单例对象(静态属性)做volatile修饰,因为java中对象的生成分为 分配内存、初始化、返回对象引用 但是由于指令的无序性。即是可能会出现 分配内存 返回对象引用 初始化的指令顺序。但是是1.5之前的jdk版本会有此问题,当然概率很小。在jdk1.5后可以使用volatile关键字保证创建一个对象的指令按顺序执行。

静态内部类

class Person{
    private Person(){}

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

    public static Person getInstance(){
        return SubPerson.INSTANCE;
    }   
}

优缺点说明

  • 1.这种方式采用了类装载机制保证初始化实例时只有一个线程。
  • 2.静态内部类在Person类被装载时,并不会立即实例化,而是在getInstance方法,被调用,才会装载SubPersonl类,从而完成Person的实例化。
  • 3.类的静态属性,只会在第一次加载类的时候初始化,所以这样的方式,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
  • 4.优点,避免了线程不安全,利用静态内部类特定实现延迟加载,效率高。
  • 5.结论 推荐使用。

枚举

代码实现

enum Person{
    INSTANCE;
}

优缺点说明

  • 1.这借助JDK1.5添加的枚举来实现单例模式,可以避免多线程同步问题,而且还能防止反序列化,重新创建新的对象。
  • 2.结论 推荐使用,但是此方法会隐性的继承Enum类,需要注意。

单例模式的应用

java.lang.Runtime类就是经典的单例模式

注意事项和细节

  • 1.单例模式保证了系统内部,一个类只存在一个对象,节省了系统资源,对于一些需要频繁创建和销毁的对象,使用单例模式可提高系统的性能。
  • 2.获取单例对象,不是new,而是调用对应的获取对象的静态方法。
  • 3.单例模式的使用场景,需要频繁的进行创建和销毁对象。创建对象耗时过多,或者耗费资源过多(即重量级对象),但又经常使用的对象,如频繁访问数据库,文件的对象(数据源,SessionFactory等)。
    只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。
posted @ 2023-09-04 22:48  长名06  阅读(44)  评论(0编辑  收藏  举报