单例设计模式

单例设计模式

使用单例模式的场景

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
  3. 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

1. 饿汉式单例模式

  • 在类加载时就创建类对象,将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身

1. 不使用静态代码块方式

* 在明确要使用到该对象时推荐使用
/**
 * 饿汉式单例模式:在类加载时就创建类对象
 * 细节:(不使用静态代码块的方式)
 * 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
 *
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 10:25
 */
public class SingLeton01 {

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

    // 创建一个静态类对象变量用于接收创建好的类对象
    private static final SingLeton01 INSTANCE = new SingLeton01();

    // 创建一个get方法用于获得
    public static SingLeton01 getInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) {
        SingLeton01 singLeton01 = new SingLeton01();
        SingLeton01 singLeton2 = new SingLeton01();
        System.out.println(singLeton01.getInstance());
        System.out.println(singLeton2.getInstance());
    }

}

2. 使用静态代码块方式

* 在明确要使用到该对象时推荐使用
/**
 * 饿汉式单例模式:在类加载时就创建类对象
 * 细节:(使用静态代码块的方式)
 * 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
 *
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 10:43
 */
public class SingLeton02 {

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

    // 在静态代码块中对类对象进行创建
    static {
        INSTANCE = new SingLeton02();
    }

    // 创建一个静态类对象变量用于接收创建好的类对象
    private static final SingLeton02 INSTANCE;

    // 创建一个get方法用于获得
    public static SingLeton02 getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        SingLeton02 singLeton01 = new SingLeton02();
        SingLeton02 singLeton2 = new SingLeton02();
        System.out.println(singLeton01.getInstance());
        System.out.println(singLeton2.getInstance());
    }
}

2. 懒汉式单例模式

  • 在使用的时候再创建该类对象,将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身

1. 线程不安全的懒汉式单例(开发中不使用)

/**
 * 懒汉式单例模式(线程不安全的懒汉式)
 * 在实际调用的时候才创建对象
 * 将类的构造器私有化,不让外界对该类进行实例化,实例化的操作交给类本身
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 17:12
 */
public class SingLeton03 {
    // 将构造器私有化
    private SingLeton03() {}

    // 创建一个静态类对象变量用于接收创建好的类对象
    private static SingLeton03 INSTANCE = null;

    // 创建一个get方法用于获得
    public static SingLeton03 getInstance() {

        /*
            判断当前对象是否已经创建,如果没有创建,则创建
            1.在这个位置可能会发生线程安全问题
            2.当线程A和线程B都走到当前位置那个线程拿到的 INSTANCE 都是null,
            所以两个线程都进行了对象创建的操作,主要就创建了两个对象,就不是单例模式了
         */
        if (INSTANCE == null){
            INSTANCE = new SingLeton03();
        }
        // 如果已经创建过则直接返回
        return INSTANCE;
    }

    public static void main(String[] args) {
        SingLeton03 singLeton01 = new SingLeton03();
        SingLeton03 singLeton02 = new SingLeton03();
        System.out.println(singLeton01.getInstance() == singLeton02.getInstance());
    }
}

2. 使用同步方法保证懒汉式安全(开发中不推荐使用)

  • 缺点:虽然保证了线程安全问题,但是存着性能问题
/**
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 17:46
 */
public class SingLeton04 {

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

    // 创建一个静态类对象变量用于接收创建好的类对象
    private static SingLeton04 INSTANCE = null;

    /*
        创建一个get方法用于获得,
        直接将该静态方法用 synchronized 同步锁将其锁住,
        锁住静态方法就相当于锁住了当前类对象,因为静态方法是属于类的,此时只允许一个线程操作该对象
     */
    public static synchronized SingLeton04 getInstance() {

        if (INSTANCE == null){
            INSTANCE = new SingLeton04();
        }
        // 如果已经创建过则直接返回
        return INSTANCE;
    }

    public static void main(String[] args) {
        SingLeton04 singLeton01 = new SingLeton04();
        SingLeton04 singLeton02 = new SingLeton04();
        System.out.println(singLeton01.getInstance() == singLeton02.getInstance());
    }
}

3. 使用双层校验的方式保证懒汉式安全(推荐使用)

/**
 * 使用双层校验的方式保证懒汉式安全(开发中推荐使用)
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 18:02
 */
public class SingLeton05 {

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

    /*
        创建一个静态类对象变量用于接收创建好的类对象
        使用volatile 轻量锁将变量锁住
            1. 保证了不同线程对这个变量进行操作时的可见性,
               即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
            2. 禁止进行指令重排序
     */
    private static volatile SingLeton05 INSTANCE;

    // 创建一个get方法用于获得
    public static SingLeton05 getInstance() {
        /*
            判断当前的 INSTANCE 是否为null,
            当后面的线程进入此代码的时候,如果已经创建了对象,不会再进入到里面的代码保证了效率
         */
        if (INSTANCE == null) {
            /*
                将当前类锁住,避免其他线程操作该类
                1. 当线程A到这来先获得锁,线程B在外面等待,那么在 INSTANCE 为null的情况下就会创建对象
                   此时因为 INSTANCE 被 volatile 修饰,使用线程B会立即知道被修改过的值
                2. 当线程A在获得锁进来时,此时 INSTANCE 已经不是 null 所以不会再进行对象的创建
             */
            synchronized (SingLeton05.class) {
                // 此时如果
                if (INSTANCE == null) {
                    INSTANCE = new SingLeton05();
                }
            }
            INSTANCE = new SingLeton05();
        }
        // 如果已经创建过则直接返回
        return INSTANCE;
    }
}

4. 通过静态内部类方式创建单例对象(推荐使用)

/**
 * 通过静态内部类方式创建单例对象
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 18:43
 */
public class SingLeton06 {

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

    /*
        创建静态内部类方式创建单例对象,
        在类SingLeton06加载的时候不会创建静态内部类对象,
        只有在被调用时才加载,并且只加载一次,在类进行加载的时候是线程安全的,别的线程无法操作
     */
    public static class  getSingLeton06Instance {
        private static SingLeton06 INSTANCE = new SingLeton06();
    }

    // 创建一个get方法用于获得
    public static SingLeton06 getInstance() {
        return getSingLeton06Instance.INSTANCE;
    }
}

3. 使用枚举的方式实现单例(推荐使用)

package com.kl.desginMode.singleton;

/**
 * 使用枚举的方式实现单例
 * 1. 支持序列化
 * 2. 线程安全
 * 3. 保证单例
 *
 * @author : 可乐
 * @version : 1.0
 * @since : 2021/7/17 19:05
 */
enum SingLeton07 {
    INSTANCE;
}

4. JDK源码中使用到的单例模式

/**
 * Runtime类中的单例模式
 */
public class Runtime {
	
    // 创建一个静态变量,接收一开始创建的Runtime对象
	private static Runtime currentRuntime = new Runtime();
    
    // 构造方法
    private Runtime() {}
    
    // getRuntime方法
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    // 剩余方法不作说明......
}

单例设计模式

上一篇:MyBatis一级缓存和二级缓存


下一篇:异常、回顾常用类