前言
1、设计模式是人们在面对同类型软件工程设计问题所总结出的一些有用经验。模式不是代码,而是某类问题的通用设计解决方案
2、4人组Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides总结写了《设计模式》
3、设计模式的优点和用途
4、学习设计模式最好的方式:在你的设计和以往的工程里寻找何处可以使用它们
5、设计模式的本质目的是使软件工程在维护性、扩展性、变化性、复杂度方面成O(N)
6、OO是原则,设计模式是具体方法、工具
在java里IO流的类设计,为什么把BufferedReader设计成:
new BufferedReader(new FileReader(“F:\test.java”));
而不是设计成:
BufferedReader extends FileReader;
然后 new BufferedReader(“F:\test.java”);
…
单例模式
所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建.(保证一个类仅有一个实例)
单例模式的意义
有些对象我们只需要一个:线程池、缓存、硬件设备等
如果多个实例会有造成冲突、结果的不一致性等问题
单例模式:确保一个类最多只有一个实例,并提供一个全局访问点
特点:
- 类构造器私有
- 持有自己类型的属性
- 对外提供获取实例的静态方法
关键点
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
应用实例:
- 一个班级只有一个班主任。
- Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
- 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
懒汉式
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
双重锁模式
如果多线程的话,会存在风险,这时就有了 双重锁模式
线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
防止单例被入侵
public class SingletonDemo04 {
private SingletonDemo04 singletonDemo04;
private static boolean flag = false;
private SingletonDemo04() {
if (flag == false) {
flag = !flag;
} else {
throw new RuntimeException("单例模式被侵犯!");
}
}
public SingletonDemo04 getInstance() {
if (singletonDemo04 == null) {
synchronized (this) {
if (singletonDemo04 == null) {
singletonDemo04 = new SingletonDemo04();
}
}
}
return singletonDemo04;
}
}
双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量有效,解决该问题。
静态内部类单例模式
public class Singleton {
private Singleton(){
}
public static Singleton getInstance(){
return Inner.instance;
}
private static class Inner {
private static final Singleton instance = new Singleton();
}
}
只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。
单例模式在 java 中的运用
1.枚举 都是单例模式的
默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上
- 枚举类隐藏了私有的构造器。
- 枚举类的域 是相应类型的一个实例对象
public enum Singleton {
INSTANCE
//doSomething 该实例支持的行为
//可以省略此方法,通过Singleton.INSTANCE进行操作
public static Singleton get Instance() {
return Singleton.INSTANCE;
}
}
2.spring 中的单例模式
singleton(单例):只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实例。
prototype(多例):对这个bean的每次请求都会创建一个新的bean实例,类似于new。
Spring bean 默认是单例模式。
<bean id="hi" class="com.test.Hi" init-method="init" scope="singleton">
将配置文件改为:
<bean id="hi" class="com.test.Hi" init-method="init" scope="prototype">
懒汉式和懒汉式单例类都不能被继承。
我们克服前两种单例类不能被继承的缺点,我们可以使用另外一种特殊化的单例模式,它被称为单例注册表。
public class RegSingleton {
private static HashMap<String, Object> registry = new HashMap<String, Object>();
// 静态块,在类被加载时自动执行
static {
RegSingleton rs = new RegSingleton();
registry.put(rs.getClass().getName(), rs);
}
// 受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点
protected RegSingleton() {
}
// 静态工厂方法,返回此类的唯一实例
public static RegSingleton getInstance(String name) {
String orinalName = RegSingleton.class.getName();
if (Strings.isNullOrEmpty(name)) {
name = orinalName;
}
if (registry.get(name) == null) {
try {
name = orinalName;
registry.put(name, Class.forName(name).newInstance());
} catch (Exception ex) {
ex.printStackTrace();
}
}
return (RegSingleton) registry.get(name);
}
}
Spring框架对单例的支持是采用单例注册表的方式进行实现的