单例模式

一、定义

    它是一种对象创建模式,用于创建对象的一个具体实例,它可以确保某个类只产生一个实例。

二、目的

    (1)节省频繁创建新对象所花费的时间,尤其对于重量级对象而言。
    (2)new操作次数减少,对于堆内存的使用也减少,并且有利于减轻垃圾回收的压力。
      因此,对于系统中频繁使用的对象,使用单例模式可以有效地改善系统性能。

三、实现

(1)最简单的方式:

//没有实现延迟加载
public class LearnSingleton0 {
	private static LearnSingleton0 obj=new LearnSingleton0();
	private LearnSingleton0(){
		System.out.println("创建实例");
	}
	public static LearnSingleton0 getInstance() {
		return obj;
	}
	public static void print() {
		System.out.println("用于展示,单例对象没有延迟加载");
	}
	public static void main(String[] args) {
		LearnSingleton0.print();
	}
}
运行结果:
创建实例
用于展示,单例对象没有延迟加载
      
    首先,构造函数必须是私有的,从而保证,无法在外部,通过new来创建实例。其次,实例变量obj必须是static的,getInstance方法也必须是静态的。
    这种方法虽然简单,但是,缺点也很明显,即没有对obj实例做延迟加载,从运行结果即看以看出,当我们调用print函数的时候,我们本不需要创建一个实例,但是它还是创建了。为了提高系统在相关函数调用时的响应速度,我们要引入延迟加载机制:

(2)引入延迟加载:

//延迟加载,但是使用了同步,性能降低
public class LearnSingleton {
	private static LearnSingleton obj=null;
	private LearnSingleton(){}
	public static synchronized LearnSingleton getInstance() {
		if(obj==null){
			obj=new LearnSingleton();
		}
		return obj;
	}
}
    首先,静态成员变量obj置为空,从而确保系统启动时没有额外的负载。其次,getInstance方法中,判断obj是否为空,从而决定是否要创建对象,保证了单例。最后需要注意的是synchronized关键字,令这个方法是同步的,否则在多线程的环境下,当线程1正在创建单例,而还没有对obj完成赋值操作时,线程2可能判断obj为null,从而线程2也将新建一个对象。
   虽然,这个方法引入了延迟加载,但是,与此同时也引入了同步关键字,因此在多线程的环境中,它的时间复杂度远远大于第一种单例模式。下面介绍一种更完善的实现方法,既可以做到延迟加载,也不必使用同步关键字:

(3)使用内部类:

//使用内部类,即实现了延迟加载,又能对多线程友好
public class LearnSingleton2 {
	private LearnSingleton2(){}
	
	private static class LearnSingletonHolder{
		private static LearnSingleton2 obj=new LearnSingleton2(); 
	}
	public static LearnSingleton2 getInstance(){
		return LearnSingletonHolder.obj;
	}
}

四、特例

    通常情况下,以上方法可以保证系统中存在单例类唯一的实例。但是,仍然有例外情况,可能导致系统生成单例类的多个实例,下面我们介绍两种情况:
(1)反射机制,强行调用单例类的私有构造函数:
public class LearnSingleton0 {
	private static LearnSingleton0 obj=new LearnSingleton0();
	private LearnSingleton0(){
		System.out.println("创建实例");
	}
	public static LearnSingleton0 getInstance() {
		return obj;
	}
	public static void print() {
		System.out.println("用于展示,单例对象没有延迟加载");
	}
	public static void main(String[] args) throws Exception {
		Class<?> testClass=Class.forName("pkgOne.LearnSingleton0");
		Constructor constructor=testClass.getDeclaredConstructor();
		LearnSingleton0 newInstance=(LearnSingleton0) constructor.newInstance();
		System.out.println("旧实例:"+getInstance());
		System.out.println("新实例:"+newInstance);
	}
}
    通过输出看以看出两个实例的地址不同,是不同的实例。

(2)序列化和反序列化的单例类实例:
//单例模式的序列化和反序列化
public class LearnSingleton3 {
	public static void main(String[] args) throws Exception {
		Fruit fruit=null;
		Fruit fruit2=Fruit.getInstance();
		FileOutputStream outputStream=new FileOutputStream("e:/123.txt");
		ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
		objectOutputStream.writeObject(fruit2);
		objectOutputStream.flush();
		objectOutputStream.close();
		outputStream.close();
		FileInputStream inputStream=new FileInputStream("e:/123.txt");
		ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
		fruit=(Fruit)objectInputStream.readObject();
		System.out.println("fruit:"+fruit);
		System.out.println("fruit2:"+fruit2);
	}

}

class Fruit implements Serializable{
	private static final long serialVersionUID = 6810712581273812115L;
	private Fruit(){
		System.out.println("创建单例fruit");
	}
	
	private static class FruitHolder{
		private static Fruit fruit=new Fruit();
	}
	public static Fruit getInstance() {
		return FruitHolder.fruit;
	}
}
运行结果:
创建单例fruit
fruit:pkgOne.Fruit@1de17f4
fruit2:pkgOne.Fruit@b8f8eb

    由此可见,经过序列化和反序列化后,得到的是两个对象。为了解决这个问题,我们可以加入一个函数readResolve解决,如下:
public class LearnSingleton3 {
	public static void main(String[] args) throws Exception {
		Fruit fruit=null;
		Fruit fruit2=Fruit.getInstance();
		FileOutputStream outputStream=new FileOutputStream("e:/123.txt");
		ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
		objectOutputStream.writeObject(fruit2);
		objectOutputStream.flush();
		objectOutputStream.close();
		outputStream.close();
		FileInputStream inputStream=new FileInputStream("e:/123.txt");
		ObjectInputStream objectInputStream=new ObjectInputStream(inputStream);
		fruit=(Fruit)objectInputStream.readObject();
		System.out.println("fruit:"+fruit);
		System.out.println("fruit2:"+fruit2);
	}

}

class Fruit implements Serializable{
	private static final long serialVersionUID = 6810712581273812115L;
	private Fruit(){
		System.out.println("创建单例fruit");
	}
	
	private static class FruitHolder{
		private static Fruit fruit=new Fruit();
	}
	public static Fruit getInstance() {
		return FruitHolder.fruit;
	}
	//实现这个方法,从而使readObject形同虚设,从而保证了单例模式经过序列化和反序列化后,
	//仍然返回同一个对象实例
        private Object readResolve(){
		return FruitHolder.fruit;
	}
}






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值