《设计模式》,emmm~老朋友了鸭,一个在本科阶段学习的课程,平常就用到其中几个而已,现在研究生期间面临找实习了,还是再全面地复习一下吧。为了加深理解,本文用Python和Java两种语言进行实现。
-
设计模式总体包括3大类、2小类,3大类是:
创建型,共五种:工厂方法、抽象工厂、单例、建造者、原型。
结构型,共七种:适配器、装饰器、代理、外观、桥接、组合、享元。
行为型,共十一种:策略、模板方法、观察者、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者、中介者、解释器。
2小类是:并发型模式、线程池模式。 -
设计模式之间的关系:
[外链图片转存失败(img-ZQlNtEhc-1563595227024)(http://dl.iteye.com/upload/attachment/0083/1179/57a92d42-4d84-3aa9-a8b9-63a0b02c2c36.jpg)]
1 单例模式
1.1 模式介绍
(1)含义
一句话:某个类只能有一个实例。
(2)好处
一句话:节省开销,节省内存,防止业务错乱。
(1) 节省开销:大型对象的创建比较复杂,需要很大的系统开销。
(2) 节省内存:没必要的对象,创建太多是在浪费内存。
举例:某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。
(3) 防止业务错乱。
举例:有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。
1.2 Python实现
- 单线程。
# -*- coding: utf-8 -*-
class Singleton(object):
def __init__(self):
# print("4:",self)
pass
def __new__(cls, *args):
# print("1:",cls.__name__)
if not hasattr(Singleton, "_instance"): # 反射
Singleton._instance = object.__new__(cls)
# print("2:",Singleton._instance)
# print("3:__new__完成")
return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1)
print(obj2)
#该程序执行过程讲解:(可以打开程序中的注释,观察执行顺序,执行顺序为:1,2,3,4。)
#类Singleton创建实例时,先将Singleton本身作为参数传递进__new__(),作为其第一个参数cls。
#__new__()将类型cls(即Singleton) 作为参数传递给父类的__new__(),父类的__new__()创建完cls的实例后,返回,然后赋值给Singleton._instance。
#__new__()只是创建了实例,分配了内存,但是还没有初始化(即实例Singleton._instance的属性还没有值)。
#Singleton.__new__()创建完实例后,python自动调用__init__()方法,将属性的值传递给__init__()。在__init__中将*args赋值给实例_instance的属性。
输出:
<__main__.Singleton object at 0x00000190F9661FD0>
<__main__.Singleton object at 0x00000190F9661FD0>
- 多线程安全:加锁
import threading
class Singleton(object):
def __init__(self):
pass
def __new__(cls, *args):
mylock = threading.RLock()
mylock.acquire()
if not hasattr(Singleton, "_instance"): # 反射
Singleton._instance = object.__new__(cls)
mylock.release()
return Singleton._instance
- 多线程安全兼顾效率:双重检查锁——两个null检查
import threading
class Singleton(object):
def __init__(self):
pass
def __new__(cls, *args):
if not hasattr(Singleton,"_instance"):
mylock = threading.RLock()
mylock.acquire()
if not hasattr(Singleton, "_instance"): # 反射
Singleton._instance = object.__new__(cls)
mylock.release()
return Singleton._instance
1.3 Java实现
参考网址:你真的会写单例模式吗?
- 单线程:synchronized。
public class Singleton {
private static volatile Singleton singleton = null;
private static Singleton getSingleton() {
//同步代码块
synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
if(singleton == null) singleton = new Singleton();
}
return singleton;
}
}
缺点:多个线程访问,可能重复创建对象。
2. 多线程安全:加锁。synchronized + volatile。
public class Singleton {
private static volatile Singleton singleton = null;
//volatile关键字保证singleton对所有线程的可见性,可见性指在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。
private static Singleton getSingleton() {
//同步代码块
synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
if(singleton == null) singleton = new Singleton();
}
return singleton;
}
}
缺点:
效率低下,无法实际应用。因为每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正遇到需要new的情况是非常少的,绝大多数都是可以并行的读操作。
3. 线程安全兼顾效率
public class Singleton {
private static volatile Singleton singleton = null;
private static Singleton getSingleton() {
if(singleton == null) {
//同步代码块
synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
if(singleton == null) singleton = new Singleton();
}
}
return singleton;
}
}
这种写法被称为“双重检查锁”,顾名思义,就是在getSingleton()方法中,进行两次null检查。看似多此一举,但实际上却极大提升了并发度,进而提升了性能。为什么可以提高并发度呢?就像上文说的,在单例中new的情况非常少,绝大多数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,执行效率提高的目的也就达到了。
其实上述单例模式的实现都还有缺点,所以参考你真的会写单例模式吗?,这篇文章写得真心不错。
参考网址
第二次学习
三种方法都可以。
第一种较繁琐、第三种使用的较少。
第二、三种最安全。
初始化时,第一种可以传参数,第二种不可以传参数。
1 双重检查锁
package com.singleton;
//单例模式建议使用双重检查锁或静态内部类的方法实现。
/**
* 可行解法1:双重检查锁。(推荐)
* 加同步锁前后,两次判断实例是否存在。
*注意:
*1)静态实例:需要的时候创建。
*2)私有构造方法:防止别人创建
*3)两次null判断:提高效率
*4)votatile限制:防止JVM乱序优化
*4)同步代码块:线程安全
* 缺点:实现复杂,容易出错
*/
public class Singleton1 {
public volatile static Singleton1 singleton= null; // 静态,需要的时候创建
//volatile的特点:可见性、有序性。有序性禁止对指令重排序。
private Singleton1(){} // 防止别人自己创建
public static Singleton1 getSingleton() {
if(singleton == null){ // 不为null时就不用加锁了。判断后再锁,提高效率
synchronized (Singleton1.class) {// 线程安全:因为同步块中的Singleton()方法是singleton对象的实例方法,所以给该实例加锁。并且,此时singleton实例可能还未创建,是一个空指针。synchronized(expr)中,expr表达式的值必须是某个对象的引用
if(singleton == null) {//为什么又判断一次?一个线程在第一次判断null进入了,但是在还没进入同步块之前,另一个线程进入了第一个null,进入了同步块,创建了一个实例。然后第一个线程才进入同步块。
singleton = new Singleton1();
//包含三步:JVM乱序优化,容易出错
// 1.在堆内存开辟内存空间。
// 2.在堆内存中实例化SingleTon里面的各个参数。
// 3.把对象指向堆内存空间。
}
}
}
return singleton;
}
public static void main(String[] args) {
Singleton1 base1 = Singleton1.getSingleton();
System.out.println(base1);
Singleton1 base2 = Singleton1.getSingleton();
System.out.println(base2);
}
}
2 静态内部类
package com.singleton;
/*
* 静态内部类(推荐)
* 注意:
* 1)私有构造方法:防止别人创建
* 2)内部类:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
*缺点:初始化时,无法传参
*/
public class Singleton2 {
private Singleton2(){}
private static class SingletonHolder{
public static Singleton2 singleton = new Singleton2();
}
public static Singleton2 getSingleton() {
return SingletonHolder.singleton;
}
public static void main(String[] args) {
Singleton2 singleton = Singleton2.getSingleton();
System.out.println(singleton);
}
}
3 枚举
package com.singleton;
/*其他的方法都有下面两个缺点:
* 1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
* 2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
* 而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
*/
enum Singleton{
INSTANCE;
private SingletonClass instance;
private Singleton() {
// TODO Auto-generated constructor stub
this.instance = new SingletonClass();
}
public SingletonClass getInstance(){
return this.instance;
}
}
public class SingletonClass{
public static SingletonClass getSingleton() {
return Singleton.INSTANCE.getInstance();
}
}