设计模式之单例模式

单例模式

单例模式的核心作用是:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

常见的单例模式应用场景:

1.Windows的Task Manager(任务管理器)就是很典型的单例模式。
2.windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3.项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
4.网站的计数器,一般也是采用单例模式实现,否则难以同步。
5.应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
7.操作系统的文件系统,也是单例模式实现的具体例子,一个操作系统只能有一个文件系统。
8.Application 也是单例的典型应用(Servlet编程中会涉及到)。
9.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理。
10.在Servlet编程中,每个Servlet也是单例。
11.在Spring MVC框架/Struts1框架中,控制器对象也是单例。

1、饿汉式

特点是: 线程安全,调用效率高。 但是,不能延时加载。

/**
 * 饿汉单例模式
 */
package com.singleTon;

public class SingleTonDemo1 {

//类初始化时,立即加载这个对象(没有延时加载的优势)加载类时,天然的是线程安全的
private static SingleTonDemo1 instance = new SingleTonDemo1();

//私有构造器
private SingleTonDemo1() {

}

//方法没有同步,调用效率高
public static SingleTonDemo1 getInstance() {
return instance;
}
}

2、懒汉式

懒汉式单例模式的特点:线程安全,调用效率不高。 但是,可以延时加载。

/**
 * 懒汉式单例模式
 */
package com.singleTon;

public class SingleTonDemo2 {

//类初始化时不初始化这个对象,延时加载,真正用的时候在创建
private static SingleTonDemo2 instance2;

private SingleTonDemo2() {

}

//方法同步,调用效率低
public static synchronized SingleTonDemo2 getInstance2(){
if(instance2==null){
instance2 = new SingleTonDemo2();
}
return instance2;
}
}

3、静态内部类式

由于加载一个类时,其内部类不会同时被加载。当且仅当内部类的某个静态成员(静态域、构造器、静态方法等)被调用时才会加载该内部类。
并且JVM会保证类加载的线程安全问题,所以利用这个特性我们可以写出兼顾效率与线程安全的优化版本,即静态内部类式单例模式。
静态内部类式单例模式特点:线程安全,调用效率高。 而且,可以延时加载。需要延时加载的情况下,这种实现方式,优于“懒汉式”。

/**
 * 静态内部类实现单例模式;线程安全,懒加载模式
 * 由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
 */
package com.singleTon;

public class SingleTonDemo3 {
//只有在用到内部类的静态属性时,才会加载,类加载过程是天然的线程安全
private static class SingleTonClassInstance {
private static final SingleTonDemo3 instance3 = new SingleTonDemo3();
}
private SingleTonDemo3() {

}
//不需要同步,调用效率高
public static SingleTonDemo3 getInstance3() {
return SingleTonClassInstance.instance3;
}
}

4、枚举式

特点:线程安全,调用效率高,不能延时加载。

/**
 * 枚举式实现单例模式(没有延时加载)
 */
package com.singleTon;

public enum  SingleTonDemo4 {
//这个枚举元素本身就是单例对象
INSTANCE;

//添加自己的需要的操作
public void singleTonOperation() {

}
}

测试函数:

/**
 * 测试单例模式
 */
package com.singleTon;

public class Client {
public static void main(String[] args) {
//SingleTonDemo1 s1 = SingleTonDemo1.getInstance();
//SingleTonDemo1 s2 = SingleTonDemo1.getInstance();

//SingleTonDemo2 s1 = SingleTonDemo2.getInstance2();
//SingleTonDemo2 s2 = SingleTonDemo2.getInstance2();

//SingleTonDemo3 s1 = SingleTonDemo3.getInstance3();
//SingleTonDemo3 s2 = SingleTonDemo3.getInstance3();

System.out.println(SingleTonDemo4.INSTANCE == SingleTonDemo4.INSTANCE);//true

//测试结果:同一个对象
//System.out.println(s1);
System.out.println(s2);
}
}

如何选用四种单例创建方式

根据开发场景,如果单例对象占用资源少,不需要延时加载:
枚举式 优于 饿汉式 (枚举式天然地可以防止反射和序列化漏洞!)
如果单例对象占用资源大,需要延时加载:
静态内部类 优于 懒汉式 (静态内部类调用效率远远高于懒汉式)

反射和反序列化可以破解上面几种实现方式(枚举除外),如何防止反射和反序列漏洞?

/**
 * 懒汉式单例模式(如何防止反射和反序列漏洞)
 */
package com.singleTon;

import java.io.Serializable;

public class SingleTonDemo5 implements Serializable {//序列化

//类初始化时不初始化这个对象,延时加载,真正用的时候在创建
private static SingleTonDemo5 instance5;

//私有构造器,多次调用时直接抛出异常
private SingleTonDemo5() {
if(instance5!=null){
throw new RuntimeException();
}

}

//方法同步,调用效率低
public static synchronized SingleTonDemo5 getInstance5(){
//通过抛出异常来表示使用反射
//if(instance5==null){
//instance5 = new com.singleTon.SingleTonDemo5();
//}
return instance5;
}

//反序列化时,直接调用此方法得到对象,不需要单独创建新对象
private Object readResolve() throws Exception {
return instance5;
}
}

测试:

/**
 * 测试反射和反序列化
 */
package com.singleTon;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;

public class Client2 {
public static void main(String[] args) throws Exception{
SingleTonDemo5 s1 = SingleTonDemo5.getInstance5();
SingleTonDemo5 s2 = SingleTonDemo5.getInstance5();

System.out.println(s1);
System.out.println(s2);

通过反射的方式直接调用私有构造器
//Class<SingleTonDemo5> clazz = (Class<SingleTonDemo5>)Class.forName("com.singleTon.SingleTonDemo5");
//Constructor<SingleTonDemo5> c = clazz.getDeclaredConstructor(null);
//c.setAccessible(true);//访问私有内容,跳过权限检查
创建对象
//SingleTonDemo5 s3 = c.newInstance();
//SingleTonDemo5 s4 = c.newInstance();
//
//System.out.println(s3);
//System.out.println(s4);

//通过反序列化的方式构造多个对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c:/test/a.txt"));
oos.writeObject(s1);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c:/test/a.txt"));
SingleTonDemo5 s3 = (SingleTonDemo5) ois.readObject();
System.out.println(s3);
}
}

测试结果:
反射结果:

 com.singleTon.SingleTonDemo5@4554617c
    com.singleTon.SingleTonDemo5@4554617c
    com.singleTon.SingleTonDemo5@74a14482
    com.singleTon.SingleTonDemo5@1540e19d

反序列化结果:

com.singleTon.SingleTonDemo5@4554617c
com.singleTon.SingleTonDemo5@4554617c
com.singleTon.SingleTonDemo5@568db2f2

测试单例模式的效率:

/**
 * 测试4种创建单例模式效率
 */
package com.singleTon;

import java.util.concurrent.CountDownLatch;

public class Client3 {
public static void main(String[] args) throws Exception{

//开始时间
long start = System.currentTimeMillis();

//测试10线程
int threadNum = 10;
//允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助,10个线程
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

for(int i=0;i<10;i++) {
//创建一个线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
//Object o = SingleTonDemo3.getInstance3();
SingleTonDemo4.INSTANCE.singleTonOperation();
}
//减少锁存器的计数,如果计数达到零,释放所有等待的线程。
countDownLatch.countDown();
}
}).start();
}

countDownLatch.await();//main线程阻塞,知道计数器变为0,才会继续往下执行

//结束时间
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start));
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值