一、分类
创建型模式:
单例模式、工厂模式、抽象工厂模式、建造者模式、原形模式结构型模式:
适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式行为型模式:
模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式
二、单例模式
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
2.1 常用场景
-
windows
的Task Manager
(任务管理器)就是很典型的单例模式 -
windows
的回收站也是。 - 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次
new
一个对象去读取。 - 网站的计数器
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加
- 数据库连接池
- 操作系统的文件系统
-
application
也是单例的典型应用(servlet
编程中会涉及到) - 在
spring
中,每个bean
默认就是单例的,这样做的有点是spring
容器便于管理 - 在
servlet
编程中,每个servlet
也是单例 - 在
struts1
中,控制器对象也是单例
2.2 优点
由于单例模式只是生成了一个实例对象,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过
在应用启动时直接产生一个单例对象,然后永久驻留在内存的方式来解决。
单例模式可以在系统设置全局的访问点,优化共享资源的访问,例如设计一个单例类,负责所有数据表的映射处理。
2.3 常见的五种单例模式实现方式
2.3.1 主要
- 饿汉式(线程安全,调用效率高,但是,不能延时加载)
SingletonDemo01
package cn.itcast.day226.singleton;
//饿汉式
public class SingletonDemo01 {
//提供静态属性,这里就不管后面会不会用到,初始化时就加载一个实例
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01(){}//构造器私有化
//这里不需要使用同步,因为加载类的时候(就是在加载上面的属性时)本身就是线程安全的了
//同时这里没有同步,当然调用效率就高了
public static /*synchronized*/ SingletonDemo01 getInstance(){
return instance;
}
}
- 懒汉式(线程安全,效率不高,但是可以延时加载)
SingletonDemo02
package cn.itcast.day226.singleton;
//懒汉式
/*
* 用的时候才会加载,资源利用率提高了,但每次调用都需要同步,调用效率就低了
* */
public class SingletonDemo02 {
private static SingletonDemo02 instance ;
private SingletonDemo02(){}
public static synchronized SingletonDemo02 getInstance(){
if(instance == null){
instance = new SingletonDemo02();
}
return instance;
}
}
2.3.2 其他
- 双重检测锁式(由于
jvm
底层内部模型原因,偶尔会出现问题,不建议使用)SingletonDemo03
package cn.itcast.day226.singleton;
//双重检测锁模式,不建议使用
public class SingletonDemo03 {
private static SingletonDemo03 instance = null;
private SingletonDemo03(){}
public static SingletonDemo03 getInstance(){
//之前在懒汉式中我们是将方法同步了,但是这里我们在方法一开始不进行同步
//而只有在第一次加载的时候发现对象没有实例化才同步
if(instance == null){
SingletonDemo03 sc ;
synchronized (SingletonDemo03.class) {
sc = instance;
if(sc == null){
synchronized (SingletonDemo03.class) {
if(sc == null){
sc = new SingletonDemo03();
}
}
instance = sc ;
}
}
}
return instance;
}
}
- 静态内部类式(线程安全,调用效率不高,但是可以延时加载)
SingletonDemo04
package cn.itcast.day226.singleton;
//静态内部类模式(其实也是一种懒加载方式)
/*
* 外部类没有static属性,则不会像饿汉式那样立即加载对象
* 只有真正调用getInstance方法才会加载静态内部类。加载类时是线程安全的。instance是static final
* 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性
* 兼备了并发高效调用和延时加载的优势
* */
public class SingletonDemo04 {
private SingletonDemo04(){}
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance(){
return SingletonClassInstance.instance;
}
}
- 枚举单例(线程安全,调用效率高,不能延时加载)
SingletonDemo05
package cn.itcast.day226.singleton;
//枚举模式,枚举天然就是单例的
/*
* 实现简单,枚举本身就是单例,由jvm从根本上提供保障。避免通过反射和反序列化的漏洞,但是不能延时加载
* */
public enum SingletonDemo05 {
INSTANCE;//定义一个枚举元素,它就代表了Singleton的一个实例
//单例可以有自己的操作
public void singletonOperation(){
//功能处理
}
//使用例子
public static void main(String[] args) {
SingletonDemo05 sd1 = SingletonDemo05.INSTANCE;
SingletonDemo05 sd2 = SingletonDemo05.INSTANCE;
System.out.println(sd1 == sd2);
sd1.singletonOperation();//调用方法
}
}
2.4 如何选用
如果单例对象占用资源要少,不需要延时加载:
使用枚举式好于饿汉式如果单例对象占用资源大,需要延时加载:
静态内部类式好于懒汉式
2.5 问题
- 反射可以破解上面几种方式(除枚举式)
- 反序列化可以破解上面几种方式(除枚举式)
- 可以通过定义readResolve()防止获得不同对象
- 反序列化时,如果对象所在类定义了readResolve(),(实际上是一种回调),定义返回哪个对象
package cn.itcast.day226.singleton;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
/*
* 测试懒汉式(如何防止反射和反序列化漏洞)
* */
public class SingletonDemo06 implements Serializable{
private static SingletonDemo06 instance;
private SingletonDemo06() {
//只有在没有实例化对象的时候才能使用构造器
if(instance != null){
throw new RuntimeException();//通过抛出异常来防止反射漏洞
}
}
public static synchronized SingletonDemo06 getInstance() {
if (instance == null) {
instance = new SingletonDemo06();
}
return instance;
}
//在反序列化时,如果我们定义了这个方法,则会直接调用这个方法返回当前的对象,而不是实例化一个新的对象
private Object readResolve() throws ObjectStreamException{
return instance;
}
public static void main(String[] args) throws Exception {
SingletonDemo02 s1 = SingletonDemo02.getInstance();
SingletonDemo02 s2 = SingletonDemo02.getInstance();
System.out.println(s1 == s2);
// 加反射
Class<SingletonDemo02> clazz = (Class<SingletonDemo02>) Class
.forName("cn.itcast.day226.singleton.SingletonDemo02");
Constructor<SingletonDemo02> c = clazz.getDeclaredConstructor(null);// 获得无参构造器
c.setAccessible(true);// 跳过权限检查
SingletonDemo02 s3 = c.newInstance();
SingletonDemo02 s4 = c.newInstance();
System.out.println(s3 == s4);// 此时两个对象就不想等了
//下面我们看上面改造之后的情况
Class<SingletonDemo06> clazz1 = (Class<SingletonDemo06>) Class
.forName("cn.itcast.day226.singleton.SingletonDemo06");
Constructor<SingletonDemo06> c1 = clazz1.getDeclaredConstructor(null);// 获得无参构造器
c.setAccessible(true);// 跳过权限检查
SingletonDemo06 s5 = c1.newInstance();
SingletonDemo06 s6 = c1.newInstance();
System.out.println(s5 == s6);// 此时两个对象就不想等了
//加反序列化
//序列化:将s1对象写到磁盘上
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo02 s7 = (SingletonDemo02) ois.readObject();
System.out.println(s1 == s7);
//防止反序列化,定义readResolve方法
SingletonDemo06 s8 = SingletonDemo06.getInstance();
//序列化:将s1对象写到磁盘上
FileOutputStream fos1 = new FileOutputStream("d:/b.txt");
ObjectOutputStream oos1 = new ObjectOutputStream(fos1);
oos1.writeObject(s8);
oos1.close();
fos1.close();
//反序列化
ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("d:/b.txt"));
SingletonDemo06 s9 = (SingletonDemo06) ois1.readObject();
System.out.println(s8 == s9);
}
}
2.6 测试五中模式的效率
- 饿汉式:22ms
- 懒汉式:636ms
- 静态内部类式:28ms
- 枚举式:32ms
- 双重检测锁式:65ms
package cn.itcast.day226.singleton;
import java.util.concurrent.CountDownLatch;
//测试多线程环境下五种创建单例模式的效率
public class Client {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 100000; j++){
Object obj = SingletonDemo01.getInstance();//这里进行检测
}
//这里在上面定义CountDownLatch的时候需要加final,因为内部类不能直接使用外部的局部变量
countDownLatch.countDown();
}
}).start();;
}
countDownLatch.await();//等待所有线程都执行完
long end = System.currentTimeMillis();
//如果不使用CountDownLatch则下面的结果只是main线程的时间,而其他线程其实还没有运行完
System.out.println(end - start);
}
}
说明:
- 这里我们通过
CountDownLatch
同步辅助类,在完成一组在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 -
countDown()
:当前线程调用此方法,则计数减一(建议放在finally
中) -
await()
:调用此方法会一直阻塞当前线程,直到计数器的值为0