这里是有关设计模式,Linux,Git的一些八股,后续会补充场景题
1.设计模式
1.概述
- 创建型模式:创建型模式关注对象的创建过程,让对象的创建更灵活、更可控。 这类模式的特点是,不让用户依赖于对象的创建或排列方式,避免用户直接使用new运算符创建对象。
- 结构型模式:结构型模式关注类和对象的组合,形成更大的结构,提升系统的灵活性。 和类有关的结构型模式设计如何合理地使用继承机制;和对象有关的结构型模式涉及如何合理地使用对象组合机制。
- 行为型模式:行为型模式关注对象之间的通信和职责分配,让协作更清晰。
创建型模式
创建型模式关注对象的创建过程,让对象的创建更灵活、更可控。 这类模式的特点是,不让用户依赖于对象的创建或排列方式,避免用户直接使用new运算符创建对象。
单例模式:确保一个类只有一个实例,并提供一个全局访问点。
核心:保证整个程序里只有一个对象实例。
抽象工厂模式:提供一个创建一系列相关或依赖对象的接口,而无需指定它们具体的类。
核心:一次性创建一组相关的对象。
解释:像订购一个套餐,里面包含多个搭配好的东西。
工厂方法模式:定义一个创建对象的接口,但让子类决定实例化哪个类。
核心:让子类来决定创建哪种对象。
解释:就像工厂流水线,具体生产什么产品由“分厂”自己定。
建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
核心:分步骤把复杂对象拼装起来。
解释:就像搭积木,一步步把房子建好。
原型模式:通过复制现有对象来创建新对象。
结构型模式
结构型模式关注类和对象的组合,形成更大的结构,提升系统的灵活性。 和类有关的结构型模式设计如何合理地使用继承机制;和对象有关的结构型模式涉及如何合理地使用对象组合机制。
适配器模式:将一个类的接口转换成客户端期望的另一个接口。
核心:让不兼容的东西能一起用。
解释:就像电源转换器,把插头改成能用的形状。
桥接模式:将抽象部分与它的实现部分分离,使它们可以独立地变化。
核心:把抽象和实现分开,让它们独立变化。
解释:像手机和充电器,分开设计但能搭配使用。
组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。
解释:像文件夹和文件,层层嵌套但统一管理。
装饰者模式:动态地给一个对象添加一些额外的职责。
核心:动态给对象加点新功能。
解释:就像给蛋糕加奶油,随时装饰一下。
外观模式:为子系统中的一组接口提供一个统一的接口。
核心:给复杂系统提供一个简单入口。
解释:像遥控器,一个按钮控制一堆功能。
享元模式:运用共享技术有效地支持大量细粒度的对象。
核心:共享对象来省内存。
解释:就像公共自行车,大家轮着用同一辆。
代理模式:为其他对象提供一种代理以控制对这个对象的访问。
核心:通过中间人控制对对象的访问。
解释:像中介,帮你跟房东谈租房。
行为型模式
行为型模式关注对象之间的通信和职责分配,让协作更清晰。
责任链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
核心:把请求扔给一串处理者,谁能搞定谁上。
解释:像客服转接,问题层层传递。
命令模式: 将请求封装成一个对象,从而使你可用不同的请求对客户进行参数化。
核心:把请求打包成对象来处理。
解释:像点外卖,把订单写好再交给厨师。
解释器模式: 给定一个语言,定义它的文法的一种表示,并定义一个解释器。
核心:为特定语言定义解释规则。
解释:像翻译官,解读外语的意思。
迭代器模式: 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
核心:挨个访问集合里的东西,不用管里面怎么存。
解释:像翻书,一页页看过去。
中介者模式: 用一个中介对象来封装一系列的对象交互。
核心:用一个中间人协调多个对象的关系。
解释:像群聊管理员,帮大家沟通。
备忘录模式: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
核心:保存对象的状态,随时可以恢复。
解释:像游戏存档,随时读档重来。
观察者模式: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
核心:一个对象变了,其他跟着知道。
解释:像订阅公众号,有更新就通知你。
状态模式:允许一个对象在其内部状态改变时改变它的行为。
核心:状态变了,行为也跟着变。
解释:像红绿灯,颜色不同规则就不同。
策略模式: 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
核心:封装一堆方案,随时换着用。
解释:像导航选路线,走快路还是省钱路随便挑。
模板方法模式: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。
核心:定个大框架,细节留给别人填。
解释:像做菜的食谱,大步骤固定,调料你自己加。
访问者模式: 在不改变数据结构的前提下,定义作用于这些元素的新操作。
核心:不改结构也能加新功能。
解释:像请专家来检查设备,不用自己动手改。
2.单例模式
单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。
单例模式通常用于管理全局共享资源,例如数据库连接池、日志对象等。
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类, 那它就能调用单例类的静态方法。 无论何时调用该方法, 它总是会返回相同的对象。
实现单例模式的关键点
- 私有构造方法:确保外部代码不能通过构造器创建类的实例。
- 私有静态实例变量:持有类的唯一实例。
- 公有静态方法:提供全局访问点以获取实例,如果实例不存在,则在内部创建。
常见写法如下(重点掌握懒汉,饿汉,双重锁检查):
1、 饿汉式(线程安全)
饿汉式在类加载时就创建实例,因此线程安全,不会出现多次创建实例的问题。缺点是即使不需要该实例,类也会被加载。
1 | public class Singleton { |
2、 懒汉式(线程不安全)
在这种方式中,单例对象是在首次使用时被创建的。但由于没有同步处理,多个线程同时访问时可能会创建多个实例。
1 | public class Singleton { |
3、 懒汉式(线程安全)
通过 synchronized
关键字来保证多线程下的安全性,但性能较差,因为每次调用 getInstance()
时都会进行同步。
1 | public class Singleton { |
4、双重锁检查(DCL)
双重检查锁定是一种优化的懒汉式实现,它结合了懒加载和线程安全的优点,只有在实例为空时才会进入同步块,从而减少了锁的竞争。
懒加载 (lazy loading):使⽤的时候再创建对象
1 | public class Singleton { |
1. 为什么要双重检查?
检查次数 | 目的 | 性能影响 |
---|---|---|
第一次检查 | 避免不必要的同步(实例已存在时) | 减少锁竞争 |
第二次检查 | 防止重复创建实例(当多个线程通过第一次检查时) | 保证单例 |
场景:多线程并发调用getInstance()
- 线程A首次调用getInstance()
- 第一次检查
instance == null
:true(实例尚未创建) - 进入同步块(获取类锁)
- 第二次检查
instance == null
:true - 执行
new DoubleCheckedSingleton()
- 释放锁
- 返回新创建的实例
- 第一次检查
- 线程B在A创建实例期间调用getInstance()
- 第一次检查
instance == null
:- 可能看到
null
(未完全构造的对象) - 也可能看到非null(已构造完成)
- 可能看到
- 如果看到
null
:- 尝试获取锁(此时线程A持有锁,线程B阻塞)
- 当线程A释放锁后:
- 线程B获得锁
- 第二次检查
instance == null
:false(已被线程A创建) - 直接返回已存在的实例
- 第一次检查
- 线程C在实例创建完成后调用getInstance()
- 第一次检查
instance == null
:false - 直接返回已存在的实例(不进入同步块)
- 第一次检查
5、枚举单例
枚举单例是实现单例模式的最佳方式之一。它由 JVM 保证线程安全和单例,并且防止反序列化创建新的对象。
1 | public enum Singleton { |
6、静态内部类
利用 JVM 的类加载机制,通过静态内部类实现延迟加载。内部类只有在 getInstance() 被调用时才会加载。
1 | public class Singleton { |
总结
- 懒汉式(线程不安全):适用于多线程环境下,但不安全。
- 懒汉式(线程安全):通过
synchronized
确保线程安全,但性能较差。 - 饿汉式:类加载时即创建实例,线程安全,但缺乏灵活性。
- 双重锁检查:性能较好,推荐使用。
- 枚举单例:推荐使用,简洁且线程安全。
3.工厂模式
Java 中的 工厂模式(Factory Pattern)是一种创建型设计模式,旨在通过定义一个接口来创建对象,但让子类决定实例化哪个类。工厂模式可以帮助减少客户端与具体产品类之间的耦合,提高代码的灵活性和扩展性。
1、简单工厂模式
简单工厂模式通过一个工厂类来根据提供的信息生成不同类型的对象。它不需要暴露创建对象的具体逻辑,只暴露一个工厂方法供客户端调用。
结构:
- 工厂类:负责创建实例。
- 产品接口:定义产品的公共接口。
- 具体产品:实现产品接口的具体类。
1 | // 产品接口 |
优缺点:
- 优点:客户端只需要知道工厂类和产品接口,无需关心具体实现。
- 缺点:工厂类一旦增加新的产品,需修改工厂类代码,违反开闭原则。
2、工厂方法模式
工厂方法模式通过在抽象类中定义一个工厂方法,让子类去实现这个方法,从而决定创建哪种产品。这种模式通过继承和多态来让子类决定创建的产品类型,避免了修改工厂类。
结构:
- 抽象工厂:声明工厂方法。
- 具体工厂:实现工厂方法,负责创建具体产品。
- 产品接口:定义产品的公共接口。
- 具体产品:实现产品接口的具体类.
1 | // 产品接口 |
优缺点:
- 优点:遵循了开闭原则,可以通过扩展子类来增加新产品,不需要修改原有代码。
- 缺点:需要创建大量的具体工厂类,如果产品种类过多,工厂类会急剧增加。
3、抽象工厂模式
3.Spring 框架中都用到了哪些设计模式?
【单例模式】
- Spring 中的 Bean 默认都是单例的
【简单工厂】
- 由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。
- Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
【工厂方法】
- 实现了FactoryBean接口的bean是一类叫做factory的bean。其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getOjbect()方法的返回值。
【模板方法模式】
- Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 父类定义了骨架(调用哪些方法及顺序),某些特定方法由子类实现。
- 最大的好处:代码复用,减少重复代码。除了子类要实现的特定方法,其他方法及方法调用顺序都在父类中预先写好了。
【包装器设计模式 】
- 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
【观察者模式】
- Spring 事件驱动模型就是观察者模式很经典的一个应用。
- spring的事件驱动模型使用的是 观察者模式 ,Spring中Observer模式常用的地方是listener的实现
【适配器模式】
- Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
- Spring定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了
【装饰器模式】
- 动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。
- Spring中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper,另一种是类名中含有Decorator。
【动态代理】
- 切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。
- 织入:把切面应用到目标对象并创建新的代理对象的过程。
【策略模式】
- Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了Resource 接口来访问底层资源。
2.Linux
1.Linux文件系统
在Linux中,“一切都是文件 ”
1.1.inode
硬盘以扇区为最小存储单位,操作系统以块为单位进行读写,块由多个扇区组成,inode存储了文件元信息(例如权限、大小、修改时间以及数据块位置) ,inode 的访问速度非常快
- inode:记录文件的属性信息,可以使用
stat
命令查看 inode 信息。 - block:实际文件的内容,如果一个文件大于一个块时候,那么将占用多个 block,但是一个块只能存放一个文件。(因为数据是由 inode 指向的,如果有两个文件的数据存放在同一个块中,就会乱套了)
1.2.硬链接和软链接
在 Linux/类 Unix 系统上,文件链接(File Link)是一种特殊的文件类型,可以在文件系统中指向另一个文件。常见的文件链接类型有两种:
硬链接是多个目录项中的「索引节点」指向一个文件,也就是指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表, 所以硬链接是不可用于跨文件系统的。由于多个目录项都是指向一个 inode, 那么只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。
软链接相当于重新创建一个文件,这个文件有独立的 inode, 但是这个文件的内容是另外一个文件的路径,所以访问软链接的时候, 实际上相当于访问到了另外一个文件,所以软链接是可以跨文件系统的, 甚至目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。
2.Linux常用命令
2.1.文件相关
ls
:列出目录内容。cd
:更改当前目录。cd ..
回到上级目录,cd ~
回到用户的主目录。rm
:删除文件或目录。rm -r
递归删除目录及其内容。mkdir
:创建新目录。cat
:查看文件内容。pwd
:显示当前工作目录的完整路径。cp
:复制文件或目录。mv
:移动或重命名文件或目录。
文件权限详解
Linux 中的权限可以应用于三种类别的用户:
- 文件所有者(u)
- 与文件所有者同组的用户(g)
- 其他用户(o)
①、符号模式
符号模式使用字母来表示权限,如下:
- 读(r)
- 写(w)
- 执行(x)
- 所有(a)
例如:
chmod u+w file
:给文件所有者添加写权限。chmod g-r file
:移除组用户的读权限。chmod o+x file
:给其他用户添加执行权限。chmod u=rwx,g=rx,o=r file
:设置文件所有者具有读写执行权限,组用户具有读执行权限,其他用户具有读权限。
②、数字模式
数字模式使用三位八进制数来表示权限,数字是其各自权限值的总和:
- 读(r)= 4
- 写(w)= 2
- 执行(x)= 1
因此,权限模式可以是从 0(无权限)到 7(读写执行权限)的任何值。
- chmod 755 file:使得文件所有者有读写执行(7)权限,组用户和其他用户有读和执行(5)权限。
- chmod 644 file:使得文件所有者有读写(6)权限,而组用户和其他用户只有读(4)权限。
2.2.系统管理相关
ps
:显示当前运行的进程。top
:实时显示进程动态。kill
:终止进程。kill -9 PID
强制终止。慎用 kill -9:可能导致数据不一致,优先用
kill -15
正常终止。chmod
:更改文件或目录的权限。df
:显示磁盘空间使用情况。df -h
以易读格式显示。du
:显示目录或文件的磁盘使用情况。
3.高频问题
如何查看 Java 进程?
ps -ef | grep java
如何实时查看日志文件?
tail -f filename.log
,-f
表示循环读取,常用于查看递增的日志文件如何查看端口占用情况?
netstat -tunlp | grep 8080
,起码记住netstat
和grep
如何杀死一个进程?
kill
,-9是强制杀死,-15是正常终止如何查看服务器内存/CPU使用情况?
top
如何查找文件?
find
高级题目
CPU飙升或100%问题怎么排查?
找到占用 CPU 最高的 Java 进程:
top
找到占用 CPU 最高的线程:
top Hp 进程id
,H
参数表示要显示线程级别的信息,p
则表示指定的pid,也就是进程id。保存线程堆栈信息:jstack 用于生成 Java 进程的线程快照(thread dump)。线程快照是一个关于 Java 进程中所有线程当前状态的快照,包括每个线程的堆栈信息。 将罪魁祸首的线程id转为16进制,然后在jstack输出的日志中查找该线程的信息
导致CPU飙到100的情况可能有哪些?
- 无限循环
- 内存不足
- 高流量
- 循环等待
3.Git
git clone <repository-url>
:克隆远程仓库。git status
:查看工作区和暂存区的状态。git add <file>
:将文件添加到暂存区。git commit -m "message"
:提交暂存区的文件到本地仓库。git log
:查看提交历史。git merge <branch-name>
:合并指定分支到当前分支。git checkout <branch-name>
:切换分支。git pull
:拉取远程仓库的更新。
1.git merge和 git rebase的区别
- Rebase(变基)是将一个分支上的提交逐个地应用到另一个分支上,使得提交历史变得更加线性。 简而言之,rebase可以将提交按照时间顺序线性排列。
- Merge(合并)是将两个分支上的代码提交历史合并为一个新的提交。 在执行merge时,Git会创建一个新的合并提交,将两个分支的提交历史连接在一起。
3.场景
1.秒杀系统设计
什么是秒杀
通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动
业务特点
- 高并发:秒杀的特点就是这样时间极短、 瞬间用户量大。
- 库存量少:一般秒杀活动商品量很少,这就导致了只有极少量用户能成功购买到。
- 业务简单:流程比较简单,一般都是下订单、扣库存、支付订单
- 恶意请求,数据库压力大
解决方案
前端:页面资源静态化,按钮控制,使用答题校验码可以防止秒杀器的干扰,让更多用户有机会抢到
nginx:校验恶意请求,转发请求,负载均衡;动静分离,不走tomcat获取静态资源;gzip压缩,减少静态文件传输的体积,节省带宽,提高渲染速度
业务层:集群,多台机器处理,提高并发能力
redis:集群保证高可用,持久化数据;分布式锁(悲观锁);缓存热点数据(库存)
mq:削峰限流,MQ堆积订单,保护订单处理层的负载,Consumer根据自己的消费能力来取Task,实际上下游的压力就可控了。重点做好路由层和MQ的安全
数据库:读写分离,拆分事务提高并发度
秒杀系统设计小结
- 秒杀系统就是一个“三高”系统,即高并发、高性能和高可用的分布式系统
- 秒杀设计原则:前台请求尽量少,后台数据尽量少,调用链路尽量短,尽量不要有单点
- 秒杀高并发方法:访问拦截、分流、动静分离
- 秒杀数据方法:减库存策略、热点、异步、限流降级
- 访问拦截主要思路:通过CDN和缓存技术,尽量把访问拦截在离用户更近的层,尽可能地过滤掉无效请求。
- 分流主要思路:通过分布式集群技术,多台机器处理,提高并发能力。
2.分布式ID
一个分布式ID需要满足
- 全局唯一:ID 的全局唯一性肯定是首先要满足的!
- 高性能:分布式 ID 的生成速度要快,对本地资源消耗要小。
- 高可用:生成分布式 ID 的服务要保证可用性无限接近于 100%。
实现方案1——UUID