0%

[toc]

为什么要用桥接#

假设希望设计一个补数据接口类Downloader,用于向其他服务器同步数据。 对外提供一个接口
download()
最开始以为只需要同步不同类型的数据即可, 那么只要实现
DataADownloader、 DataBDownloader这几种即可。

但是到后来, 发现不仅要在下载数据中选择, 还可能要选择补数据的方式。
比如有的服务希望是异步download
有的服务是希望加密download

如果只是简单增加类型,则可能出现
AsyncDataADownloader
AsyncDataBDownloader
EncryptDataADownloader
EncryptDataBDownloader

当你发现你不断实现的子类 出现了功能在不同层次叠加的情况,就要注意用桥接模式,做不同层次接口的桥接,而不是只用一个接口导致子类爆炸增长。

桥接方式#

成员的方式进行桥接。
把功能A类型接口作为成员,桥接进另一个功能B类型的抽象类中, 另一个功能的抽象方法只要考虑如何调用这个成员即可。

补数据的场景如下所示:


87c460721063abb1d3856e4a124534cd49b21ce3

桥接模式优点:#

  • 解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
    抽象和实现不再在同一个继承层次结构中,而是“子类化”它们,使它们各自都具有自己的子类,以便任何组合子类,从而获得多维度组合对象
  • 桥接模式可以取代多层继承方案(例如刚才的另一种解决方案可能是多重继承2个接口),多层继承方案违背了“单一职责原则”,复用性较差,且类的个数非常多,桥接模式是比多层继承方案更好的解决方法,它极大减少了子类的个数
  • 提高了系统的可扩展性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合“开闭原则”

桥接模式的缺点#

  • 增加系统的理解与设计难度,由于关联关系建立在抽象层,要求开发者一开始就针对抽象层进行设计与编程
  • 要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性,如何正确识别两个独立维度也需要一定的经验积累。
    因此缺点主要在设计难度上。

其他案例: 知乎:用点咖啡来讲解桥接模式

[toc]

适配器模式#

当你试图引入一个外部jar包的api时, 担心这个jar包以后会换成别的, 或者因为包版本经常更新,api可能会变。
这时候可以用一个adpter来适配一下, 把这个api封装到自己的adpter里,然后再通过adapter提供给自己的代码使用。

0b43d0acc4ebf7c24233c420b0f3afa64f6c6783

注意第三方调用的方法名不是request,这就是经常会变动的地方,所以我们封住到adpter里使用。


Q: 类适配器模式和对象适配器模式的区别?#

A:
类适配器: 本身继承自需要适配的那个对象
118ad90eb040674630cb4a19e34490cd8ed5d0b3
对象适配器: 把需要适配的对象作为成员
57964df74fd72f44157e6d51628fee576d84361e


Q: 适配器模式的优点?#

A:

  1. 增加了调用类的透明性和复用性。 调用者无需关心内部实现,且容易复用给其他模块
  2. 灵活性和扩展性非常好, 内部api升级修改时,无需引发扩散修改。
    通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

Q: 类适配器模式和对象适配器哪个好?为什么#

A:
建议用对象适配器,类适配器的限制较多。
类适配器的缺点:

  1. 对于Java、C#等不支持多重类继承的语言,一次最多只能适配一个适配者类,不能同时适配多个适配者;
  2. 适配者类不能为最终类,如在Java中不能为final类,C#中不能为sealed类;
  3. 在Java、C#等语言中,类适配器模式中的目标抽象类只能为接口,不能为类,其使用有一定的局限性。

Q: 讲一下适配器模式的应用#

A:


参考: 设计模式 | 适配器模式及典型应用

[toc]

参考: 秒懂设计模式之建造者模式(Builder pattern

简单版本#

java的builder构造方式就是经典建造者的应用(但不是模式,因为不涉及父类啥的)
XXX xxx = XXX.builder().set().set().set().build();

抽象版本#

抽象的模式版本中, builder是一个接口,可以被继承的, 因此可以有多个builder。
而会有一个指挥者Direct,负责去调用builder的set操作,塞入不同的值

  • Product: 最终要生成的对象,例如 Computer实例。
  • Builder: 构建者的抽象基类(有时会使用接口代替)。其定义了构建Product的抽象步骤,其实体类需要实现这些步骤。其会包含一个用来返回最终产品的方法Product getProduct()。
  • ConcreteBuilder: Builder的实现类。
  • Director: 决定如何构建最终产品的算法. 其会包含一个负责组装的方法void Construct(Builder builder), 在这个方法中通过调用builder的方法,就可以设置builder,等设置完成后,就可以通过builder的 getProduct() 方法获得最终的产品。

5344c172e18ca7fb47e7ccb4d96e90bdefcc372c


Q: 建造者模式的优点是什么?#

A:
封装性好,客户端不必知道内部产品的实现细节。
建造者独立,容易扩展, 不用维护复杂的入参列表。
便于控制细节风险。


Q: 建造者模式的缺点是什么?#

A:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;
如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。(即builder提供的接口有很大的差异,或者很多用不上的话,就不好用了,还不如不抽象,自己写一个)

[toc]

简单工厂#

Q: 讲一下简单工厂模式#

A:
简单工厂模式不是23种设计模式里的一种,简而言之,就是有一个专门生产某个产品的类。
比如下图中的鼠标工厂,专业生产鼠标,给参数0,生产戴尔鼠标,给参数1,生产惠普鼠标。
注意这个参数, 我必须根据入参去确定返回
factory.create(int type)

10a62974ba163f38f838686b36b3a7bc028589bb

Q: 简单工厂模式的缺点是什么?#

A:
当我试图新增一种类型, 我就得在create里新增case-when, 外面也要做类型的适配

但是由于工厂类集中了所有实例的创建逻辑,违反了高内聚责任分配原则,将全部创建逻辑集中到了一个工厂类中;它所能创建的类只能是事先考虑到的,如果需要添加新的类,则就需要改变工厂类了。当系统中的具体产品类不断增多时候,可能会出现要求工厂类根据不同条件创建不同实例的需求.这种对条件的判断和对具体产品类型的判断交错在一起,很难避免模块功能的蔓延,对系统的维护和扩展非常不利;


工厂方法模式#

Q: 讲一下工厂方法模式是什么?#

A: 把创建对象的操作集中到工厂方法中,工厂支持继承。
外界不关注内部怎么生成或者生成什么, 只要给了对应的工厂,返回给我正确的产品基类即可。
举例:
有1个父类模式
工厂接口A , 和 产品接口B
从接口上可以看出, 接口A.create()函数生产出产品B,

然后后面类似工厂->生产->产品的情况, 都继承过来

工厂类C继承接口A, 产品类D继承接口B。
于是工厂就有好多个了

当我试图切换产品,只需要切换工厂即可,不再需要给出方法入参了!
1b2cf617402f7f5975285f9056cfbbb11b580879
**

Q: 和简单工厂相比,优点是什么?#

A:

  • 一个调用者想创建一个对象,只要知道其名称就可以了。
  • 扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。
  • 屏蔽产品的具体实现,调用者只关心产品的接口。(调用者不用想着我传啥参数了,是1还是2还是什么枚举,直接拿到这个工厂create就完事)

Q: 工厂方法的缺点是什么?#

A:
每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

注意: 这里的工厂方法,一般只有一个create()方法,没有其他的了, 所以模式名特地指出了”方法“二字


抽象工厂模式#

Q: 抽象工厂是什么?#

A:
抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于 抽象工厂方法里的create有好几个,是一个产品族
6149353b20a1615a3a79e672af25da6cbc5f3bbe


Q: 抽象工厂相比工厂方法模式的改进?#

A:
工厂方法模式只有1种create, 当你要新增一类产品的时候, 得重新设计工厂接口类。
而抽象工厂中, 直接工厂接口类上新增方法, 后面的子类同一实现即可。
新增接口方法, 不需要再新增接口类

  • 因此这个工厂更像一个有很多能力的大工厂了, 不再是一个简单的create()了

抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易,所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
增加新的产品族很方便,无须修改已有系统,符合“开闭原则”。


Q: 抽象工厂的缺点?#

A:
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,甚至需要修改抽象层代码,这显然会带来较大的不便,违背了“开闭原则”。


Q: 讲一下工厂方法模式和抽象工厂模式的应用?#

A:

  • 我们最常用的 Spring 就是一个最大的 Bean 工厂,IOC 通过FactoryBean对Bean 进行管理。
  • 我们使用的日志门面框架slf4j,点进去就可以看到熟悉的味道 private final static Logger logger = LoggerFactory.getLogger(HelloWord.class);
    而这里的工厂实现,通过类加载去获取,应用开发者根本不需要关心背后实现是什么。 项目维护人员维护好日志jar包和配置即可。
  • JDK 的 Calendar 使用了简单工厂模式Calendar calendar = Calendar.getInstance();
  • 日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。
  • 数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。设计一个连接服务器的框架,需要三个协议,“POP3”、“IMAP”、“HTTP”,可以把这三个作为产品类,共同实现一个接口。
  • 比如 Hibernate 换数据库只需换方言和驱动就可以

[toc]

OOA#

Object-Oriented Analysis:面向对象分析法
指的是在一个系统的开发过程中进行了系统业务调查以后,按照面向对象的思想来分析问题。

  • OOA方法的具体步骤
    在用OOA具体分析一个事物时。大致上遵循如下5个基本步骤;
  1. 确定对象和类。这里所说的对象是对数据及其处理方式的抽象,它反映了系统保存和处理现实世界总某些事物的信息能力。。类是多个对象的共同属性和方法集合的描述,它包括如何在一个类中建立一个新对象的描述。
  2. 确定结构(structure)。结构是指问题域的复杂性和连接关系。类成员结构反映了泛华—特化关系,整体-部分结构反映整体和局部之间的关系
  3. 确定主题(subject)。主题是指事物的总体概貌和总体分析模型
  4. 确定属性(attribute)。属性就是数据元素,可用来描述对象或分类结构的实例,可在图中给出,并在对象的存储中指定。
  5. 确定方法(method)。方法是在收到消息后必须进行的一些处理方法:方法要在图中定义,并在对象的存储中指定。对于每个对象和结构来说,那些用来增加、修改、删除和选择一个方法本身都是隐含的(虽然它们是要在对象的存储中定义的,但并不在图上给出),而有些则是显示的。

OOD#

面向对象设计(Object-oriented Design,OOD)方法是oo方法中一个中间过渡环节。其主要作用是对OOA分析的结构作进一步的规范化整理,以便能够被oop直接接受。

OOD就是“根据需求决定所需的类、类的操作以及类之间关联的过程”。

使用OOD这种设计范式,我们可以用对象(object)来表现问题领域(problem domain)的实体,每个对象都有相应的状态和行为


Q: OO面向对象(Object-Oriented)和PO面向过程(process oriented)的区别?#

A:

  • 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
  • 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

OOP#

所谓的OOP就是指的面向对象编程
OOP的一条基本准则是计算机程序是由单个能够引起子程序作用的单元或对象组合而成.OOP达到了软件工程的三个主要目标:重用性,灵活性和扩展性.为了实现整体运算,每个对象都能够接收消息,处理数据和向其它对象发送消息.OOP主要有以下的概念和组件.

DDD#

https://blog.csdn.net/h295928126/article/details/122951726

DDD(domain driver designer,也就是领域驱动设计),它有三个关键词:领域,驱动,设计。领域,是要探索业务的边界;驱动,表示前者是后者的决定性因素;设计,包括产品设计,UIUE设计,软件设计。它不仅仅是开发架构的方案,而是完整的解决方案实施思路。正是因为它是完整的方案,才能让领域专家,产品和研发真正在同一个角度去思考和沟通,避免推诿扯皮,含糊不清。
从上文就可以知道DDD是一种拆解业务、划分业务、确定业务边界的方法, 是一种高度复杂的领域设计思想,将我们的问题拆分成一个个的域, 试图分离技术实现的复杂性,主要解决的是软件难以理解难以演进的问题,DDD不是一种架构, 而是一种架构方法论, 目的就是将复杂问题领域简单化, 帮助我们设计出清晰的领域和边界, 可以很好的实现技术架构的演进。下面为常用的的设计架构图

在这里插入图片描述

[toc]

单例模式,顾名思义, 就是一个系统内只提供一个单例,只有一个静态getInstance()方法。

  • 单例模式的类,其构造函数是private的,禁止外部去new

deb5ab479e94e371f07f770c11c80a0b202a07c4


Q: 讲一下单例模式的优点?#

A:

  • 在内存中只有一个对象,节省内存空间;

  • 避免频繁的创建销毁对象,可以提高性能;

  • 避免对共享资源的多重占用,简化访问;

  • 为整个系统提供一个全局访问点。


Q: 讲一下单例模式的缺点?#

A:

  • 不适用于变化频繁的对象;
  • 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;

单例的几种java实现#

Q: 下面的饿汉单例实现有什么问题?#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 饿汉式单例
public class Singleton1 {

// 指向自己实例的私有静态引用,主动创建
private static Singleton1 singleton1 = new Singleton1();

// 私有的构造方法
private Singleton1(){}

// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton1 getSingleton1(){
return singleton1;
}
}

A:
会造成启动速度变慢。 如果这个单例对象比较大或者多的话。 例如springbean的单例,如果都设置成饿汉式,则启动会很慢。


Q: 下面的懒汉式单例虽然能避免启动速度过慢,但这个代码有什么问题?#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 懒汉式单例
public class Singleton2 {

// 指向自己实例的私有静态引用
private static Singleton2 singleton2;

// 私有的构造方法
private Singleton2(){}

// 以自己实例为返回值的静态的公有方法,静态工厂方法
public static Singleton2 getSingleton2(){
// 被动创建,在真正需要使用时才去创建
if (singleton2 == null) {
singleton2 = new Singleton2();
}
return singleton2;
}
}

A:
一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式


Q: 讲一下懒汉加载如何避免上述的问题?#

A:
使用双重加锁, 先判空, 判空后再加锁判断, 拿到锁之后再进行new
0b16110f2c2e97166c7998baf7ebdb228d00c4cb


Q: 可以不用锁实现java的单例延迟加载吗?#

A:
可以, 使用内部类的静态初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private Singleton() {
//实例的初始化
}

static class SingletonHolder {
static Singleton instance = new Singleton();
}

public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

原因是因为内部类的加载和初始化本身是有用到类锁的,而内部类只会用到的时候加载, 不会提前被扫描进来。因此不会出现重复new两次, 但能够保证用到的时候才生成。

[toc]

23种设计模式(5):原型模式 CSDN

含义: 就一个clone的抽象接口, 告诉实现的子类们 你们必须实现这个方法, 因为外面(client)很可能经常要用到你的拷贝类

48fdece0e11b0bb3039a1e8d5ff1467bad7e9e24

应用场景#

而且用到clone,一般做的是复制对象或者直接取出一个不可变的引用, 不需要重新new一个新对象
clone可以带参数,例如clone(int type)
实现类里有一个内存map<type, 原型对象> , 从map里取出已经构造好的返回去。

优点#

  • 提升创建对象的性能
    因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

  • 简化对象的创建
    使得创建对象就像我们在编辑文档时的复制粘贴一样简。

缺点#

如果返回的原型对象会被外部调用进行”修改“,而非检查读操作, 那么容易引发原型对象的变更,导致下一次clone时内容发生变化。
因此一般要注意返回的是不可变类。

java中的原型类应用:#

  • 实现Cloneable接口。
    在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写Object类中的clone方法。
    Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。

[toc]

单一职责原则 (Single Responsibility Principle)#

就一个类而言,应该仅有一个让它变化的原因;通俗地说,即一个类只负责一项职责


开放-关闭原则 (Open-Closed Principle)#

软件实体 (类、模块、函数等等) 应该是可以被扩展的,但是不可被修改
即尽可能避免修改内部逻辑,而是扩展实现


里氏替换原则 (Liskov Substitution Principle)#

当使用继承时候,类 B 继承类 A 时,除添加新的方法完成新增功能 P2,尽量不要修改父类方法预期的行为
即你新增的P2,不能影响父类方法其他的功能逻辑, 必须其他方法中用到了override的P2,结果导致出错了。


依赖倒转原则 (Dependence Inversion Principle)#

高层模块不应该依赖低层模块,二者都应该于抽象。进一步说,抽象不应该依赖于细节,细节应该依赖于抽象
处理业务逻辑的程序员提供一个封装好数据库操作的抽象接口,交给低层模块的程序员去编写,这样双方可以单独编写而互不影响


接口隔离原则 (Interface Segregation Principle)#

客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上


迪米特法则(Law Of Demeter)#

它表示一个对象应该对其它对象保持最少的了解。通俗来说就是,只与直接的朋友通信
对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外提供 public 方法,不对泄漏任何信息。


组合/聚合复用原则 (Composite/Aggregate Reuse Principle)#

对于被依赖的类来说,无论逻辑多么复杂,都尽量的将逻辑封装在类的内部,对外提供 public 方法,不对泄漏任何信息。

设计模式原则例子详解

[toc]

总线#

1概念#

总线是连接多个部件的信息传输线
各部件共享的传输介质(多个组件共用1条总线)

1.1设备概念:#

主设备——当前获得总线控制权的设备
从设备——被主设备访问的设备,只能响应

1.2特点:#

分时: 同一时刻只允许一个组件发送消息
共享: 可多个线同时接收信息

2 总线分类#

2.1 按结构分类#

2.1.1 单总线#

内存、CPU、I/O都连接同一根线
发生抢占时,需要让总线判断优先级
且不适用单周期指令,1个时钟周期内没法传输地址+数据

2.1.2 面向CPU双总线#

总共两根线,分别是

  • M内存总线(CPU与内存之间的总线)
  • I/O总线(I/O和CPU的总线)
    缺点:
    当希望从IO直接传给内存时,需要先经过CPU

2.1.3 三总线#

以内存为中心
CPU<–> Memory <–> I/O
CPU和Memory之间可能存在DMA进行高速交互。

2.2 按连接部件分类#

2.2.1 片内总线#

指在芯片内部的总线
比如CPU内部、寄存器之间、ALU与寄存器

2.2.2 系统总线#

连接CPU\主存、IO的总线
数据线: 传送指令、数据、中断信号
地址线: 访问主存或者IO,由CPU单向通往内存。 位数决定地址空间
控制线: 监控和维护各部件状态, 传送应答信号

2.2.3 通信总线(外总线)#

计算机和其他系统之间的总线(计算机外部的)。

猝发传输: 一次性传输存储地址下的多个字

3. 性能指标#

  • 传输周期/总线周期 —— 一次总线操作所需的时间
  • 总线宽度——数据总线的根数(并非地址总线)
  • 时钟同/异步——和CPU时钟是否同步工作
  • 总线复用—— 能否在1根总线中复用地址和数据的功能
  • 信号线数——地址、数据、控制 三线总和
  • 控制方式—— 突发控制/计数控制/配置控制
  • 其他指标—— 负载能力/电源电压/宽度扩展性

4. 总线标准#

总线标准, 指 系统和模块间的互连标准, 只要求自身与接口一致即可, 不一定要求所有线都一样。

  • ISA—— 独立于CPU的总线时间
  • EISA—— 和ISA兼容, 主线控制权不由CPU决定
  • VESA—— 局部总线, 使用高速信息传输通道
  • PCI——局部总线, 猝发传输
  • AGP——显卡专用总线, 双激励,时钟上下沿都可以传输
  • RS-232C——用于DTE终端设备和DCE数据通信设备。 解/调码器之间的那根线
  • USB——设备总线, 连接设备与设备控制器
  • PCI\AGP\PCI-E 负责连接 主存、网卡、视频卡之类内部局部总线

5. 总线仲裁#

f1b6d0148c0426779d3adb1f906a9e81cf2e4f1c

5.1 判优控制#

主设备才有优先级之分
主线才能传输数据。

  • 集中式总线: 控制逻辑集中于CPU,利用一个特定的裁决算法进行裁决
  • 分布式总线: 优先逻辑分散到各部件

集中式仲裁方式如下:

5.2 链式查询#

0873d9af0c25df689e116c960251d1dfd8204452

  • 总线上所有的部件共用一根总线请求线,当有部件请求使用总线时,需经此线发总线请求信号到总线控制器,总线控制器便查询总线是否忙碌,如不忙碌便立即发总线响应信号到 BG 线串行地从一个部件传送到下一个部件,依次查询,直到某个部件有总线请求便不再传下去。
  • 此方式下,部件离总线控制器越近优先级越高,离总线控制器越远则优先级越低。
  • 优点:优先级固定,只需较少的控制线就能按一定优先次序实现总线控制,结构简单,扩充容易。
  • 缺点:对硬件电路的故障敏感,且优先级不能改变,这要极易导致当优先级高的部件频繁请求总线时,优先级低的部件长期不能使用总线。

5.3 计数器定时查询(n根)#

5a8557fe7db45a7b9a15729a6a18cb8d67c545b5

仍是共有一根总线请求线。#

  • 工作原理如下,当总线控制器收到总线请求信号并判断总线空闲时,计数器开始计数,计数值通过设备地址线发向各个部件,当地址线上的计数值与请求使用总线设备的地址一致时,该设备获得总线控制权,同时中止计数器的计数及查询。
  • 优点:计数器计数可从“0”开始,当设备优先次序固定,则设备优先级就按0,1……的顺序排列,固定不变;计数可以从上一次的终点开始,即采用一种循环方法,此时设备使用总线的优先级相等;计数器的初值还可由程序设置,因此优先次序可以改变,且这种方式对电路的故障不那么敏感。
  • 缺点:增加了控制线,若设备有 n 个,则大致需要 ?log_2?n ?+2条控制线,控制也比链式查询复杂。

5.4 独立请求#

d87d9c7042b349025ebc77b41891c34fba9fd50b

  • 每个设备都有一对总线请求线和总线允许线,当部件需要使用总线时,经各自的总线请求线向总线控制器发送总线请求信号,在控制器中排队,总线控制器按一定的优先次序决定批准某个部件的请求,并经该部件的总线允许线向该部件发送总线响应信号,将总线控制器交给该部件。
  • 优点:响应速度快,对优先次序的控制相当灵活。
  • 缺点:控制线数量多,若有设备 n 个,则需要 2n+1 条控制线,其中的 1 是指反馈线,用于让设备向总线控制器反馈已经使用完总线;总线控制逻辑复杂。

5.5 分布式仲裁#

分布仲裁方式不需要中央仲裁器,每个潜在的主模块都有自己的仲裁号和仲裁器。
当它们有总线请求时,就会把它们各自唯一的仲裁号发送到共享的仲裁总线上,每个仲裁器从仲裁总线上得到的仲裁号与自己的仲裁号比较。
若仲裁总线上的仲裁号优先级高,则它的总线请求不予响应,并撤销它的仲裁号。最后,获胜者的仲裁号保留在仲裁总线上。

通信方式#

总线周期的4个阶段
申请分配——寻址——传数据——结束

同步通信#

按照4个周期上升沿才执行,主从设备完全按照周期执行
1314b925470f419d06eb92e8fd83c23ca79e7bce

读: 发地址->发送读命令->读取数据->撤销占用
写:发地址->提取数据->发送写命令->撤销
用于存取时间比较一致且短的场合

异步通信#

不会一定要主从设备按照一样的周期执行
dad51425b7a657c21e60027d737acbbf975a85df

  • 不互锁: 不用接收应答,按时间撤销
  • 半互锁: 主模块接收应答才撤销——共享存储器
  • 全互锁: 主从都要应答,类似TCP的应答方式

半同步通信#

和同步类似, 但是在命令和数据周期内增加了Tw等待周期。
发送方用系统时钟前沿法信号
接收方用后沿判断和等待
f9664e2e26ce03170b7537e95fb020cf5816c758

以上三种通信的共同点:
(1)主模块发地址、命令 占用总线
(2)从模块准备数据 不占用总线 总线空闲(3)从模块向主模块发数据 占用总线

分离式通信#

充分挖掘系统总线每个瞬间的潜力
一个总线传输周期
子周期1 :主模块申请占用总线,使用完后即放弃总线的使用权。
子周期2:从模块申请占用总线,将各种信息送至总线上。

分离式通信特点:
1、各模块有权申请占用总线
2、采用同步方式通信,不等对方回答
3、各模块准备数据时,不占用总线
4、总线被占用时,无空闲


Q: 什么是总线锁?有什么缺点?替代方案是什么?#

A:

  • 在早期处理器提供一个 LOCK# 信号,CPU1在操作共享变量的时候会预先对总线加锁,此时CPU2就不能通过总线来读取内存中的数据了
  • 缺点:但这无疑会大大降低CPU的执行效率。其他CPU都无法正常操作了。
  • 替代方案:缓存一致性协议
    由于总线锁的效率太低所以就出现了缓存一致性协议,Intel 的MESI协议就是其中一个佼佼者。MESI协议保证了每个缓存变量中使用的共享变量的副本都是一致的
    2a4506c7e57b0e03c7d9a44613a843dce3ee207e
  • CPU1使用共享数据时会先数据拷贝到CPU1缓存中,然后置为独占状态(E),这时CPU2也使用了共享数据,也会拷贝也到CPU2缓存中。
  • 通过总线嗅探机制,当该CPU1监听总线中其他CPU对内存进行操作,此时共享变量在CPU1和CPU2两个缓存中的状态会被标记为共享状态(S);
  • 若CPU1将变量通过缓存回写到主存中,需要先锁住缓存行,此时状态切换为(M),向总线发消息告诉其他在嗅探的CPU该变量已经被CPU1改变并回写到主存中。
  • 接收到消息的其他CPU会将共享变量状态从(S)改成无效状态(I),缓存行失效。若其他CPU需要再次操作共享变量则需要重新从内存读取。

Q: 什么情况下缓存一致性会失效?#

A:

  • 共享变量大于缓存行大小,MESI无法进行缓存行加锁;
  • CPU并不支持缓存一致性协议

Q: 缓存一致性可能会引发什么问题?#

A: 可能会引发总线风暴。原因如下:

  • 嗅探机制:每个处理器会通过嗅探器来监控总线上的数据来检查自己缓存内的数据是否过期,如果发现自己缓存行对应的地址被修改了,就会将此缓存行置为无效。当处理器对此数据进行操作时,就会重新从主内存中读取数据到缓存行。
  • MESI会触发嗅探器进行数据传播。当有大量的volatile 和cas 进行数据修改的时候就会产大量嗅探消息。
  • 结合上面2点, 总线是固定的,所有相应可以接受的通信能力也就是固定的了
  • 如果缓存一致性流量突然激增,必然会使总线的处理能力受到影响。而恰好CAS和volatile 会导致缓存一致性流量增大。如果很多线程都共享一个变量,当共享变量进行CAS等数据变更时,就有可能产生总线风暴。
    因此volatile和CAS不能用太多。

[toc]

cpu包含两类东西:

  1. 控制器。
  2. 运算器ALU

详解:

1. 控制器#

控制器主要负责

  • 取指令
  • 分析指令(译码)
  • 执行指令

包含的东西有

  • 程序计数器PC
  • 指令寄存器IR
  • 指令译码器
  • 地址寄存器MAR
  • 数据寄存器MDR
  • 时序系统,分频,时序信号
  • 微操作信号发生器CU
  • 中断标记寄存器

2.运算器#

包含

  • ALU算术逻辑单元(底层是数电的那种门电路实现)
  • 暂存寄存器——只负责暂存读出来的数据,不存中间结果
  • 累加寄存器——暂存ALU的“运算”结果
  • 通用寄存器组—— 存放操作数和地址信息等。
  • 程序状态字寄存器PSW——存放当前指令执行结果的各种状态信息,如有无进位(CY位),有无溢出(OV位),结果正负(SF位),结果是否为零(ZF位),奇偶标志位(P位)
  • 移位器
  • 计数器——控制乘除时的操作步数

指令周期#

CPU指令周期分为 取指、间指、执行、中断四个周期。

  • 取指:
    PC->MAR->主存->MDR->IR->CU控制PC+1

简单来说
PC把指令地址存入MAR地址寄存器
主存取出指令数据,放到MDR
MDR把指令转到IR里存着
CU控制PC+1

  • 间指
    IR->MDR->MAR->存储器->MDR

要寻找指令执行数据的地址
IR解析地址数据,放到MDR中
MDR把地址放到MAR中,让他进行读操作
主存中读出的数据放入MDR中

  • 中断
    PC->MDR->主存 原地址存入主存
    CU->PC更新中断地址