设计模式: 单例

文章目录

  • 0. ref
  • 1. lazy 线程不安全的
  • 2. lazy 线程安全的
    • fix: 前面两个都没有加上的private static 前缀
      • 这样的话,第二个多线程安全的单例就是:
  • 3. lazy 双重校验锁
  • 4. eager, 加了volatile 的lazy 懒汉?
  • 5. 静态内部类,用了JVM内部的机制
  • 6. 枚举

0. ref

  1. 最好的设计模式 总结 博客

https://www.cnblogs.com/maowang1991/archive/2013/04/15/3023236.html

  1. hensen 设计模式速攻

https://blog.csdn.net/qq_30379689/article/details/77349786

1. lazy 线程不安全的

public class Singleton {
	Singleton s;
	private Singleton(){}; // rem
	public static Singleton getInstance () {
		if (s == null) {
			s = new Singleton();
		}
		return s; // rem
	}
}

2. lazy 线程安全的

public class Singleton {
	Singleton s;
	private Singleton(){}; // rem
	public static Singleton getInstance () {
		synchronize(Singleton.class) {
			if (s == null) {
				s = new Singleton();
			}
			return s; // 不要再这里返回吧
		}
	}
}

改正

public class Singleton {
	Singleton s;
	private Singleton(){}; // rem
	public static Singleton getInstance () {
		synchronize(Singleton.class) {
			if (s == null) {
				s = new Singleton();
			}
		}
		return s; // rem 在这里返回
	}
}

fix: 前面两个都没有加上的private static 前缀

应该让所有类实例都持有同一个单例对象(尽管我们的类实例的构造器是private的,但是也要遵循封闭式原则,如下:

Singleton s; 
改为:
private static Singleton s = null;

这样的话,第二个多线程安全的单例就是:

public class Singleton {
	private static Singleton s = null;
	private Singleton(){}; // rem
	public static Singleton getInstance () {
		synchronize(Singleton.class) {
			if (s == null) {
				s = new Singleton();
			}
		}
		return s; // 推荐 在这里返回
	}
}

3. lazy 双重校验锁

第二个方式的加锁太重量级了,也就是说,锁太大了,只要访问了getInstance()这个方法,就会进行加锁,就是会发生创建代码中的临界区,这个加锁的动作,会消耗一定的cpu指令周期,以至于程序整体的效率会因此(经常加锁)而降低。

于是,方案3来了: 就是在单例不存在的时候,才去新建,而且新建单例的时候,保持多线程安全。

代码如下:

public class Singleton {
	private static Singleton s = null;
	private Singleton(){};
	public static Singleton getInstance () {
		if (s == null) { // here 划重点!单例不存在的时候
			synchronize(Singleton.class) {// 而且新建单例的时候,加锁保持多线程安全
				if (s == null) {
					s = new Singleton();//才去新建
				}
			}
		}
		return s; // 推荐 在这里返回
	}
}

这个方式3 照他说的,

还是有点危险

在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton(); 这个语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间,然后(1)直接赋值给instance成员,然后再(2)去初始化这个Singleton实例,但是万一一个其他的线程B 在2还没做的时候进来,(eg 1.5)的时候,这样就可能出错了

我们以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断

  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();

  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。

  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。

  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

4. eager, 加了volatile 的lazy 懒汉?

非也,在一开始声明的时候就初始化, 这是有多饿

public class Singleton {
	//在一开始声明的时候就初始化
	private volatile static Singleton s = new Singleton();
	private Singleton(){};
	public static Singleton getInstance() {
		return s;
	}
}

5. 静态内部类,用了JVM内部的机制

用了 jvm 的特性:

JVM内部的机制能够保证: 当一个类被加载的时候,这个类的加载过程是线程互斥的

class OutterSingleton {
    private OutterSingleton() {}
    private static class SingletonFactory{
        private static OutterSingleton s = new OutterSingleton();
    }

    public static OutterSingleton getInstance() {
        return SingletonFactory.s;// 你可以访问内部类的private 
    }
}
public class singletonTestInnerPrivate{
    public static void main(String[] args) {
        OutterSingleton m = OutterSingleton.getInstance();
        System.out.println("done");
    }
}

6. 枚举

// version 1.6 
public enum EnumSingleton {
    INSTANCE;  

    public void doSomeThing() {  

    }  
}

这是利用了1.6 的新特性,请todo

你可能感兴趣的:(面试)