单例模式的优点之为什么要使用单例模式
1.由于单例模式只生成一个实例,减小系统性能开销,当一个对象的产生需要多个资源时(读取配置文件、产生其他依赖)那么我们可以在应用启动时直接创建该对象实例,然后永久驻留内存。
2.单例模式可以在系统设置全局访问点,优化共享资源访问。例如可以设计一个单例类,负责所有数据表的映射处理。
五种单例模式的实现方式
饿汉式单例模式(线程安全,调用效率高,不能延时加载)
package singal;
/**
饿汉式单例 一上来就把对象加载了 可能会存在浪费内存的问题
**/
public class Hungry {
private Byte aByte1[]=new Byte[1024];
private Byte aByte2[]=new Byte[1024];
private Byte aByte3[]=new Byte[1024];
private Byte aByte4[]=new Byte[1024];
//构造函数私有化
private Hungry(){ }
private final static Hungry HUNGRY =new Hungry(); //类初始化时立即加载对象!
//方法没有同步所以调用效率高
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例模式(调用效率不高、可以延时加载)
package singal;
//懒汉式
public class LazySingal {
private LazySingal(){
}
private static LazySingal LAZY_SINGAL; 延迟加载,也称为懒加载 真正用的时候我们才加载
public static synchronized LazySingal getInstance(){ //如果不加同步的话会存在创建对象不唯一的情况
if (LAZY_SINGAL==null){
LAZY_SINGAL=new LazySingal();
}
return LAZY_SINGAL;
}
}
DCL懒汉式单例模式(volatile)(懒汉式以及双重检测锁式)
package singal;
import javax.sound.midi.Soundbank;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
懒汉式单例
**/
public class LazyMan {
private static Boolean XUAN =false; //自定义布尔常量防止反射创建新对象
//构造函数私有
private LazyMan(){
synchronized (LazyMan.class){
if (XUAN==false){
XUAN=true;
}else throw new RuntimeException("不要试图通过反射来破坏单例!");
}
}
private volatile static LazyMan lazyMan; //延迟加载,也称为懒加载 真正用的时候我们才加载
//双重检测的懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan= new LazyMan();
/**
* 为什么要加volatile?
* 因为在创建对象的时候
* 1.分配内存空间
* 2.执行构造方法初始化对象
* 3.把这个对象指向内存空间
* 原本顺序为123 但是在内存中是完全有可能发生指令排序出现问题的现象的比如132 这不算错误但是对于线程来说就 * 是会出现问题
* 如果A线程执行132 B线程进来时原本还没完成构造的A内存空间被误判为非空
* 于是B线程直接走return lazyMan 但是这空间却是虚无的
*/
}
}
}
return lazyMan;
}
//测试多线程并发
/*public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}*/
//通过反射来破坏单例
public static void main(String[] args) throws Exception {
//拿到XUAN字段
Field xuan = LazyMan.class.getDeclaredField("XUAN");
//将private失效
xuan.setAccessible(true);
//懒汉式实例对象
//LazyMan instance = LazyMan.getInstance();
//获取空构造
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
//使private失效
declaredConstructor.setAccessible(true);
//构造实例对象
LazyMan newInstance = declaredConstructor.newInstance();
//set false就是为了能够再创建对象
xuan.set(newInstance,false);
LazyMan newInstance1 = declaredConstructor.newInstance();
//对象比较
System.out.println(newInstance1);
System.out.println(newInstance);
System.out.println(newInstance1==newInstance);
}
}
singal.LazyMan@4e50df2e <<-----------上面的输出结果 明显是单例被反射破坏了 结果产生了两个对象
singal.LazyMan@1d81eb93
false
静态内部类(线程安全、调用效率高、可以延时加载)
package singal;
// 静态内部类
public class Holder {
//构造函数私有化
private Holder(){
}
public static Holder getInstance(){ //只有你去调用内部类的时候才会加载初始化对象
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER =new Holder();
}
}
以上都是可以通过反射直接破坏单例模式的
枚举(反射和反序列化不能破坏的单例)(调用效率高,不能延时加载)
//由JVM从根本上提供保障
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Class<?> caller = override ? null : Reflection.getCallerClass();
return newInstanceWithCaller(initargs, !override, caller);
}
/* package-private */
T newInstanceWithCaller(Object[] args, boolean checkAccess, Class<?> caller)
throws InstantiationException, IllegalAccessException,
InvocationTargetException
{
if (checkAccess)
checkAccess(caller, clazz, clazz, modifiers);
if ((clazz.getModifiers() & Modifier.ENUM) != 0) //用枚举创建对象
throw new IllegalArgumentException("Cannot reflectively create enum objects");//用反射破坏枚举单例会报错
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(args);
return inst;
}
package singal;
import java.lang.reflect.Constructor;
/**
* 什么是枚举?一个被命名整数常数集合
**/
public enum EnumSingle { //这是一个枚举类
INSTANCE; //单例枚举对象
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle single1 = EnumSingle.INSTANCE;
//EnumSingle single2 = EnumSingle.INSTANCE;
//我们尝试反射创建一下对象 发现是不行的
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);//String.class,int.class是反编译出来构造参数
declaredConstructor.setAccessible(true);
EnumSingle single2 = declaredConstructor.newInstance();
System.out.println(single1);
System.out.println(single2);
}
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:493)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
at singal.Test.main(EnumSingle.java:28) <<<------ 控制台输出结果
单例模式的应用场景
- Windows的任务管理器就是典型的单例模式(不论你打开多少次任务管理器 只能打开一个)
- 应用程序的日志应用,因为要实现实时的动态更新,一般只能有一个实例去操作。
- 项目中读取配置文件的类,一般只有一个对象。(Properties加载DataSource)
- 数据库连接池也是采用单例模式
- spring的IOC容器中的每一个bean都是单例的这样能方便容器管理
- SpringMVC的控制器也是单例的
如何选用合适的单例模式?
- 单例对象占用资源少,不需要延迟加载 此时枚举好用于饿汉式
- 单例对象占用资源多,需要延时加载 静态内部类好用于懒汉式