单例模式学习笔记
去年有整理过一篇单例模式的博客单例模式 整理, 近期在看了学习一些资料后,有了一些新的心得
第一种 : 饿汉模式
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();
}
}
在构造方法进行判断,如果已经实例化过了,在调用构造方法进行实例,抛出异常.