为什么要有单例模式
单例模式是一个类对外仅提供一个实例对象,防止出现对象的不一致状态,与多例对象是对立的
单例模式有以下特点
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 : 从代码量上看,最好用的应该是枚举和静态内部类,并发系统中需要考虑线程的安全问题