单例模式:单例模式是什么?有啥用?什么时候用?为什么要用?怎样用?(先来一波素质五连)
是什么?
单例模式?字面上理解,单:一个,例:实例,也就是我们所说的对象
有啥用?
是为了资源的重复利用,只需要赋值或者初始化一次,大家就都能重复使用
什么时候用?为什么要用?(只有一份的如日历,只需要一份的如IOC容器)
Listener 监听器,Calender 日历类,IOC容器类,配置信息Config(一搬是单例)这些都是单例的
怎样用?(这个一句话说不了,容我细细道来)
下面我将用饿汉式,懒汉式,最牛逼单例模式,枚举,注册登记式,反序列化如何保证单例 等单例模式的运用形式进行详细说明
- 饿汉式(有处明显错误导致无法实现单例,答案暗藏在后文中)
什么叫饿汉式?我的理解就是,饿了就迫不及待想吃,看下面第9行代码,声明为
private static final:说明类加载时就初始化就初始化对象,然后静态的,所以对象一直保存,直到程序停止
缺点:自然就是无论你用不用这个对象,在加载这个类的时候他都给你初始化了,都会占用空间,直到程序停止
优点:很明显,因为它在类被加载时就初始化,所以不存在对象创建时的并发问题,也就是不存在线程安全问题,可以保证单例,而且效率也算不错,因为当创建对象时需要的那几纳秒省了
建议应用场景:如果某个类的对象只需要一份,并且使用非常频繁,可以使用这种模式
- 懒汉式(明说了,答案就在下图)
什么是懒汉式?我的理解就是,懒人特点了解下,什么事情都不急着提前准备,需要用时才去准备
我们可以看到,懒汉模式下的对象,我们可以在需要时再调用方法得到,并且能保证单例(能保证?)下面就来在优缺点里继续分析
优点:避免了内存的浪费,需要对象时才获得,而且非高并发时基本能保证单例
缺点:很明显,上面都说了非高并发时能保证单例,那么高并发时不能保证一定单例, 怎样解决呢,在方法前面加个synchronized关键字(如果对性能要求不高的话可加,毕竟同步锁要等待,不懂的可以去了解下同步锁synchronized)
- 既然上面或多或少都有缺点,下面再来个懒汉式改进版(也就是标题的最牛逼版,请不要叫我标题档...)
优点:兼顾了饿汉模式的内存占用问题与synchronized的性能问题(性能我有测试,创建200W个对象速度跟饿汉模式基本没区别),测试代码就不贴了,自己动手试试
缺点:欢迎留言
注册式单利
注册式单例模式又称为等级式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例i模式有两种:一种是枚举式单例模式,另一种是容器式单例模式。
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;
}
}
测试代码如下:
@Test
void EnumSingletonTest(){
EnumSingleton e1 = null;
EnumSingleton e2 = EnumSingleton.getInstance();
e2.setData(new Object());
FileOutputStream fos = null;
try {
fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(e2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
e1 = (EnumSingleton)ois.readObject();
ois.close();
System.out.println(e1.getData());
System.out.println(e2.getData());
System.out.println(e1.getData() == e2.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果如下:
枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能别类加载器加载多次。
那么反射能否破坏枚举式单例模式呢?测试代码如下:
@Test
void EnumSingletonTestThread() {
try{
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor();
c.newInstance();
} catch (Exception e){
e.printStackTrace();
}
}
运行结果如下:
报错没有找到无参构造方法。查看 Enum 源码 他的构造方法只有一个 protected 类型的构造方法,代码如下:
protected Enum(String name, int ordinal){ this.name = name; this.ordinal = ordinal; }
再做如下测试:
@Test
void EnumSingletonTestThread() {
try{
Class clazz = EnumSingleton.class;
Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("l-coil",666);
} catch (Exception e){
e.printStackTrace();
}
}
测试结果如下:
报错已经很明显了,不能用反射来创建枚举类型。
枚举式单例模式也是 Effective Java 书中推荐的一种单例模式实现写法。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){
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
容器式单例模式使用与实例非常多的情况,编辑管理。单它是非线程安全的。