JAVA 设计模式之单例模式

一 单例模式

(一)描述

1、一个类只有一个实例。

2、只能由自己实例化自己,私有化构造器。

3、提供一个全局的方法为其他对象提供自己的实例。

(二)如何实现

1、饱汉式(懒汉)

一般写法:

public class Singleton {

	private static Singleton singleton = null;

	public static Singleton getInstance() {
		if (null == singleton)
			singleton = new Singleton();
		return singleton;
	}

	private Singleton() {

	}
}

在单线程模式下,不会出现问题,但是在多线程情况下,就会new多个实例。

public class Test {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new Thread(() -> System.out.println(Singleton.getInstance().hashCode())).start();
		}
	}
}

//运行结果如下:
1244690221
944921576
1124920156
1124920156
1124920156
1124920156
944921576
944921576
1124920156
944921576

 可以看出,当有在多线程环境下,实例的hashcode是存在多个的。

为了保证只存在一个实例,可以在该方法上进行加锁,并且给实例对象加上volatile(jdk1.5)修饰,禁止编译器指令重排序进行优化:

public class Singleton {

	private static volatile Singleton singleton = null;

	public static Singleton getInstance() {
		synchronized (Singleton.class) {
			if (null == singleton)
				singleton = new Singleton();
		}
		return singleton;
	}

	private Singleton() {

	}
}

//测试结果
944921576
944921576
944921576
944921576
944921576
944921576
944921576
944921576
944921576
944921576

可见只存在一个实例,因为同步加锁,效率比较低。

这里就引入了双重校验来提高效率:

public class Singleton {

	private static volatile Singleton singleton = null;

	public static Singleton getInstance() {
		if (null == singleton) {
			synchronized (Singleton.class) {
				if (null == singleton)
					singleton = new Singleton();
			}
		}
		return singleton;
	}

	private Singleton() {

	}
}

因为在加锁前进行了判断,而不需要无脑加锁,提示效率。

 

2、饿汉式

public class Singleton {

	private static volatile Singleton singleton = new Singleton();

	public static Singleton getInstance() {
		return singleton;
	}

	private Singleton() {

	}
}

因为在类加载时创建实例对象, 就不存在多线程问题;但是如果在重量级应用中,可能会消耗大量内存,又可能因为长时间不实用,被gc回收,占用资源。

 

3、使用静态内部类

public class Singleton {

	private static class SingletonChildre {
		private static Singleton instance = new Singleton();
	}

	public static Singleton getInstance() {
		return SingletonChildre.instance;
	}

	private Singleton() {

	}
}

和饿汉式有点类似,但是却和饿汉式有个本质的区别,静态内部类在外部类被加载时,并不会同时被加载。所以比较推荐此方法。

 

以上方法如果反序列化,则会生成一个新的实例:

public class Singleton implements Serializable {

	private static final long serialVersionUID = 1770120889063120344L;

	private static class SingletonChildre {
		private static Singleton instance = new Singleton();
	}

	public static Singleton getInstance() {
		return SingletonChildre.instance;
	}

	private Singleton() {

	}

}

//测试类
public class Test {
	public static void main(String[] args) {
		Singleton singleton = Singleton.getInstance();
		System.out.println(singleton.hashCode());

		ByteArrayOutputStream baos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bais = null;
		ObjectInputStream ois = null;
		try {
			baos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(baos);
			oos.writeObject(singleton);
			bais = new ByteArrayInputStream(baos.toByteArray());
			ois = new ObjectInputStream(bais);

			Singleton singleton_new = (Singleton) ois.readObject();
			System.out.println(singleton_new.hashCode());
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			if (null != ois) {
				try {
					ois.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != oos) {
				try {
					oos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}

//测试结果
2018699554
2003749087

 可以看出生成了一个新的实例,但是如果添加一个readResolve()方法,则会有不一样的结果:

public class Singleton implements Serializable {

	private static final long serialVersionUID = 1770120889063120344L;

	private static class SingletonChildre {
		private static Singleton instance = new Singleton();
	}

	public static Singleton getInstance() {
		return SingletonChildre.instance;
	}

	private Singleton() {

	}

	private Object readResolve() {
		System.out.println("======readResolve=======");
		return getInstance();
	}
}


//测试结果
2018699554
======readResolve=======
2018699554

可以看出反序列化得到的对象仍然是同一个实例。

 readResolve 详见:

https://blog.csdn.net/u014653197/article/details/78114041

 

4、使用枚举

前面三种方式都有两个相同之处:

a、需要实现序列化,否则反序列化后都是一个新的实例

b、可以通过反射强制创建一个新的实例

但是使用枚举,则不存在这个问题。

public enum SingletonEnum {

	INSTANCE;

	private static int aIntaeger = 0;

	public void doSomething() {
		aIntaeger++;
		System.out.println("Something");
		System.out.println(aIntaeger);
	}
}

不仅提供了自动序列化和反序列化不创建新的对象,而且是线程安全,防止被反射;保证单例。

参考:https://www.cnblogs.com/cielosun/p/6596475.html

 

(三)应用场景

1、sping默认创建的bean都是单例模式

2、常见的工具类

3、数据库连接池

4、资源共享在内存中

5、频繁实例化然后销毁的对象

 

(四)优缺点

优点:

1、避免对资源的多重占用,比如文件操作。

2、减少内存开支,减少系统频繁创建实例,提示性能。

缺点:

1、单例模式一般没有接口,扩展难。

2、单例类职责过重,违背了“单一职责原则”。

3、可能会导致内存溢出。

参考:

http://www.importnew.com/18872.html

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值