Java设计模式-单例模式

最近学习到设计模式,现总结个人学习单例模式内容。
上一篇:Java设计模式-原型模式

定义

一个类有且仅有一个实例,并且自行实例化向整个系统提供。

优缺点

优点

  • 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例,大对象的创建也是硬伤。
  • 避免对资源的多重占用(比如写文件操作,防止两个写操作,写入同一个文件中)解决不一致性。

缺点

  • 由于单例模式,不是抽象的所以可扩展性比较差
  • 单例类,职责过重,在一定程度上,违背了单一职责

实现

懒汉式

  • 私有构造【缺点:AccessibleObject.setAccessible方法,通过反射机制调用私有构造器】
  • 私有静态变量
  • 共享获取静态变量方法
    提前测试:通过反射暴力破解
Class  c = Class.forName("template.sing.impl.SingLazy1");
			Constructor con = c.getDeclaredConstructor();
			con.setAccessible(true); // 设置可访问为:true
			Object obj = con.newInstance();
			System.out.println(obj);
			System.out.println(SingLazy1.install());
			//template.sing.impl.SingLazy1@5cb08ba7 结果明显不一样
			//template.sing.impl.SingLazy1@4aa0b07b

第一种:常见方式

public class SingLazy1 {
	//静态变量保存单例的引用
	private static SingLazy1 sing = null;
	//私有化自己的构造函数
	private SingLazy1(){};
	//懒汉式单例类.在第一次调用的时候实例化自己
	public static SingLazy1 install(){
		if(sing == null) {
			sing = new SingLazy1();
		}
		return sing;
	}
}

测试:构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,这个单列的实例化只能通过install方法进行实例化,但是没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个SingLazy1的 实例。
在这里插入图片描述
简单多线程测试:发现这里就明显指向了两个实例。

在这里插入图片描述

第二种方式:方法上加同步锁

public class SingLazy2 {
	private SingLazy2(){};//同SingLazy1
	private static SingLazy2 sing=null;
	//给方法加上同步锁synchronized,保证多线程环境下正确访问,
	public static synchronized  SingLazy2 install(){
		if(sing == null) {
			sing = new SingLazy2();
		}
		return sing;
	}
}

测试:基于第一种方式实现添加线程安全。多次测试基本上没有了第一种的情况。但是对整个方法加上了线程同步,效率比较低,性能不是太友好。

第三种方式:方法内加同步锁的方式

//双重检查,较常用
public class SingLazy3 {
	private SingLazy3(){};//同SingLazy1
	private static SingLazy3 sing=null;
	//给方法加上同步锁synchronized,保证多线程环境下正确访问,
	public static   SingLazy3 install(){
		if(sing == null) {//第一重检查
			synchronized(SingLazy3.class){
				if(sing == null) {//第一重检查
					sing = new SingLazy3();
				}
			}
		}
		return sing;
	}
}

在方法内部使用同步锁,而且采用双重检查,只锁定创建对象的时候,使同步更加友好,确保同步单例对象。

饿汉式

第一种方式:对象加载就创建静态成员变量并初始化单例对象【线程安全】

public class SingHungry1 {
	//1、默认构造方法私有化
	private SingHungry1() {}
	//2、声明静态变量,将对象引用保存,在类实例化之前就初始化变量,
	private static final SingHungry1 single = new SingHungry1();  
	//3、获取保存的单列实例
	public static SingHungry1 getInstance() {  
	    return single;  
	}  
}

饿汉和懒汉对比

饿汉就是类一旦加载,就把单例初始化完成,保证install的时候,单例是已经存在的了。
懒汉比较懒,只有当调用install的时候,才回去初始化这个单例。
另外从以下两点再区分以下这两种方式:
1、线程安全:
饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,
懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别有懒汉的第二2,第三3中实现方法。
2、资源加载和性能:
饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,在第一次调用时速度也会更快,因为其资源已经初始化完成,
而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

枚举单例【推广,推广理由百度一大堆】

通过反射不能破解,且线程安全。

public enum  SingEnum {
	SENUM;
	public SingEnum getInstance(){
        return SENUM;
    }
	//TODO 业务start
	public void test(){
		System.out.println((Object)this.getClass());
	}
	//TODO 业务end
}

登记式单例【注册机】

当前类的注册

public class SingRegist1 {
	 private static Map<String,SingRegist1> map = new HashMap<String,SingRegist1>();  
	 static{  
		 SingRegist1 single = new SingRegist1();  
	    	map.put(single.getClass().getName(), single);  
	 } 
	 //保护的默认构造子  
	 private SingRegist1(){}  
	//静态工厂方法,返还此类惟一的实例  
	public static SingRegist1 getInstance(String name) {  
		if(name == null) {
			name = SingRegist1.class.getName(); 
		}
		if(map.get(name) == null) {
			try {
				  map.put(name, (SingRegist1) Class.forName(name).newInstance()); 
			} catch (Exception e) {
				e.printStackTrace();
			}   
		}  
		return map.get(name);  
	}  
	
	public static void main(String[] args) {
		SingRegist1 s1 = SingRegist1.getInstance("template.sing.impl.SingRegist1");
		System.out.print(s1);
	}
}

用于其他类的登记注册

public class SingRegist {
	 private static Map<String,Object> map = new HashMap<String,Object>();  
	 static{  
	    	SingRegist single = new SingRegist();
	    	map.put(single.getClass().getName(), single);//优先注册自己  
	 } 
	 //保护的默认构造子  
	private   SingRegist(){}  
	//静态工厂方法,返还此类惟一的实例  
	public static Object getInstance(String name) {  
		if(name == null) {
			name = SingRegist.class.getName(); 
		}
		if(map.get(name) == null) {
			try {
				Constructor con = Class.forName(name).getDeclaredConstructor();
				con.setAccessible(true); 
				map.put(name, con.newInstance());//默认构造器
			} catch (Exception e) {
				e.printStackTrace();
			}    
		}  
		return map.get(name);  
	}  
}

测试

在这里插入图片描述


日常对单例使用,优先推荐枚举【简单,线程安全】的方式,其次采用懒汉双重验证【使用的时候初始化】,最后登记模式适用于各种标准制定内部使用。。

以上仅为个人学习,如果错误望指出,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值