细数单例模式

为什么要有单例模式

单例模式是一个类对外仅提供一个实例对象,防止出现对象的不一致状态,与多例对象是对立的

 单例模式有以下特点

  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

单例模式的有点

  1. 只有一个对象,内存开支少、性能好

  2. 避免对资源的多重占用

  3. 在系统设置全局访问点,优化和共享资源访问

单例的写法 

 饿汉模式

不管对象会不会被使用,都提前实例化 好对象,等待被使用

 1、是线程安全的

  2、因为在类文件加载之初就会实例化对象,如果最终该对象没有被使用到会造成一定程度的内存浪费(针对目前的服务器能力无伤大雅)


/**
 * @Description: <饿汉式>
 * @Author: milla
 * @CreateDate: 2020/09/18 10:33
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 10:33
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class Singleton {

    /**
     * 内部实例化
     */
    private final static Singleton instance = new Singleton();

    /**
     * 私有化构造-阻止其他再次实例化该对象
     */
    private Singleton() {
    }

    /**
     * 对外提供获取对象的方式
     *
     * @return
     */
    public static Singleton getInstance() {
        return instance;
    }
}

 懒汉式

先声明出来对象,但是并不做实例化,当被调用的时候才会去实例化

1、线程不安全(可通过改版进行线程安全改造)

2、按需实例化,更符合节约资源的思想


/**
 * @Description: <懒汉式>
 * @Author: milla
 * @CreateDate: 2020/09/18 10:33
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 10:33
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class SingletonLazy {

    /**
     * 内部实例化
     */
    private static SingletonLazy instance;

    /**
     * 私有化构造
     */
    private SingletonLazy() {
    }

    /**
     * 对外提供获取对象的方式
     *
     * @return
     */
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

 上述类,在多线程情况下很容易出现线程安全问题,如果对方法直接加同步锁(synchronized),是可以达到线程安全的目的,但是会大大降低程序执行的性能,因为获取对象的时候都会形成阻塞

 懒汉式改版-方法上加同步关键字

1、线程安全

2、但是因为线程会形成阻塞队列,因此会牺牲性能


/**
 * @Description: <懒汉式>
 * @Author: milla
 * @CreateDate: 2020/09/18 10:33
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 10:33
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class SingletonLazy {

    /**
     * 内部实例化
     */
    private static SingletonLazy instance;

    /**
     * 私有化构造
     */
    private SingletonLazy() {
    }

    /**
     * 对外提供获取对象的方式
     *
     * @return
     */
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

饿汉式改版-双重加锁判空并防止指令重排 


/**
 * @Description: <懒汉式>
 * @Author: milla
 * @CreateDate: 2020/09/18 10:33
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 10:33
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class SingletonLazy {

    /**
     * 内部实例化
     */
    private static volatile SingletonLazy instance;

    /**
     * 私有化构造
     */
    private SingletonLazy() {
    }

    /**
     * 对外提供获取对象的方式
     *
     * @return
     */
    public static SingletonLazy getInstance() {
        //如果对象已经被实例化就不用阻塞
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                //防止多个线程都判断实例对象为空,然后形成阻塞队列,重复实例对象
                if (instance == null) {
//                    JVM新建对象的时候,会经过三个步骤
//                    1.分配内存
//                    2.初始化构造器
//                    3.将对象指向分配的内存的地址
//                    PS:2和3可能会出现指令重排,导致重复创建对象,因此对象要要使用volatile关键字组织JVM指令重排
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

静态内部类 

1、线程安全

2、静态变量只会加载一次,在初始化时JVM会强行保证同步,所以能保证实例是单例且是线程安全的


/**
 * @Description: <静态内部类>
 * @Author: milla
 * @CreateDate: 2020/09/18 11:12
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 11:12
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public class Singleton {
    public static Singleton getInstance() {
        return SingletonInner.instance;
    }

    /**
     * 私有化
     */
    private Singleton() {
    }

    /**
     * 静态内部类
     * 静态变量只会加载一次,在初始化时JVM会强行保证同步,所以能保证实例是单例且是线程安全的
     */
    private static class SingletonInner {
        /**
         * 内部实例化
         */
        protected final static Singleton instance = new Singleton();
    }
}

枚举模式 


/**
 * @Description: <枚举>
 * @Author: milla
 * @CreateDate: 2020/09/18 11:24
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/18 11:24
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public enum Singleton {
    /**
    * 实例对象
    */
    SINGLETON;

    /**
     * 实例要执行的方法
     */
    public void doSomething() {
        //业务逻辑在枚举类中
    }
}

单例模式实现案例 

用于用户登录表单用户名和密码及登录之后token的本地线程存储


/**
 * @Description: <单例存储用户登录前后信息>
 * @Author: milla
 * @CreateDate: 2020/09/11 17:48
 * @UpdateUser: milla
 * @UpdateDate: 2020/09/11 17:48
 * @UpdateRemark: <>
 * @Version: 1.0
 */
public enum AuthenticationStoreUtil {
    AUTHENTICATION;
    /**
     * 登录认证之后的token(每个请求都要验证token是否非法)
     */
    private final ThreadLocal<String> tokenStore = new ThreadLocal<>();
    /**
     * 登录时需要验证用户名
     */
    private final ThreadLocal<String> usernameStore = new ThreadLocal<>();
    /**
     * 登录时需要验证的密码
     */
    private final ThreadLocal<String> passwordStore = new ThreadLocal<>();

    public static String getUsername() {
        return AUTHENTICATION.usernameStore.get();
    }

    public static void setUsername(String username) {
        AUTHENTICATION.usernameStore.set(username);
    }

    public static String getPassword() {
        return AUTHENTICATION.passwordStore.get();
    }

    public static void setPassword(String password) {
        AUTHENTICATION.passwordStore.set(password);
    }

    public static String getToken() {
        return AUTHENTICATION.tokenStore.get();
    }

    public static void setToken(String token) {
        AUTHENTICATION.tokenStore.set(token);
    }

    /**
     * 清除所有登录信息
     */
    public static void clear() {
        AUTHENTICATION.tokenStore.remove();
        AUTHENTICATION.passwordStore.remove();
        AUTHENTICATION.usernameStore.remove();
    }
}

  PS : 从代码量上看,最好用的应该是枚举和静态内部类,并发系统中需要考虑线程的安全问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值