操作系统中的虚拟地址转换物理地址的全过程?
一个可执行文件被加载到内存中执行时,就成为了进程,在DOS时期,采用实地址模式,进程直接使用物理地址,但是这种情况下进程可以直接修改物理内存,很容易发生占用其他内存的情况,甚至会覆盖操作系统内存,因此要采用虚拟内存
操作系统负责虚拟内存映射到物理内存,保证多个进程独立,并且当线程访问不属于自己内存的地址的时候,操作系统要加以制止
虚拟内存:可以看成把一个线程空间切碎(分成一个个页),只有一部分页面会在内存中,当CPU访问的地址不在内存中时,可以在磁盘中加载对应的部分,同时内存不够时可以把长期不用的页面删除 ,保存在磁盘中
MMU(内存管理单元):负责把虚拟地址转换为物理地址,通过页号在 页表中查询对应的页框号 ,偏移地址不变,因此就完成了虚拟地址到物理地址的转换,MMU中会存放TLB,可以更快的转换
TCP/IP的拥塞控制
慢启动(slow-start)、拥塞避免(congestion avoidance)、快速重传(fast retransmission)和快速恢复(fastrecover)。
乐观锁和悲观锁,具体实现
悲观锁(Pessimistic Lock): 就是很悲观,每次去拿数据的时候都认为别人会修改。所以每次在拿数据的时候都会上锁。这样别人想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程
但是在效率方面,处理加锁的机制会产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,如果已经锁定了一个线程A,其他线程就必须等待该线程A处理完才可以处理
数据库中的行锁,表锁,读锁(共享锁),写锁(排他锁),以及syncronized实现的锁均为悲观锁
乐观锁(Optimistic Lock): 就是很乐观,每次去拿数据的时候都认为别人不会修改。所以不会上锁,但是如果想要更新数据,则会在更新前检查在读取至更新这段时间别人有没有修改过这个数据。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(当然也允许更新失败的线程放弃操作),乐观锁适用于多读的应用类型,这样可以提高吞吐量
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本(version)或者是时间戳来实现,不过使用版本记录是最常用的。
CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
设计模式?哪些框架有设计模式?每个模式是什么样的?
Spring框架中的设计模式:工厂设计模式(简单工厂和工厂方法),单例设计模式,代理设计模式,模板方法设计模式,观察者设计模式,适配器设计模式,装饰者设计模式,策略设计模式
工厂设计模式(简单工厂和工厂方法)
Spring使用工厂模式可以通过BeanFactory或ApplicationContext创建bean对象。
两者对比:
-
BeanFactory :延迟注入(使用到某个 bean 的时候才会注入),相比于BeanFactory来说会占用更少的内存,程序启动速度更快。
-
ApplicationContext :容器启动的时候,不管你用没用到,一次性创建所有 bean -BeanFactory 仅提供了最基本的依赖注入支持,ApplicationContext 扩展了 BeanFactory ,除了有BeanFactory的功能还有额外更多功能,所以一般开发人员使用ApplicationContext会更多。
单例模式
实现一个类只有一个实例化对象&提供一个全局访问点
Spring中bean的默认作用域就是singleton,Spring通过ConcurrentHashMap实现单例注册表的特殊方式实现单例模式。
代理设计模式
一个客户不想或者不能直接引用一个对象,可以通过代理对象在客户端和目标对象之间起到中介作用。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这时候Spring AOP会使用Cglib,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理。
模板方法设计模式
定义一个模板结构,将具体的内容延迟到子类中去实现
模板方法模式是一种行为设计模式,它定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变一个算法的结构即可重定义该算法的默写特定步骤的实现方式。Spring中jdbcTemplate、hibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是Spring并没有使用这种方式,而是使用Callback模式与模板方法配合,既达到了代码复用的效果,同时增加了灵活性。
观察者设计模式
观察者设计模式是一种对象行为模式。它表示的是一种对象与对象之间具有依赖关系,当一个对象发生改变时,这个对象锁依赖的对象也会做出反应。Spring事件驱动模型就是观察者模式很经典的应用。
适配器设计模式
适配器设计模式将一个接口转换成客户希望的另一个接口,适配器模式使得接口不兼容的那些类可以一起工作,其别名为包装器。在Spring MVC中,DispatcherServlet根据请求信息调用HandlerMapping,解析请求对应的Handler,解析到对应的Handler(也就是我们常说的Controller控制器)后,开始由HandlerAdapter适配器处理。为什么要在Spring MVC中使用适配器模式?Spring MVC中的Controller种类众多不同类型的Controller通过不同的方法来对请求进行处理,有利于代码的维护拓展。
装饰者设计模式
装饰者设计模式可以动态地给对象增加些额外的属性或行为。相比于使用继承,装饰者模式更加灵活。Spring 中配置DataSource的时候,DataSource可能是不同的数据库和数据源。我们能否根据客户的需求在少修改原有类的代码下切换不同的数据源?这个时候据需要用到装饰者模式。
策略设计模式
准备一组算法 & 将每一个算法封装起来,让外部按需调用 & 使得互换
Spring 框架的资源访问接口就是基于策略设计模式实现的。该接口提供了更强的资源访问能力,Spring框架本身大量使用了Resource接口来访问底层资源。Resource接口本身没有提供访问任何底层资源的实现逻辑,针对不同的额底层资源,Spring将会提供不同的Resource实现类,不同的实现类负责不同的资源访问类型。
为什么会出现线程不安全?
多线程同时操作对象的属性或者状态时,会因为线程之间的信息不同步,A线程读取到的状态已经过时,而A线程并不知道。所以并发安全的本质问题在于线程之间的信息不同步
wait,notify,notifyAll
1.wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。
2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。
3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。
当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。
只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。
也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程让其获得锁
4、wait() 需要被try catch包围,以便发生异常中断也可以使wait等待的线程唤醒。
5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。
6、notify 和 notifyAll的区别
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。
7、在多线程中要测试某个条件的变化,使用if 还是while?
要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑;显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while进行等待,直到满足条件才继续往下执行。
Tcp协议为啥稳定
我们都知道 IP 协议是“不太靠谱”。因为 IP 协议是不可靠的,所以 IP 数据包可能在传输过程中发生错误或者丢失。这就意味着,TCP 协议不得不面对以下三个问题。1)每个数据包有可能发送不成功 2)数据包在传输过程中有可能被丢弃 3)接收端有可能接受不到数据包
TCP 为了解决这丢包问题,提出两个补救措施。
- ACK 回复
在每收到一个正确的、符合次序的片段之后,就向发送方(也就是连接的另一段)发送一个特殊的 TCP 片段,用来知会(ACK,acknowledge)发送方:我已经收到那个片段了。这个特殊的 TCP片段 叫做 ACK 回复。如果一个片段序号为 L,对应ACK 回复有回复号 L+1,也就是接收方期待接收的下一个发送片段的序号。
- 重新发送机制
如果发送方在一定时间等待之后,还是没有收到 ACK 回复,那么它推断之前发送的片段一定发生了异常。发送方会重复发送(retransmit)那个出现异常的片段,等待 ACK 回复,如果还没有收到,那么再重复发送原片段… 直到收到该片段对应的 ACK 回复(回复号为 L+1 的 ACK)。
虽然采用 “ACK 回复” + “重新发送机制” 方式能实现不丢包,但是会存在两个问题。
-
效率低的问题。 stop-and-wait。stop-and-wait 虽然实现了 TCP 通信的可靠性,但同时牺牲了网络通信的效率。同时,在等待ACK的时间段内,我们的网络都处于闲置(idle)状态
-
有点小缺陷
TCP 为了进一步优化解决这两个问题,提出滑动窗口(sliding window)的概念。滑动窗口被同时应用于接收方和发送方, 发送方和接收方各有一个滑窗。当片段位于滑窗中时,表示 TCP 正在处理该片段。此外,如果滑窗中可以有多个片段,也就是可以同时处理多个片段。
对于发送端
如果滑动窗口第一个片段一直没有收到 ACK 回复,窗口不会向右滑动。但是发送方还是可以继续发送后面两个片段数据包。
对于接受端
如果滑窗中第一个片段先收到,滑窗会向右移动。如果滑窗中后面两个片段先收到,但是第一个片段没有收到。窗口不会向右滑动
那么实际应用中确实是这样吗?如果接收方每接受一个片段,就回复一个 ACK。这种效率有点低。所以实际应用中, TCP 协议为了减少了 ACK 回复所消耗的流量,采用的是累计 ACK 回复。 接收方往往利用一个 ACK 回复来知会连续多个片段的成功接收。通过累计 ACK,所需要的 ACK 回复通常可以降到 50%。
springboot启动流程
1.创建一个Spring应用
new SpringApplication(primarySources).run(args)
2.new SpringApplication
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
2.1 资源加载
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
2.2 将主类放入linkedHastSet当中
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
2.3 应用类型的选择
this.webApplicationType = WebApplicationType.deduceFromClasspath();
2.4 设置初始化(构造)器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
2.5 设置(应用)监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
2.6 配置应用的主方法所在类
this.mainApplicationClass = deduceMainApplicationClass();
}
3. run(args);
public ConfigurableApplicationContext run(String... args) {
3.1 计时器开始
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
3.2 配置参数
configureHeadlessProperty();
3.3 配置运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
3.4 监听器开始工作
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
3.5准备环境--监听器和应用参数
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
3.6配置忽略的Bean信息
configureIgnoreBeanInfo(environment);
3.7打印图案
Banner printedBanner = printBanner(environment);
3.8 创建应用上下文
context = createApplicationContext();
3.9 异常报告
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
3.10 准备上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
3.11 刷新上下文
refreshContext(context);
3.12 刷新上下文善后
afterRefresh(context, applicationArguments);
3.13 计时器结束
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
3.14 启动所有监听器
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
3.15 处理运行失败
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
3.16 将应用上下文传递给每一个监听器
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
进程通信的方式?
-
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
-
信号量( semophore ): 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-
消息队列( message queue ): 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
-
信号 ( sinal ): 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
-
共享内存( shared memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
-
套接字( socket ): 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
怎么判断链表有环
快慢指针
行锁是怎么加的?
创建行锁条件:
1、表中创建索引, select ... where 字段(必须是索引) 不然行锁就无效。
2、必须要有事务,这样才是 行锁(排他锁)
3、在select 语句后面 加 上 FOR UPDATE;
重载和重写?
重写: 1.发生在父类与子类之间 2.方法名,参数列表,返回类型(除过子类中方法的返回类型是父类中返回类型的子类)必须相同 3.访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private) 4.重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常 重载: 1.重载要求同名方法的参数列表不同(参数类型,参数个数甚至是参数顺序) 2.重载的时候,返回值类型可以相同也可以不相同。因此无法以返回类型作为方法重载的区分标准