1、懒汉式单例模式 ,线程不安全(不推荐使用)
private static LazyMan instance;
/**
* 私有构造器避免外部new
*/
private LazyMan() {
}
/**
* 1、单线程单例模式,多线程不适合
*
* @return
*/
public static LazyMan getInstance() {
//此处若多线程同时调用会产生多个实例,仅适用于单线程情况下
if (instance == null) {
instance = new LazyMan();
}
return instance;
}
2、懒汉式单例模式 ,线程安全(DCL双重锁检测机制)不完美!
private static LazyMan instance;
public static LazyMan getDouLockInstance() {
//第一次检测
synchronized (LazyMan.class) {
if (instance == null) { //第二次检测
instance = new LazyMan();
}
}
return instance;
}
这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new LazyMan()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情(new的时候jvm干了哪些事参阅JVM内存划分和new时JVM做了什么)。
1.给 instance 分配内存 2.调用 LazyMan 的构造函数来初始化成员变量 3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。 个人理解:多线程时候多个线程都在自己的栈空间内对instance进行判断是否为空,实际上可能别的线程已经新建了对象,只是当前线程的instance副本并不是最新的,从而导致多次初始化异常。若是能够多线程在判断instance是不是为空时从主存拿最新数据就能完美了,所以解决方案是: 我们只需要将 instance 变量声明成 volatile 就可以了。
//多线程&完美的懒汉模式
private volatile static LazyMan instance;
public static LazyMan getDouLockInstance() {
synchronized (LazyMan.class) {
if (instance == null) {
instance = new LazyMan();
}
}
return instance;
}
3、饿汉式 (线程安全,比较常用,但容易产生垃圾)
private static HungryMan instance = new HungryMan();
private HungryMan() {
}
public static HungryMan getInstance() {
return instance;
}</code></pre>
这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法也会生成实例,造成一定的资源浪费。
4、静态内部类实现(线程安全+懒汉式=最佳推荐模式)
private InnnerClass() {
}
//d对外公共提供实例的方法
public static InnnerClass getInstance() {
return InnerClassInner.instance;
}
//内部类是**私有的**,在其中实现new 实例,是懒汉式实现
private static class InnerClassInner {
private static InnnerClass instance = new InnnerClass();
}
这种写法仍然使用JVM本身机制保证了线程安全问题;由于 InnerClassInner 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。
5、枚举实现单例模式
public enum SingleEnum {
INSTANCE;
}
认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
枚举类隐藏了私有的构造器(String,int)。
枚举类的域 是相应类型的一个实例对象
但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。