单例模式,是指确保一个类在任何情况下都只有一个实例,并提供一个全局访问点。单例模式就是创建型模式、
1.饿汉式单例模式
饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。他绝对线程安全,在线程还没有出现的时候就已经初始化了,不可能存在任何访问安全问题
优点:没有加任何锁,执行效率比较高,用户体验比懒汉式单例要好
缺点:类加载的时候就初始化,不管用不用都占者空间,浪费内存
示例如下:
public class HungrySinglenton {
private static final HungrySinglenton hungry = new HungrySinglenton();
private HungrySinglenton() {}
public static HungrySinglenton getInstance() {
return hungry;
}
}
或:
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungry;
static {
hungry = new HungryStaticSingleton();
}
private HungryStaticSingleton() {}
public static HungryStaticSingleton getInstance() {
return hungry;
}
}
2.懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载
示例如下:
public class LazySimpleSingleton {
private LazySimpleSingleton() {}
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance() {
if(lazy==null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
但此种方式有一定概率出现线程安全问题针对这点进行优化
public class LazySimpleSingleton {
private LazySimpleSingleton() {}
private volatile static LazySimpleSingleton lazy = null;
public static synchronized LazySimpleSingleton getInstance() {
if(lazy==null) {
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
线程安全问题虽然解决但是对方法使用synchroized在线程数量较多的情况下会造成大量阻塞针对此点再进行优化
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton () {}
public static LazyDoubleCheckSingleton getInstance() {
if(lazy == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if(lazy == null) {
lazy=new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
在方法内部双重判断加锁的方式极大的优化了性能但是使用synchronized关键字始终都是要加锁下面给出优化方案
public class LazyInnerClassSingleton {
//使用LazyInnerClassSingleton类的时候默认要加载内部类
//如果没使用默认是不加载的
private LazyInnerClassSingleton() {};
public static final LazyInnerClassSingleton getInstance() {
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
3.反射破坏单例
通过反射对代码进行测试
测试如下
public class LazyInnerClassSingletonTest {
public static void main(String args[]) {
try {
//进行破坏
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射获取私有构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object o1 = c.newInstance();
//初始化了两次
Object o2 = c.newInstance();
System.out.println(o1==o2);
} catch (Exception e) {
// TODO: handle exception
}
}
}
结果如下:
false
显然我们创建了两个不同的对象,所以应该对构造方法做一些限制,一旦出现重复创建,直接抛出异常
下面是最终版本
public class LazyInnerClassSingleton {
//使用LazyInnerClassSingleton类的时候默认要加载内部类
//如果没使用默认是不加载的
private LazyInnerClassSingleton() {
if(LazyHolder.LAZY != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static final LazyInnerClassSingleton getInstance() {
//在返回结果前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
4.序列化破坏单例
//反序列化导致破坏单例模式
public class SerializableSinglenton implements Serializable {
//序列化就是把内存中的状态转换成字节码的形式
//从而转换一个I/O流,写入其他地方(磁盘或网络I/O)
//内存中的状态会永久保存下来
//反序列化就是将已经持久化的字节码转换为I/O
//通过I/O读取,将读取的内容转换为Java对象
//在转换的过程中会重新创建对象new
public static final SerializableSinglenton INSTANCE = new SerializableSinglenton();
private SerializableSinglenton() {}
public static SerializableSinglenton getInstance() {
return INSTANCE;
}
}
进行序列化测试
public class SerializableSinglentonTest {
public static void main(String args[]) {
SerializableSinglenton s1 = null;
SerializableSinglenton s2 = SerializableSinglenton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SerializableSinglenton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SerializableSinglenton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SerializableSinglenton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
} catch (Exception e) {
// TODO: handle exception
}
}
}
测试结果
SerializableSinglenton.SerializableSinglenton@776ec8df
SerializableSinglenton.SerializableSinglenton@5c647e05
false
解决方法是
public class SerializableSinglenton implements Serializable {
//序列化就是把内存中的状态转换成字节码的形式
//从而转换一个I/O流,写入其他地方(磁盘或网络I/O)
//内存中的状态会永久保存下来
//反序列化就是将已经持久化的字节码转换为I/O
//通过I/O读取,将读取的内容转换为Java对象
//在转换的过程中会重新创建对象new
public static final SerializableSinglenton INSTANCE = new SerializableSinglenton();
private SerializableSinglenton() {}
public static SerializableSinglenton getInstance() {
return INSTANCE;
}
private Object readResolve() {
return INSTANCE;
}
}
通过重写readResolve方法解决。但是这种解决方式在底层代码中依然被实例化了两次,只不过新创建的对象没有被返回。如果创建对象的频率加大,也就意味着内存分配的开销也会随之增大
5.注册式单例模式
注册式单例模式又称为登记式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例模式有两种:一种为枚举模式,另一种为容器模式
1.枚举式单例模式
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
序列化测试
public class EnumSingletonTest {
public static void main(String args[]) {
EnumSingleton s1 = null;
EnumSingleton s2 = EnumSingleton.getInstance();
s2.setData(new Object());
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(s1.getData());
System.out.println(s2.getData());
System.out.println(s1.getData()==s2.getData());
} catch (Exception e) {
// TODO: handle exception
}
}
}
测试结果
java.lang.Object@4eec7777
java.lang.Object@4eec7777
true
因为枚举类型在JDK底层源码层面就已经做了优化所以我们不用考虑枚举类型会被反射及序列化破坏单例
2.容器式单例
public class ContainerSingleton {
private ContainerSingleton() {}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getBean(String className) {
synchronized (ioc) {
if(!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return obj;
}else {
return ioc.get(className);
}
}
}
}
容器式单例适用于实例非常多的情况便于管理,但它是非线程安全的
Spring的IOC中用容器式单例情况较多