单例模式深入浅出---详细注释

1、单例模式

1.1、单例设计模式

它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

数据库连接池就使用到了单例模式,初始化的时候创建譬如100个connection对象,然后再需要的时候
提供一个,用过之后返回到pool中,我们用单例模式,是保证连接池有且只有一个。不仅避免了实例的
重复创建,节约了内存,

使用线程池来管理线程,使用线程池来管理线程的好处是线程池中的线程可以复用,在一个线程使用过
时候,再返回到线程池中,不需要每次都创建一个线程。由于线程池是公共的,因此我们使用单例模式
来保证线程池有且仅有一个。

单例模式的优点:

  • 控制资源的使用,通过线程同步来控制资源的并发访问;
  • 控制实例产生的数量,达到节约资源的目的;
  • 作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的线程或者进程之间实现通信

    1.1.2、单例模式的实现

单例设计模式分两类:

  • 饿汉式:类加载就会导致该单例对象被创建
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建

1、饿汉式----方式1(静态变量方式)

package com.njust.singleton.hungryman_staticvar;

/**
 * @program: ruoyi
 * @description:单例模式----饿汉式1-----静态成员变量法
 * @author: tjj
 * @create: 2021-09-24 18:55
 **/
//类加载到内存后,就实例化一个单例,JVM保证线程安全
//缺点:无论是否用到,类加载时就完成实例化
public class SingletonStaticVar {
    
    

    // 1.私有构造方法 --------- 为什么私有构造方法 ----> 当私有构造方法以后,外界就访问不到这个构造方法
    private SingletonStaticVar() {
    
     }
    //外界访问不到之后,就一个方法都创建不了------> 那如何实例化对象呢 -----> 自己在该类中创建一个该类的对象,供外界使用

    // 2.在本类中创建该类对象, ------> 因为只能创建一次,所以用static修饰
    private static SingletonStaticVar singletonStaticVar = new SingletonStaticVar();

    // 3.提供一个公共的访问方式,让外界去访问该对象------>getInstance是可以换名字的,不过是一个返回对象的方法----
    // 不过最好用getInstance,业界都这样写
    public static SingletonStaticVar getInstance(){
    
    
        return singletonStaticVar;
    }

    /*
    * 其他的业务方法,无所谓,随便加
    * */
    public void printM() {
    
    
        System.out.println("M");
    }

    public void printQ() {
    
    
        System.out.println("Q");
    }
    
}

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //创建singleton类的对象
        SingletonStaticVar instance = SingletonStaticVar.getInstance();
        System.out.println("创建的实例对象是:"+instance);

        SingletonStaticVar instance2 = SingletonStaticVar.getInstance();
        System.out.println("创建的实例对象2是:"+instance2);

        //判断创建的两个对象是不是同一个对象    == 判断的是两个对象的  内存地址 是否一样
        System.out.println(instance == instance2);

        /*
        * 然后就可以调用该类的方法了
        * */
        instance.printM();
        instance2.printQ();
    }
}

2.饿汉式----方式2-----(静态代码块)

public class SingletonStaticBlock {
    
    

    //1.私有构造方法
    private SingletonStaticBlock() {
    
    }

    //2.声明SingletonStaticBlock类型的变量------>初始值为null
    private static SingletonStaticBlock singletonStaticBlock;

    //3.在静态代码块中赋值
    static {
    
    
        singletonStaticBlock = new SingletonStaticBlock();
    }

    //4.对外提供一个获取该类对象的方法,自然也就是public
    public static SingletonStaticBlock getInstance(){
    
    
        return singletonStaticBlock;
    }
    
    /*
     * 其他的业务方法,无所谓,随便加
     * */
    public void printM() {
    
    
        System.out.println("M");
    }

    public void printQ() {
    
    
        System.out.println("Q");
    }
}

3.懒汉式

public class SingleLazyman {
    
    
    //私有构造方法
    private SingleLazyman() {
    
    }

    //声明单例类型的变量---->只是声明了该类型的变量,并没有进行赋值--->null
    private static SingleLazyman singleLazyman;

    //对外提供访问方式     当多线程的时候,可能存在线程安全的问题
    public static synchronized SingleLazyman getInstance() {
    
    
        //懒汉式和饿汉式的区别--->懒汉式是首次使用该类的对象的时候,才会被创建---->为了只创建一次---->所以判断该类对象是否被创建了
        //如果该类为null,那么便是第一次,创建(static很关键)
        if( singleLazyman == null){
    
    
            singleLazyman = new SingleLazyman();
            return singleLazyman;
        }
        //如果已经创建了该类,那么便 不再创建,
        return singleLazyman;
    }
    /*
    * 该类的一些方法
    * */
    public void printM() {
    
    
        System.out.println("M");
    }
    public void printA() {
    
    
        System.out.println("A");
    }

}

4.懒汉式-双重检查锁

讨论:懒汉模式加锁的问题

对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没有必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机========》由此产生了一种新的实现模式:双重检查锁!!!!!

package com.njust.singleton.lazyman_double_check_lock;

/**
 * @program: ruoyi
 * @description: 单例模式-----懒汉式-----双重检查锁方式
 * @author: tjj
 * @create: 2021-09-24 20:57
 **/
//双重检查锁是一种很好的单例模式,解决了单例,性能,线程安全的问题
//存在问题:在多线程情况下,可能会出现空指针异常的问题--->原因:JVM在实例化对象的时候会进行优化和指令重排序操作
// ------->解决,使用volatile关键字!!!
public class SingleLazymanDoubleLock {
    
    
    //私有构造方法
    private SingleLazymanDoubleLock() {
    
    }

    //声明该单例模式对象的变量
    private static volatile SingleLazymanDoubleLock singleLazymanDoubleLock;

    //对外提供公共的访问方式
    public static SingleLazymanDoubleLock getInstance() {
    
    
        //第一次判断,如果singleLazymanDoubleLock的值不为null,那么就不要抢占锁,直接返回对象
        //如果为null,那么给它加一把锁
        if (singleLazymanDoubleLock == null) {
    
    
            synchronized (SingleLazymanDoubleLock.class) {
    
    
                //第二次判断
                if (singleLazymanDoubleLock == null) {
    
    
                    singleLazymanDoubleLock = new SingleLazymanDoubleLock();
                }
            }
        }
        return singleLazymanDoubleLock;
    }
    /*
    * 该类的一些方法
    * */
    public void printR() {
    
    
        System.out.println("R");
    }
}
public class Client {
    
    
    public static void main(String[] args) {
    
    
        SingleLazymanSync instance = SingleLazymanSync.getInstance();
    }
}

懒汉式-----静态内部类

// 静态内部类单例模式中,由内部类创建,由于   JVM在加载外部类的过程中,是不会加载静态内部类的, 只有静态内部类的属性/方法,被调用时才会被加载,
// 并初始化其静态属性.       静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序.

    //第一次加载SingletonLazymanInnerClass的时候不会初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化
    //INSTANCE,这样不仅能确保线程安全,也能保证SingletonLazymanInnerClass的唯一性
public class SingletonLazymanInnerClass {
    
    

    //私有构造方法
    private SingletonLazymanInnerClass() {
    
    }

    //定义一个静态内部类
    private static class SingletonHolder {
    
    
        //在内部类中声明并初始化外部类的对象    为了防止外界对他进行修改---->加上final关键字
        private static final SingletonLazymanInnerClass INSTANCE = new SingletonLazymanInnerClass();
    }

    //提供一个公共的访问方式
    public static SingletonLazymanInnerClass getInstance() {
    
    
        return SingletonHolder.INSTANCE;
    }

    /*
    * 该类的一些方法,
    * */
    public void printL() {
    
    
        System.out.println("L");
    }

}

5.恶汉式-----枚举

枚举类实现单例模式是极力推荐的单例模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分利用了枚举的这个特性来实现单例模式

// 不考虑内存浪费空间的时候首选枚举类型
public enum SingletonEvilEnum {
    
    
    INSTANCE;
}
public class Client {
    
    
    public static void main(String[] args) {
    
    
        SingletonEvilEnum instance = SingletonEvilEnum.INSTANCE;
        SingletonEvilEnum instance2 = SingletonEvilEnum.INSTANCE;

        System.out.println(instance == instance2);
        //结果为true,可以看出枚举类的单例模式两次获取到的对象是同一个对象
    }
}

猜你喜欢

转载自blog.csdn.net/Wulawuba/article/details/120895740
今日推荐