最近学习到设计模式,现总结个人学习单例模式内容。
上一篇: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);
}
}
测试
日常对单例使用,优先推荐枚举【简单,线程安全】的方式,其次采用懒汉双重验证【使用的时候初始化】,最后登记模式适用于各种标准制定内部使用。。
以上仅为个人学习,如果错误望指出,谢谢。