Java单例模式的几种实现方式


一、"饿汉模式"

public class 饿汉模式 {
	public static void main(String[] args) {

		mySingleton theObject=mySingleton.getInstance();
		mySingleton theObject2=mySingleton.getInstance();
		System.out.println(theObject.hashCode()==theObject2.hashCode());//true
	}
		
}

class mySingleton {
	private static mySingleton theObject=new mySingleton();
	private mySingleton(){}
	public static mySingleton getInstance(){
	return theObject;
	}
}

使用static  在类加载时就完成对象实例的创建,之后返回同一对象  ,简单方便,但由于是立即加载的方式,难免会有些不必要的开销,比如此刻并不需要用到它

二、静态代码块("饿汉")

public class 静态代码块饿汉 {
	public static void main(String[] args) {

	mySingleton4 theObject=mySingleton4.getInstance();
	mySingleton4 theObject2=mySingleton4.getInstance();
	System.out.println(theObject.hashCode()==theObject2.hashCode());//true
	}
}


class mySingleton4 {
	private static mySingleton4 theObject=null;
	private mySingleton4(){}
	static{
		theObject=new mySingleton4();
	}
	public static mySingleton4 getInstance(){
	return theObject;
	}
}

由于静态代码块static也只进行一次,且在对象创建前执行,所以返回的也都是同一单例

三、"懒汉模式"

public class 懒汉模式 {
	public static void main(String[] args) {
		mySingleton2 theObject=mySingleton2.getInstance();
		mySingleton2 theObject2=mySingleton2.getInstance();
		System.out.println(theObject.hashCode()==theObject2.hashCode());
	}
}

class mySingleton2 {
	private static volatile mySingleton2 theObject=null;
	private mySingleton2(){}
	public static mySingleton2 getInstance(){
		if(theObject==null){
			synchronized (mySingleton2.class) {
				if(theObject==null)
					theObject=new mySingleton2();
			}
			
		}
		return theObject;
	}
}

延迟加载,在第一次用到此实例时,创建唯一对象,由于可能存在多线程安全问题,故采用DCL 双重检测锁机制,确保只创建返回一个实例(但锁同步需要开销,注意!DCL 可能会发送happen-before指令重排错误,需要对实例附加volatile !不推荐使用DCL)

四、静态内部类单例

public class 静态内部类单例 {
	public static void main(String[] args) {
		mySingleton3 myObject=mySingleton3.getInstance();
		mySingleton3 myObject2=mySingleton3.getInstance();
		System.out.println(myObject.hashCode()==myObject2.hashCode());
	}
}

class mySingleton3 {
	
	private static class SingletonHandler{
		private static mySingleton3 theObject=new mySingleton3();
	}
	private mySingleton3(){}
	
	public static mySingleton3 getInstance(){
		return SingletonHandler.theObject;
	}
}

使用静态的内部类进行延迟加载,在第一次用到本实例时,加载返回内部类的static 对象,解决了饿汉模式的不必要开销问题和懒汉模式的线程同步开销问题

五、序列化单例

public class 单例与序列化 {

	public static void main(String[] args) throws Exception{
		
			mySingleton5 myObject = mySingleton5.getInstance();
			FileOutputStream fosRef = new FileOutputStream(new File(
					"E://myObjectFile.txt"));
			ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
			oosRef.writeObject(myObject);
			oosRef.close();
			fosRef.close();
			System.out.println(myObject.hashCode());

	 		FileInputStream fisRef = new FileInputStream(new File(
					"E://myObjectFile.txt"));
			ObjectInputStream iosRef = new ObjectInputStream(fisRef);
			mySingleton5 myObject2 = (mySingleton5) iosRef.readObject();
			iosRef.close();
			fisRef.close();
			System.out.println(myObject2.hashCode());
	
	}
}


class mySingleton5 implements Serializable{
	
	private static class SingletonHandler{
		private static mySingleton5 theObject=new mySingleton5();
	}
	private mySingleton5(){}
	
	public static mySingleton5 getInstance(){
		return SingletonHandler.theObject;
	}
	protected Object readResolve() throws ObjectStreamException {
		System.out.println("调用了readResolve方法!");
		return SingletonHandler.theObject;
	}
}

由于反射机制和序列化,会在返回时调用构造器生成一个新的对象,故违反了单例的原则。且序列化时是不会与静态方法有关联的,就导致了存入文件和读取文件的对象,不是同一实例对象。解决方式是 在单例中实现 readResolve方法,返回静态的对象。

在ObjectInputStream 类的源码中,存在一个 readOrdinaryObject(boolean unshared) 方法,其中判断了序列化对象是否有readResolve() 方法,若有则会去进行调用,则可在此方法内返回我们所需要的单例对象。

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        //此处省略部分代码

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        //此处省略部分代码

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

关键在于 if 判断中desc.hasReadResolveMethod()  ,Object rep = desc.invokeReadResolve(obj);  去执行readResolve 返回创新obj 。

 关于单例与序列化可参考此引用博客:单例与序列化的那些事儿    深入分析Java的序列化与反序列化

六、枚举单例

public class enum枚举单例 {

	public static void main(String[] args) throws Exception{
		
			EnumSingleton hah=EnumSingleton.getInstance();
			EnumSingleton hah2=EnumSingleton.getInstance();
			
			mySingleton6 o1=hah.getSingleton6();
			mySingleton6 o2=hah2.getSingleton6();
			System.out.println(o1.hashCode()==o2.hashCode());
			
			EnumSingleton myObject = EnumSingleton.getInstance();
			FileOutputStream fosRef = new FileOutputStream(new File(
					"E://myObjectFile.txt"));
			ObjectOutputStream oosRef = new ObjectOutputStream(fosRef);
			oosRef.writeObject(myObject);
			oosRef.close();
			fosRef.close();
			System.out.println(myObject.getSingleton6().hashCode());
		 
			FileInputStream fisRef = new FileInputStream(new File(
					"E://myObjectFile.txt"));
			ObjectInputStream iosRef = new ObjectInputStream(fisRef);
			EnumSingleton myObject2 = (EnumSingleton) iosRef.readObject();
			iosRef.close();
			fisRef.close();
			System.out.println(myObject2.getSingleton6().hashCode());
		 
		
	}

}

enum EnumSingleton{
	SingletonHandler;
	class mySingleton6{
		private mySingleton6(){}
	}
	private mySingleton6 theObject=null;
	private EnumSingleton(){
		theObject=new mySingleton6();
	}
	public static EnumSingleton getInstance(){
		return SingletonHandler;
	}
	public mySingleton6 getSingleton6(){
		return theObject;
	} 
	
 }

采用枚举可能是最合适的一种实现单例的方式,即可以避免线程安全问题,又可以完成序列化与反序列化的事。无需自己手动添加readResolve()方法去返回。

一般使用静态内部类的方式(若需要序列化则再加上readResolve()方法)和枚举的方式,当然最好的选择是枚举方式。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值