package com.imooc.design.singleton;
/**
* 非线程安全懒汉式
*
* 弊端:当多个线程同时调用getInstance时,都检测到singleton位null,然后就开始创建对象了。
* 这时候就会创建多个实例,而不是一个了。
*/
public class SingletonLazy {
private static SingletonLazy singleton = null;
//私有化构造函数,防止创建多个
private SingletonLazy() {}
public static SingletonLazy getInstance() {
if(singleton == null) {
singleton = new SingletonLazy();
}
return singleton;
}
}
package com.imooc.design.singleton;
/**
* 使用Synchronized 控制线程安全的懒汉式
* 弊端:使用synchronized关键字的话,在多线程的环境下,每次只有一个线程能够进入getInstance方法,
* 其他线程必须等待该线程释放类锁才能竞争获取类锁的机会,所以大量线程下,效率很慢。如果调用的次数不多,这种方法还是可以的。
*/
public class SingletonLazy_Synchronized {
private static SingletonLazy_Synchronized singleton;
private SingletonLazy_Synchronized() {}
public static synchronized SingletonLazy_Synchronized getInstance() {
if(singleton == null) {
singleton = new SingletonLazy_Synchronized();
}
return singleton;
}
}
package com.imooc.design.singleton;
/**
* 双重校验锁不使用volatile关键字
* 解析:第一层判断是为了对象已经创建好了,1延迟实例化,需要时才创建 2就不用再去竞争类锁执行创建对象,减少了等待锁的过程;
* 第二层判断是为了:防止第一层判断后,已经有多个线程在竞争等待类锁,这个时候,内部需要再加上一层判断,
* 防止后面获得类锁的线程进入同步代码块还执行对象实例的创建。
*
* 弊端:某些线程可能会获得一个不完全构造的对象。当一个线程A运行到第二层判断后,
*
* 对象构造过程的JVM解释:
*
* 对于对象构造的一行代码obj = new Object();可以分解成3个步骤运行:
* memory = allocate();//1.分配对象的内存空间
* ctorInstance(memory);//2.调用构造方法进行初始化
* Instance = memory;//3.设置instance指向刚分配的内存
* 但是JVM编译器存在指令重排序的优化,最终的执行顺序是1-2-3或1-3-2,如果是1-3-2的话,那么在3执行完,2执行前,
*
* 由于instance 已经有了地址,
*
* 所以到程序的第三步的时候,singleton已经有了值,A会释放类锁,第二个线程B进入了同步代码块,判断singleton非空,
* 直接返回singleton,这个时候,对于直接使用该实例的B线程,就会报错。其他的线程可能报错也可能不报错。
*
*/
public class SingletonLazy_DoubleCheck {
private static SingletonLazy_DoubleCheck singleton = null;
private SingletonLazy_DoubleCheck(){}
public static SingletonLazy_DoubleCheck getInstance() {
if(singleton == null) { //1层判断
synchronized (SingletonLazy_DoubleCheck.class) {
if(singleton == null) {//2层判断
singleton = new SingletonLazy_DoubleCheck();//第三步
}
}
}
return singleton;
}
}
package com.imooc.design.singleton;
/**
* 双重检查锁:使用volatile的关键字
* 《深入理解Java虚拟机》:
*
* 第一:保证被volatile修饰的变量会保证对所有的线程的可见性,
* 这里的“ 可见性 ”是指当一条线程修改了这个变量的值,新值对于其他变量是可以立即i得知的。
* 第二:使用volatile变量的语意是禁止指令重排序优化,
* 普通的变量仅仅会保证该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,
* 而不能保证变量赋值操作顺序与程序代码中的执行顺序一致。
*
*弊端:
* 在使用volatile的同时使我们的代码不能被编译器进行代码优化,
* 他需要在本地代码中插入许多的内存屏障指令来保证处理器不发生乱序执行,
* 导致我们的程序在执行的时候变慢。。。。。。。。。。。
*/
public class SingletonLazy_DoubleCheck_Volatile {
private volatile static SingletonLazy_DoubleCheck_Volatile singleton = null;//禁止指令重排序
private SingletonLazy_DoubleCheck_Volatile() {}
public static SingletonLazy_DoubleCheck_Volatile getInstance() {
if(singleton == null) {
synchronized(SingletonLazy_DoubleCheck_Volatile.class) {
if(singleton == null) {
singleton = new SingletonLazy_DoubleCheck_Volatile();
}
}
}
return singleton;
}
}
package com.imooc.design.singleton;
/**
* 饿汉模式
*
* 这种方式使用静态实例解决了多线程安全问题。
* 弊端: 就算没有调用getInstance方法,还是会产生这个类的实例。
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {}
public static SingletonHungry getInstance() {
return instance;
}
}
package com.imooc.design.singleton;
/**
* 静态内部类
*
* 这种方式里也是利用了classloder的机制来保证初始化intance时只有一个线程,它跟普通的恶汉式的区别:
* 普通的恶汉式是只要类被装载了,那么就会实例化(没有达到懒加载的效果),而这种方式就算类被初始化了,
* INSTANCE也不一定会被初始化,因为SingletonHolder类没有被主动使用,只有显示调用getInstance方法
* 才会显示的装载SingletonHolder类,从而实例化INSTANCE。
*
*跟普通恶汉比较的优点:1.如果实例化instance很消耗资源,那么可以懒加载;
* 2.不希望在类加载的时候就实例化instance,不能确保类SingletonHungry_StaticInnerClass会
* 在其他的地方被主动使用从而被加载。
*
*/
public class SingletonHungry_StaticInnerClass {
private static class SingletonHolder {
private static final SingletonHungry_StaticInnerClass INSTANCE = new SingletonHungry_StaticInnerClass();
}
private SingletonHungry_StaticInnerClass() {}
public static final SingletonHungry_StaticInnerClass getInstance() {
return SingletonHolder.INSTANCE;
}
}
package com.imooc.design.singleton;
/**
* 神一样的设计:
* 1.自由序列化
* 2.线程安全
* 3.只有一个实例
*/
public enum Singleton_Enum {
INATANCE;
private Singleton_Enum() {
}
public void hello() {
System.out.println("hello");
}
}
有两个问题需要注意:
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
- private static Class getClass(String classname)
- throws ClassNotFoundException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- if(classLoader == null)
- classLoader = Singleton.class.getClassLoader();
- return (classLoader.loadClass(classname));
- }
- }
对第二个问题修复的办法是:
- public class Singleton implements java.io.Serializable {
- public static Singleton INSTANCE = new Singleton();
- protected Singleton() {
- }
- private Object readResolve() {
- return INSTANCE;
- }
- }