核心作用:
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见的应用场景:
1、windows的任务管理器(Task Manager)
2、Windows的回收站(Recycle Bin)
3、项目中,读取配置文件的类,一般也是单例模式,没有必要每次读取配置的时候都new一个对象
4、网站的计数器,不用单例模式,难以统一
5、应用程序的日志应用,一般都用单例实现,这一般是由共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
6、数据库连接池的设计,
操作系统的文件系统,一个操作系统只能有一个文件系统
7、Application也是单例应用
8、在Spring中,每个Bean默认是单例的,方便Spring管理
9、在Servlet编程中,每个Servlet也是单例的
10、在SpringMVC框架/Struct1中,控制器也是单例
单例模式的优点:
1、由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如得去配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留在内存的方式来解决
2、单例模式可以在系统设置全局的访问点,优化环境共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
常见的五种单例模式实现方式:
1、饿汉式(线程安全,调用效率高,但是,不能延时加载)
2、赖汉式(线程不安全,调用效率不高,但是可以延时加载)
其他:
3、双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
4、静态内部类式(线程安全,调用效率高,但是,可以延时加载)
5、枚举单例(线程安全,调用效率高,不能延时加载)
主要:
1、饿汉式(线程安全,调用效率高,但是,不能延时加载)
package com.silence.singleton;
/**
* 测试饿汉式单例模式
* @author zhuxiang
*
*/
public class SingletonDemo1 {
//类初始化时立即加载这个对象(没有延时加载的优势)。加载类时是天然的线程安全
private static SingletonDemo1 instance = new SingletonDemo1();
//私有构造器
private SingletonDemo1(){
}
//方法没有同步,调用效率高
public static SingletonDemo1 getInstance(){
return instance;
}
}
2、赖汉式(线程不安全,调用效率不高,但是可以延时加载)
package com.silence.singleton;
/**
* 测试懒汉式单例模式
* @author zhuxiang
*
*/
public class SingletonDemo2 {
//类初始化时,不立即初始化这个对象(具有延时加载的优势)。
private static SingletonDemo2 instance;
//私有构造器
private SingletonDemo2(){
}
//方法同步,调用效率低
public static synchronized SingletonDemo2 getInstance(){
if (null == instance) instance = new SingletonDemo2();
return instance;
}
}
其他:
3、双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题,不建议使用)
4、静态内部类式(线程安全,调用效率高,但是,可以延时加载)
package com.silence.singleton;
/**
* 测试静态内部类式单例模式
* 这种方式,线程安全,调用效率高,并且实现了延时加载
* @author zhuxiang
*
*/
public class SingletonDemo4 {
private static class SingletonInnerClass {
private static final SingletonDemo4 instance = new SingletonDemo4();
}
//私有构造器
private SingletonDemo4(){
}
//方法没有同步,调用效率高
public static SingletonDemo4 getInstance(){
return SingletonInnerClass.instance;
}
}
5、枚举单例(线程安全,调用效率高,不能延时加载)
package com.silence.singleton;
/**
* 测试枚举式的单例模式
* 效果挺好的,但是没有延时加载的特点
* 可以有效的防止反射和反序列化的漏洞
* @author zhuxiang
*
*/
public enum SingletonDemo5 {
//这个枚举元素,本身就是一个单例对象!
INSTANCE;
//添加自己需要的操作
public void singletonOperation(){
}
}
//测试用例如下,可以将SingletonDemo4 换成其他的单例实现类
package com.silence.singleton;
public class Client {
public static void main(String[] args) {
SingletonDemo4 instance = SingletonDemo4.getInstance();
SingletonDemo4 instance2 = SingletonDemo4.getInstance();
System.out.println(instance);
System.out.println(instance2);
//枚举类型测试 System.out.println(SingletonDemo5.INSTANCE==SingletonDemo5.INSTANCE);
}
}
几种单例模式中,除了枚举类型之外,其他的都可以用反射和反序列化的方法进行破解:
package com.silence.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
/**
* 测试反射和反序列化破解单例模式
*
* @author zhuxiang
*
*/
@SuppressWarnings(“all”)
public class Client2_6 {
public static void main(String[] args) throws Exception {
SingletonDemo6 instance = SingletonDemo6.getInstance();
SingletonDemo6 instance2 = SingletonDemo6.getInstance();
System.out.println(instance);
System.out.println(instance2);
//通过反射的方式直接调用私有构造器破解单例模式
/*Class<SingletonDemo6> clz = (Class<SingletonDemo6>) Class.forName("com.silence.singleton.SingletonDemo6");
Constructor<SingletonDemo6> constructor = clz.getDeclaredConstructor(null);
constructor.setAccessible(true);
SingletonDemo6 instance3 = constructor.newInstance();
SingletonDemo6 instance4 = constructor.newInstance();
System.out.println(instance3);
System.out.println(instance4);
*/
//通过反序列化的方式破解单例模式,创建多个对象
FileOutputStream fos = new FileOutputStream("d:/myjava/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/myjava/a.txt"));
SingletonDemo6 ins = (SingletonDemo6) ois.readObject();
System.out.println(ins);
}
}
为了防止被反射和反序列化破解,在实现单例模式的时候可以用如下的模式防范
package com.silence.singleton;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 测试懒汉式反射和反序列化的破解单例模式
* @author zhuxiang
*
*/
public class SingletonDemo6 implements Serializable{
//类初始化时,不立即初始化这个对象(具有延时加载的优势)。
private static SingletonDemo6 instance;
//私有构造器
private SingletonDemo6(){
//避免反射破解单例
if (null != instance) {
throw new RuntimeException();
}
}
//方法同步,调用效率低
public static synchronized SingletonDemo6 getInstance(){
if (null == instance) instance = new SingletonDemo6();
return instance;
}
//避免反序列化破解单例模式,在反序列化时直接执行该方法,返回instance对象,不返回反序列化的对象
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
对于这么多的单例模式,我们在使用的时候一般如何选择呢?总结如下:
1)、单例对象占用资源少,不需要延时加载 时,枚举式要好于饿汉式
2)、单例对象占用资源多,需要延时加载时,静态内部类式好于懒汉式
一般来说,饿汉式相对较不错比其他都快。