核心作用:保证一个类只有一个实例,并提供一个访问该实例的全局访问点。
常见应用场景:
– Windows的Task Manager(任务管理器)就是很典型的单例模式
– windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
– 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
– 网站的计数器,一般也是采用单例模式实现,否则难以同步。
– 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作
,否则内容不好追加。
– 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
– 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
– Application 也是单例的典型应用(Servlet编程中会涉及到)
– 在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
– 在servlet编程中,每个Servlet也是单例
– 在spring MVC框架/struts1框架中,控制器对象也是单例
单例模式的5种实现:
饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
双重检查锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
枚举式(线程安全,调用效率高,不能延时加载)
单例模式的优点:
只生成一个实例,减少了系统性能开销。
单例模式的实现:
- 一个对象只创建一个实例
- 在系统设置该对象实例的全局访问点
1、饿汉式
- static变量会在类装载时初始化,虚拟机保证只会装载一次该类,而且此时也不会有多个线程访问的问题,所以线程安全。
- 没有线程同步,所以调用效率高。
- 由于在类装在时初始化,所有没有实现懒加载。
public class SingletonDemo1 {
//类初始化时加载,线程安全;但没有实现懒加载
private static SingletonDemo1 singletonInstance = new SingletonDemo1();
//私有化构造器
private SingletonDemo1() {
}
//没有方法同步,调用效率高
public static SingletonDemo1 getInstance() {
return singletonInstance;
}
}
2、懒汉式
- 使用synchronized线程同步,所以线程安全,只能创建一个实例。
- 使用了synchronized线程同步,所以执行效率低。
- 没有在加载类的时候创建实例,而是在使用的时候才创建实例,实现了懒加载
public class SingletonDemo2 {
private static SingletonDemo2 singletonInstance;
//私有化构造器
private SingletonDemo2() {
}
//方法同步,线程安全,调用效率低;初次调用方法时创建单例对象,实现懒加载
public static synchronized SingletonDemo2 getInstance() {
if(singletonInstance == null) {
singletonInstance = new SingletonDemo2();
}
return singletonInstance;
}
}
3、双重检查锁式
对懒汉式进行了优化,把synchronized放到了if语句下,不必每次获取对象都同步,只有第一次获取才同步。
问题:由于编译器优化原因和JVM底层内部模型原因,偶尔会出问题。不建议使用。
线程安全,执行效率高、实现了懒加载
public class SingletonDemo3 {
private static SingletonDemo3 singletonInstance;
//私有化构造器
private SingletonDemo3() {
}
public static SingletonDemo3 getInstance() {
if(singletonInstance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class) {
sc = singletonInstance;
if(sc == null) {
synchronized(SingletonDemo3.class) {
if(sc == null) {
sc = new SingletonDemo3();
}
}
singletonInstance = sc;
}
}
}
return singletonInstance;
}
}
4、静态内部类式
- 由于加载类SingletonDemo4时候,未加载内部类,执行getInstance()方法的时候才加载,所以实现了懒加载。
- 加载内部类的时初始化,所以线程安全。
- 没有同步,执行效率高。
public class SingletonDemo4 {
//SingletonDemo4初始化时不加载,实现懒加载
private static class SingletonClassInstanc{
//类初始化时加载,线程安全;
private static final SingletonDemo4 singletonInstance = new SingletonDemo4();
}
//私有化构造器
private SingletonDemo4() {
}
//方法没有同步,调用效率高
public static SingletonDemo4 getInstance() {
return SingletonClassInstanc.singletonInstance;
}
}
5、枚举式
- 线程安全
- 没有同步,执行效率高。
- 没有实现懒加载
- 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
//JVM根本上提供保障,避免反射和反序列化漏洞
public enum SingletonDemo5 {
//枚举元素,本身就是单例
INSTANCE;
public void SingletonOperation() {
//单例的功能操作
}
}
6、反射破解单例模式
反射可以破解上面的除枚举式以外的4四种单例实现,也就是说可以通过反射创建多个对象。
代码如下:
@Test
public void test() throws Exception {
//创建并获得单例
SingletonDemo6 s1 = SingletonDemo6.getInstance();
System.out.println(s1);
//通过反射直接调用私有构造器
Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("club.ityuchao.gof32.singleton.SingletonDemo6");
Constructor<SingletonDemo6> constructor = clazz.getDeclaredConstructor(null);
//跳过权限检查
constructor.setAccessible(true);
SingletonDemo6 s2 = constructor.newInstance();
SingletonDemo6 s3 = constructor.newInstance();
System.out.println(s2);
System.out.println(s3);
}
避免通过反射创建多个对象的方法:
由于反射创建对象是走的构造方法,所以在构造方法中判断一下,如果在已经实例化的情况下依旧执行构造方法,就手动抛出异常。
代码如下(以懒汉式为例):
public class SingletonDemo6 implements Serializable {
private static SingletonDemo6 singletonInstance;
//私有化构造器
private SingletonDemo6() {
//防止反射破解单例模式
if(singletonInstance != null) {
throw new RuntimeException();
}
}
//方法同步,线程安全,调用效率低;初次调用方法时创建单例对象,实现懒加载
public static synchronized SingletonDemo6 getInstance() {
if(singletonInstance == null) {
singletonInstance = new SingletonDemo6();
}
return singletonInstance;
}
}
7、反序列化破解单例模式
反序列化可以破解上面的除枚举式以外的4四种单例实现,也就是说可以通过反序列化创建多个对象。
代码如下:
@Test
public void test2() throws Exception {
//创建并获得单例
SingletonDemo6 s1 = SingletonDemo6.getInstance();
System.out.println(s1);
//通过反序列化构造多个对象
FileOutputStream fos = new FileOutputStream("E://a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("E://a.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
SingletonDemo6 s2 = (SingletonDemo6) ois.readObject();
ois.close();
fis.close();
System.out.println(s2);
}
防止反序列化破解单例模式的方法:
让单例类实现Serializable 接口,在单例类中写一个readResolve()方法。
目的:反序列化的时候,如果定义了readResolve()方法,则直接此方法指定的对象,而不用创建新的对象。
public class SingletonDemo6 implements Serializable {
private static SingletonDemo6 singletonInstance;
//私有化构造器
private SingletonDemo6() {
//防止反射破解单例模式
if(singletonInstance != null) {
throw new RuntimeException();
}
}
//方法同步,线程安全,调用效率低;初次调用方法时创建单例对象,实现懒加载
public static synchronized SingletonDemo6 getInstance() {
if(singletonInstance == null) {
singletonInstance = new SingletonDemo6();
}
return singletonInstance;
}
//反序列化的时候,如果定义了readResolve()方法,则直接此方法指定的对象,而不用创建新的对象
private Object readResolve() throws ObjectStreamException{
return singletonInstance;
}
}
8、测试5种单例模式实现的效率
下面代码测试当执行10个线程,每个线程访问单例10万次执行的时间。
public class TestAll {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int threadNum = 10;
CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i = 0;i < threadNum; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 100000; i++) {
//Object o = SingletonDemo5.INSTANCE;
Object o = SingletonDemo1.getInstance();
}
//计数器减一
countDownLatch.countDown();
}
}).start();;
}
//阻塞mian线程,知道计数器为0,内部其实就是循环判断计数器是否为0
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println("总耗时:" + (end - start));
}
}
执行效果:
懒汉式:22
饿汉式:94
双重检查锁式:27
静态内部类式:23
枚举式:18
结论:就执行速度而言,饿汉式由于方法同步的原因,执行最慢,然后其他四种差不多速度。
9、如何选择
– 单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式