一 单例模式
(一)描述
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