设计模式学习笔记--单例模式

单例模式学习笔记

去年有整理过一篇单例模式的博客单例模式 整理, 近期在看了学习一些资料后,有了一些新的心得

第一种 : 饿汉模式

class  Singleton2{
	private static  final Singleton2 INSTANCE = new Singleton2();
	private Singleton2(){}
	public static Singleton2 getInstance(){
		return INSTANCE;
	}
}

static 修饰意义:在类加载时就进行初始化单例

final 修饰意义; 防止反射修改单例实例,虽然是私有private修饰词,但是反射机制是可以调用到的,前面博文有讲过Class.forName()和xxx.class,下面会提到如何防止反射破坏单例

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅坑不拉屎。
Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例。

 第二种: 懒汉模式

//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
  private LazySimpleSingleton(){}
  //静态块,公共内存区域
  private static LazySimpleSingleton lazy = null;
  public static LazySimpleSingleton getInstance(){
    if(lazy == null){
       lazy = new LazySimpleSingleton();
     }
     return lazy;
  }
}

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。是线程不安全

我们可以通过加锁的方式使之线程安全

如下,双重加锁

public class LazyDoubleCheckSingleton {
   private volatile static LazyDoubleCheckSingleton lazy = null;
   private LazyDoubleCheckSingleton(){}
   public static LazyDoubleCheckSingleton getInstance(){
     if(lazy == null){
       synchronized (LazyDoubleCheckSingleton.class){
          if(lazy == null){
             lazy = new LazyDoubleCheckSingleton();
              //1.分配内存给这个对象
              //2.初始化对象
              //3.设置 lazy 指向刚分配的内存地址
          }
       }
     }
     return lazy;
   }
}

这样就没有开始的线程不安全问题

之所以进行两次null的判断,是为了提高效率,毕竟竞争锁也是需要浪费资源的,但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。

第三种:静态内部类

//这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题
//完美地屏蔽了这两个缺点
public class LazyInnerClassSingleton {
   //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
   //如果没使用的话,内部类是不加载的
   private LazyInnerClassSingleton(){}
   //每一个关键字都不是多余的
   //static 是为了使单例的空间共享
   //保证这个方法不会被重写,重载
   public static final LazyInnerClassSingleton getInstance(){
   //在返回结果以前,一定会先加载内部类
      return LazyHolder.LAZY;
   }
   //默认不加载
   private static class LazyHolder{
      private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
   }
}

但是上面这几种创建单例是在不考虑反射的情况下来说,虽然这些构造fang方法都是私有private修饰(如果不是私有修饰构造,那就谈不上什么单例,在外面随便可以调用了),但是反射是可以调用私有方法的,这个在去年的博客中也有提到

 

看看反射破坏单例:

public class LazyInnerClassSingletonTest {
   public static void main(String[] args) {
       try{
		   //很无聊的情况下,进行破坏
		   Class<?> clazz = LazyInnerClassSingleton.class;
		   //通过反射拿到私有的构造方法
		   Constructor c = clazz.getDeclaredConstructor(null);
		   //强制访问
		   c.setAccessible(true);
		   //暴力初始化
		   Object o1 = c.newInstance();
		   //调用了两次构造方法,相当于 new 了两次
		   //犯了原则性问题,
		   Object o2 = c.newInstance();
		   System.out.println(o1 == o2);
			// Object o2 = c.newInstance();
	  }catch (Exception e){
		e.printStackTrace();
	  }
   }
}

如何来防止反射破坏单列呢?

我们可以从构造方法下手,限制创建多个单例

我们队静态内部类进行优化限制,代码如下

//史上最牛 B 的单例模式的实现方式
public class LazyInnerClassSingleton {
	//默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
	//如果没使用的话,内部类是不加载的
	private LazyInnerClassSingleton(){
	   if(LazyHolder.LAZY != null){
	      throw new RuntimeException("不允许创建多个实例");
	   }
	}
	//每一个关键字都不是多余的
	//static 是为了使单例的空间共享
	//保证这个方法不会被重写,重载
	public static final LazyInnerClassSingleton getInstance(){
	  //在返回结果以前,一定会先加载内部类
	   return LazyHolder.LAZY;
	}
	//默认不加载
	private static class LazyHolder{
	   private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}
}

在构造方法进行判断,如果已经实例化过了,在调用构造方法进行实例,抛出异常.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值