面试准备0918

ArrayList和LinkedList区别 底层实现

  • 底层实现不同:
    • ArrayList底层是基于数组实现的,有索引,支持随机访问
    • LinkedList底层是基于双向链表的,每个元素都包括指向前一个和后一个的引用
  • 查询的时间复杂度不同
    • ArrayList 查询的时间复杂度是O(1),查询块,增删慢
    • LinkedList 需要从头部或者尾部遍历链表,直到找到目标元素,查询的时间复杂度是O(n),查询慢,增删快
  • 空间占用不同
    • ArrayList 占用的空间是连续的,会产生内存碎片问题
    • LinkedList 占用的空间是不连续的,通过链表来连接元素,每个结点都包括前后结点的引用,占用的空间更大

hashMap底层实现

HashMap的底层在jdk1.8以前采用的是数组+链表的结构,JDK1.8,引入了红黑树,采用了数组+链表+红黑树的方式。HashMap内部维护了一个Entry数组,每个Entry包含了一个键值对,以及指向下一个Entry的指针。当向HashMap中添加元素时,

  1. 计算key的hashcode值。HashMap会调用key的hashCode()方法来计算其hashcode值。
  2. 计算key在数组中的位置。HashMap会将hashcode值对数组的长度-1取模, index = (length - 1) & hashcode得到key在数组中的位置。
  3. 如果该位置上不存在元素,直接插入新的键值对。如果该位置上已经存在元素,则需要遍历链表或红黑树,查找是否已经存在相同的key。如果存在相同的key,则更新对应的value值;如果不存在相同的key,将新的key-value对插入到链表或红黑树的末尾。jdk1.8采用的是尾插法,jdk1.7采用的是头插法。
  4. hashMap默认初始化长度为16,扩容因子是0.75(符合正态分布),当元素数量超过了12时,就会触发扩容操作。将数组的大小扩大一倍,并将原有的元素重新分配到新的数组中。
  5. 如果链表长度超过了阈值(默认为8)且数组长度超过64,则将链表转化为红黑树,以提高查找效率。

HashMap为什么容量要设置成2的倍数
1/位运算快 index = (n - 1) & hashcode
2/计算出来的桶的位置更加均匀,减少哈希冲突

HashMap头插与尾插
Jdk1.7采用的是头插法,1.8采用的是尾插法。
采用尾插法的好处是,当需要遍历链表时,可以按照元素被添加到HashMap中的顺序来遍历,这样可以保证遍历的顺序与元素被添加到HashMap中的顺序一致。此外,尾插法还可以避免出现死循环的情况,因为新元素总是被添加到链表的末尾,不会出现环形链表的情况。

为什么ConcurrentHashMap不能为null
安全,多线程环境下的安全和一致性
ConcurrentHashMap的key和value都不能为null。

如果允许key或value为null,那么在多线程环境下,可能会出现以下问题:

  1. 在某个线程中,将key或value设置为null,但是另一个线程可能还在使用该key或value,导致出现空指针异常等问题。
  2. 在某个线程中,将key或value设置为null,但是另一个线程可能还没有看到该变化,导致出现数据不一致的问题。
    因此,为了避免这些问题,ConcurrentHashMap规定key和value都不能为null,以保证多线程环境下的安全性和一致性。如果需要存储null值,可以使用特殊的占位符来代替null值,比如使用Optional类来包装value值。

ConcurrentHashMap是如何保证线程安全的

ConcurrentHashMap相当于是HashMap的多线程版本,它的功能本质上和HashMap没什么区别。因为HashMap在并发操作的时候会出现各种问题,比如死循环问题、数据覆盖等问题。而这问题,使用ConcurrentHashMap可以完美地解决。

1、JDK1.7实现原理
它基本延续了HashMap的设计, 采用的是数组加链表的形式。和HashMap不同的是,ConcurrentHashMap中的数组设计分为大数组Segment和小数组HashEntry,使用Segment分段锁,基于ReentrantLock重入锁实现的加锁和释放锁的操作,这样就能保证多个线程同时访问ConcurrentHashMap时,同一时间只能有一个线程能够操作相应的节点,从而保证了ConcurrentHashMap的线程安全。

2、JDK1.8
JDK1.8以后采用 了数组 加 链表 加 红黑树的方式优化了ConcurrentHashMap的实现。当链表长度大于8,并且数组长度大于64时,链表就会升级为红黑树的结构。它主要是使用了CAS 加 volatile 或者 synchronized 的方式来保证线程安全。

从源码片段中看到,添加元素时首先会判断容器是否为空, 如果为空则使用 volatile 加 CAS 来初始化, 如果容器不为空,则根据存储的元素计算该位置是否为空。

如果根据存储的元素计算结果为空则利用 CAS 设置该节点; 如果根据存储的元素计算为空不为空,则使用 synchronized 对头结点加锁,然后,遍历桶中的数据,并最后再判断是否需要转为红黑树。这样就能保证并发访问时的线程安全了。

相比jdk1.7,这样设计的好处是,使得锁的粒度相比Segment来说更小了,发生hash冲突 和加锁的频率也降低了,在并发场景下的操作性能也提高了。而且,当数据量比较大的时候,查询性能也得到了很大的提升。

java异常

java中的异常分为两种
运行时异常runtimeExpection和检测时异常checkedException
常见的运行时异常:
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针异常)
ArrayStoreException(数据存储异常,操作数组是类型不一致)
BufferOverflowException

常见的检测时异常:
IOException
FileNotFoundException
SQLException

对于异常的处理方式有三种:

  • try catch 捕获
  • 方法后面throws声明
  • 方法内部throw 抛出异常

==和quals

装箱拆箱

装箱和拆箱是基本数据类型和对应包装类的转换过程,通过装箱可以使基本数据类型转换成对应的包装类从而能够参与面向对象的操作,比如集合的使用以及与基本数据类型相关的工具方法。

装箱就是自动将基本数据类型转换为包装器类型;调用包装器的valueOf方法实现
拆箱就是 自动将包装器类型转换为基本数据类型。调用包装器的 xxxValue方法实现

在Java SE5之前,生成一个数值为10的Integer对象,必须
Integer i = new Integer(10); 提供自动装箱后,只需要Integer i = 10;

加入了缓存,在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

Java锁

java中常用的锁有两种,分别是通过关键字实现的synchronized锁
通过类的ReentrantLock锁,需要lock()和unlock()方法配合try/finally完成。

synchronized是Java的一个关键字,它能够将代码块/方法锁起来

  • 如果synchronized修饰的是实例方法,对应的锁则是对象实例
  • 如果synchronized修饰的是静态方法,对应的锁则是当前类的Class实例
  • 如果synchronized修饰的是代码块,对应的锁则是传入synchronized的对象实例

无论synchronized修饰的是方法还是代码块,对应的锁都是一个实例对象,在内存中,对象一般由三部分组成,分别是对象头、实际数据和padding,对象头包括Mark Word和class pointer,其中Mark Word会记录对象关于锁的信息。Mark Word对锁的状态记录一共有4种:无锁、偏向锁、轻量级锁和重量级锁

Jdk1.6以前,是重量级锁,加锁是C++,依赖底层操作系统的 mutex 相关指令实现,调用C++,由于权限隔离的关系,会有用户态和内核态之间的切换,损耗性能。
Jkd1.6进行锁优化,引入偏向锁和轻量级锁,在jvm层面实现锁的逻辑,不依赖底层操作系统,不存在用户态和内核态的切换,不会造成性能损耗。

偏向锁指的就是JVM会认为只有一个线程在执行同步代码,不存在竞争
在Mark Word会直接记录线程ID,只要线程来执行代码了,会比对线程ID是否相等,相等则当前线程能直接获取得到锁,执行同步代码
如果不相等,则用CAS来尝试修改当前的线程ID,如果CAS修改成功,就获取得锁,执行同步代码。如果CAS失败了,说明存在竞争,将偏向锁升级为轻量级锁,也就是自旋锁,
在轻量级锁状态下,当前线程会在栈帧下创建Lock Record,将Mark Word拷贝到Lock Record,且有个Owner指针指向加锁的对象。用CAS +自旋的方式获取锁。自旋会一直占用线程,自旋一定次数后,则升级为重量级锁,使用操作系统层面的mutex指令,用户态和内核态之间的切换,损耗性能。

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,
两者区别:

  • synchronized可以修饰方法和代码块,ReentrantLock只能用在代码块上
  • 获取锁和释放锁的方法不同,synchronized会自动加锁和释放锁,ReentrantLock需要手动加锁和释放锁
  • 锁的类型不同:synchronized是非公平锁,ReentrantLock既可以是公平锁也可以是非公平锁,默认是非公平锁。
  • 响应中断不同,如果发送死锁,synchronized会一直等待,ReentrantLock可以使用lockInterruptibly()获取锁相应中断指令并释放锁,从而解决死锁问题。
  • 底层实现不同,synchronized是JVM层面通过监视器moniter实现的,ReentrantLock是通过JDK层面基于AQS实现的。

什么是AQS锁?

AQS是一个抽象类,可以用来构造锁和同步类,如ReentrantLock

AQS的原理是,AQS内部有三个核心组件,一个是state代表加锁状态初始值为0,一个是获取到锁的线程,还有一个阻塞队列。当有线程想获取锁时,会以CAS的形式将state变为1,CAS成功后便将加锁线程设为自己。当其他线程来竞争锁时会判断state是不是0,不是0再判断加锁线程是不是自己,不是的话就把自己放入阻塞队列。这个阻塞队列是用双向链表实现的

可重入锁的原理就是每次加锁时判断一下加锁线程是不是自己,是的话state+1,释放锁的时候就将state-1。当state减到0的时候就去唤醒阻塞队列的第一个线程。

为什么AQS使用的双向链表?

因为有一些线程可能发生中断 ,而发生中断时候就需要在同步阻塞队列中删除掉,这个时候用双向链表方便删除掉中间的节点

有哪些常见的AQS锁

AQS分为独占锁和共享锁

ReentrantLock(独占锁):可重入,可中断,可以是公平锁也可以是非公平锁,非公平锁就是会通过两次CAS去抢占锁,公平锁会按队列顺序排队

Semaphore(信号量):设定一个信号量,当调用acquire()时判断是否还有信号,有就获取一个信号量,没有就阻塞等待其他线程释放信号量,当调用release()时释放一个信号量,唤醒阻塞线程。

应用场景:允许多个线程访问某个临界资源时,如上下车,买卖票

CountDownLatch(倒计数器):给计数器设置一个初始值,当调用CountDown()时计数器减一,当调用await() 时判断计数器是否归0,不为0就阻塞,直到计数器为0。

应用场景:启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行

CyclicBarrier(循环栅栏):给计数器设置一个目标值,当调用await() 时会计数+1并判断计数器是否达到目标值,未达到就阻塞,直到计数器达到目标值

应用场景:多线程计算数据,最后合并计算结果的应用场景

sleep()和wait()的区别

(1)wait()是Object的方法,sleep()是Thread类的方法

(2)wait()会释放锁,sleep()不会释放锁

(3)wait()要在同步方法或者同步代码块中执行,sleep()没有限制

(4)wait()要调用notify()或notifyall()唤醒,sleep()自动唤醒

yield()和join()区别

yield()调用后线程进入就绪状态

A线程中调用B线程的join() ,则B执行完前A进入阻塞状态

并发

  • 线程池的七大核心参数

池线程数量:
IO 密集型:线程中十分消耗Io的线程数*2
CPU密集型: cpu线程数量(+1)

  • 线程池的工作原理

线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。

阻塞队列有哪些

线程间的通信方式

  • volatile和synchronized
    volatile修饰的对象,任何线程对变量的访问都需要从共享内存中获取,并且对他的改变也必须同步刷到共享内存中,保证所有线程对这个变量的可见性
    synchronized可以修饰方法和同步块,保证在多线程的情况下,只有一个线程能进入执行这个方法,保证的线程对变量访问的可见性和排他性
  • 等待/通知机制
    调用 wait()方法后 线程从RUNNABLE 状态 进入 WAITING 状态, 进入等待队列等待
    调用 notify/notifyAll() 方法后 唤醒等待队列当中的任意一条或者全部线程去重新竞争锁
  • Thread.join():如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才 从thread.join()返回。
  • ThreadLocal: 线程本地缓存机制,缓存在当前线程,线程私有,所以是线程安全的。是线程变量,一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
  • 管道输入/输出流

进程间的通信方式

  • 管道:匿名管道(linux命令行里的|),命名管道
  • 消息队列:A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了,每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。多用于生产者消费者模型。
  • 共享内存:共享内存的机制,就是拿出一块虚拟地址空间来,多个进程都可以访问到这个存储空间,这样一个进程写入的东西,其他的进程马上就能看到了,不需要来回拷贝,解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,大大提高了进程间通信的速度。
  • 信号量:使用共享内存,可以会出现多线程访问类似的不安问题,通常配合信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,可以实现访问的互斥性,还可以通过 P 操作和 V 操作实现进程间的同步。
  • 信号:异步通信机制,信号可以在应用进程和内核之间直接交互,出现问题时候,可以利用信号来通知用户空间的进程发生了哪些系统事件
  • Socket:实现跨网络与不同主机上的进程之间通信

MYSQL事务、INNODB等

MyIsAm和InnoDB的区别

  • InnoDB有三大特性,分别是事务、外键、行级锁,这些都是MyIsAm不支持的,
  • InnoDB是聚簇索引,MyIAm是非聚簇索引,
  • MyIsAM的访问速度一般InnoDB快,差异在于innodb的mvcc、行锁会比较消耗性能,还可能有回表的过程(先去辅助索引中查询数据,找到数据对应的key之后,再通过key回表到聚簇索引树查找数据)

mysql事务特性

  • 原子性:一个事务内的操作统一成功或失败
  • 一致性:事务前后的数据总量不变
  • 隔离性:事务与事务之间相互不影响
  • 持久性:事务一旦提交发生的改变不可逆

怎么保证事务

原子性:由undolog日志保证,他记录了需要回滚的日志信息,回滚时撤销已执行的sql

一致性:由其他三大特性共同保证,是事务的目的

隔离性:由MVCC保证

持久性:由redolog日志和内存保证,mysql修改数据时内存和redolog会记录操作,宕机时可恢复

事务的隔离级别

在高并发情况下,并发事务会产生脏读、不可重复读、幻读问题,这时需要用隔离级别来控制

读未提交: 允许一个事务读取另一个事务已提交的数据,可能出现不可重复读,幻读。

读提交: 只允许事务读取另一个事务没有提交的数据可能出现不可重复读,幻读。

可重复读: 确保同一字段多次读取结果一致,可能出现欢幻读。

可串行化: 所有事务逐次执行,没有并发问日

Inno DB 默认隔离级别为可重复读级别,分为快照度和当前读,并且通过间隙锁解决了幻读问题。

什么是快照读和当前读

快照读读取的是当前数据的可见版本,可能是会过期数据,不加锁的select就是快照读

当前读读取的是数据的最新版本,并且当前读返回的记录都会上锁,保证其他事务不会并发修改这条记录。如update、insert、delete、select for undate(排他锁)、select lockin share mode(共享锁) 都是当前读

MVCC是什么

MVCC是多版本并发控制,为每次事务生成一个新版本数据,每个事务都由自己的版本,从而不加锁就解决读写冲突,这种读叫做快照读。只在读已提交和可重复读中生效。

  • 三个隐式字段

    • 最近修改记录的事务ID
    • 回滚指针,配合undolog指向数据的上一个版本
    • 隐藏主键
  • undo log日志:记录了数据历史版本,undolog版本链,具体查询返回哪个版本不是由版本链决定的,而是由MVCC的readview。用于回滚操作,保证了事务的原子性

  • readView:通过readview我们才知道自己能够读取哪个版本,包含四个核心字段
    在这里插入图片描述

  • 在RC隔离级别 读已提交,在事务中每一次执行快照读时生成readview

  • 在RR隔离级别 可重复读,仅在事务中第一次执行快照读时生成readview,后续复用该readview

TCP

三次握手的原因:

在两次握手的情况下,服务端没有中间状态给客户端来阻止历史连接,导致服务端可能建立一个历史连接,造成资源浪费

三次握手才可以阻止重复历史连接的初始化(主要原因),避免资源浪费
三次握手才可以同步双方的初始序列号

为什么需要 TIME_WAIT 状态?

  • 防止历史连接中的数据,被后面相同四元组的连接错误的接收;
  • 保证「被动关闭连接」的一方,能被正确的关闭;

TCP粘包

是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
正常的理想情况,两个包恰好满足TCP缓冲区的大小或达到TCP等待时长,分别发送两个包;
粘包:两个包较小,间隔时间短,发生粘包,合并成一个包发送;
拆包:一个包过大,超过缓存区大小,拆分成两个或多个包发送;
拆包和粘包:Packet1过大,进行了拆包处理,而拆出去的一部分又与Packet2进行粘包处理。
导致的原因:Nagle算法
因为TCP默认会使用Nagle算法,此算法会导致粘包问题。
而Nagle算法主要做两件事,1)只有上一个分组得到确认,才会发送下一个分组;2)收集多个小分组,在一个确认到来时一起发送。多个分组拼装为一个数据段发送出去,如果没有好的边界处理,在解包的时候会发生粘包问题。
接收方不及时处理
(接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时,前一包未被取走,那么就会接到前一包后,用户线程再取的时候就可能会一次取到多个包)

粘包问题如何处理?
1.Nagle算法问题导致的,需要结合应用场景适当关闭该算法。
2.发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
3.发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
4.可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

TCP 滑动窗口

TCP 流量控制,主要使用滑动窗口协议,滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量控制的目的。如果TCP发送方收到接收方的零窗口通知后,会启动持续计时器。计时器超时后向接收方发送零窗口探测报文,如果响应仍为0,就重新计时,不为0就打破死锁

TCP拥塞控制

发送方会维护一个拥塞窗口大小的状态变量,大小取决于网络的拥塞程度。发送方的发送窗口大小是取接收方接收窗口和拥塞窗口中较小的一个

拥塞控制有四种算法:

慢开始:从小到大主键发送窗口,每收到一个确认报文窗口大小指数增长

拥塞避免:当窗口大小到达一定阈值时,转为拥塞避免,每收到一个确认报文窗口大小+1。若此时网络超时,就把阈值调小一半,重新慢开始

快重传:要求接收方收到请求后要立即回复

快恢复:发送方连续收到多个确认时,就把拥塞避免阈值减小,然后直接开始拥塞避免

HTTP和HTTPS等

HTTP和HTTPS的区别

  • 端口号不同:HTTP是80,HTTPS是443
  • 安全性不同:http是明文传输,客户端和服务器端无法验证对方身份
    https在http和tcp之间加了ssl/tls对传输内容进行加密,安全性更高。
  • 消耗资源:和HTTP相比,HTTPS通信会因为加解密的处理消耗更多的CPU和内存资源。
  • 开销:HTTPS通信需要证书,这类证书通常需要向认证机构申请或者付费购买。

HTTP/1.1 相比 HTTP/1.0 提高了什么性能?

  • 使用长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
  • 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。

HTTP/2 相比 HTTP/1.1 性能上的改进:

头部压缩
二进制格式:增加了数据传输的效率
并发传输
服务器主动推送资源

TLS握手过程

HTTPS 相比 HTTP 协议多一个 TLS 协议握手过程,目的是为了通过非对称加密握手协商或者交换出对称加密密钥,这个过程最长可以花费掉 2 RTT,接着后续传输的应用数据都得使用对称加密密钥来加密/解密。

TLS 协议建立的详细流程:
第一次握手:
由客户端向服务器发起ClientHello 加密通信请求
发送内容包括(1)客户端支持的 TLS 协议版本
(2)客户端生产的随机数,后面用于生成「会话秘钥」条件之一。
(3)客户端支持的密码套件列表,如 RSA 加密算法。
第二次握手:
服务器收到客户端请求后,确认 TLS 协议版本,如果浏览器不支持,则关闭加密通信。
1/向客户端做出响应,也就是 SeverHello。包括(1)确认 TLS 协议版本号(2)服务器生产的随机数(Server Random),也是后面用于生产「会话秘钥」条件之一。(3)确认的密码套件列表,如 RSA 加密算法。
2/服务器为了证明自己的身份,会发送数字证书。
3/随后服务器端发送server hello done 告诉客户端已经把该给你的东西给你了,本次打招呼完毕。
客户端收到服务器的回应之后,拿到数字证书,去第三方机构CA验证数字证书的真实性。

第三次握手:
如果证书没有问题,客户端会从数字证书中取出服务器的RSA公钥,然后使用它加密一个随机数(pre-master key)发送给服务器。

服务器收到后,用RSA私钥解密,得到刚刚的随机数(pre-master key)。此时双方都得到了这三个随机数,通过协商的加密算法,就能计算出本次通信的「会话秘钥」。然后,向客户端发送最后的信息:
(1)加密通信算法改变通知,告诉服务端开始使用加密方式发送消息。
(2)再发送一个finish消息,把之前所有发送的数据使用会话密钥加密一下,让服务器做个验证,验证加密通信「是否可用」和「之前握手信息是否有被中途篡改过」。

第四次握手,
服务器端同样的操作,发送一个算法改变通知和finish消息。整个 TLS 的握手阶段全部结束。接下来,客户端与服务器进入加密通信,就完全是使用普通的 HTTP 协议,只不过用「会话秘钥」加密内容。

https工作流程

  1. 客户端发起Https 请求,连到服务器✁ 443 端口。

  2. 服务器必须要有一套数字证书(证书内容有公钥、证书颁发机构、失 日期等)。

  3. 服务器将自己数字证书发送给客户端(公钥在证书里面,私钥由服务器持有)。

  4. 客户端收到数字证书之后,会验证证书的合法性。如果证书验证通过, 就会生成一个随机的对称密钥,用证书的公钥加密。

  5. 客户端将公钥加密后的密钥发送到服务器。

  6. 服务器收到客户端发来的密文密钥之后,用自己之前保留的私钥对其 进行非对称解密,解密之后就得到客户端的密钥,然后用客户端密钥对返回数据进行对称加密

  7. 服务器将加密后的密文返回到客户端。

  8. 客户端收到后,用自己密钥对其进行对称解密,得到服务器返回数据。

rpc和http的区别?

  • 从功能特性来说。http是一个属于应用层的超文本传输协议,是万维网数据通信的基础,主要服务在网页端和服务端的数据传输上。
  • RPC是一个远程过程调用协议,它的定位是实现不同计算机应用之间的数据通信,屏蔽通信底层的复杂性,让开发者就像调用本地服务一样完成远程服务的调用。因此,这两个协议在定位层面就完全不同。
    从实现原理来说。
  • http协议是一个已经实现并且成熟的应用层协议,它定义了通信的报文格式Request Body和Request Header,以及Response Body和Response Header。也就是说,符合这样一个协议特征的通信协议,才是http协议。
  • RPC只是一种协议的规范,它并没有具体实现,只有按照RPC通信协议规范实现的通信框架,也就是RPC框架,才是协议的具体实现,比如Dubbo、gRPC等。因此,我们可以在实现RPC框架的时候,自定义报文通信的协议规范、自定义序列化方式、自定义网络通信协议的类型等等因此,从这个层面来说,http是成熟的应用协议,而RPC只是定义了不同服务之间的通信规范。
    最后,应用层面来说。
  • http协议和实现了RPC协议的框架都能够实现跨网络节点的服务之间通信。并且他们底层都是使用TCP协议作为通信基础。但是,由于RPC只是一种标准协议,只要符合RPC协议的框架都属于RPC框架。因此,RPC的网络通信层也可以使用HTTP协议来实现,比如gRPC、OpenFeign底层都采用了http协议。

总结:
RPC主要⽤用于公司内部的服务调⽤用,性能消耗低,传输效率⾼高,服务治理理⽅方便便。HTTP主要⽤用于对外的异构环境,浏览器器接⼝口调⽤用,APP接⼝口调⽤用,第三⽅方接⼝口调⽤用等。

cookie和session的区别

因为http协议是无状态的,无法识别两次请求是否来自同一个客户端,于是就有了cookie和session的概念。

cookies:是存放在客户浏览器中,每次http请求都会携带,可以用来告知服务端两个请求是否来自同一浏览器,cookied的单个数据大小和存储个数是有限制的,不同浏览器限制不同,cookie的安全性较低。

session:当客户端第一次请求服务器时服务器为这个请求分配的一块区域,存储结构为ConcurrentHashMap,服务器会将sessionId返回给客户端并存入cookie。相比cookie他的安全性更高,但失效时间较短

接下来客户端每次请求带着cookie,服务端会从中获取sessionId来进行匹配,就实现了服务器和客户端进行有记忆的对话。

Spring

NIO BIO AIO

BIO:同步并阻塞,线程发起 IO 请求,不管内核是否准备好 IO 操作,从发起请求起,线程一直阻塞,直到操作完成。
NIO:同步非阻塞,线程发起 IO 请求,立即返回;内核在做好 IO 操作准备之后,通过调用注册回调函数通知线程做 IO 操作,线程开始阻塞,直到操作完成。
AIO:异步非阻塞,线程发起 IO 请求,立即返回;内存做好 IO 操作准备之后,执行IO操作,直到操作完成或者失败,通过调用注册✁回调函数通知线程做 IO 操作完 成或者失败。

IO多路复用详解

通过一个线程同时监控多个线程的IO就绪操作,当某个线程可读或可写,就去通知用户进程。IO多路复用有三种方式
(1)select
(2)poll
(3)epoll:优点是当有文件描述符就绪时,只把已就绪的文件描述符写给用户空间,不需要每次都遍历FD集合;每个FD只有在调新增的时候和就绪的时候才会在用户空间和内核空间之间拷贝一次。

设计模式

设计模式六大原则

(1)单一职责原则:一个类或者一个方法只负责一项职责,尽量做到类只有一个行为引起变化;
(2)里氏替换原则:子类可以扩展父类的功能,但不能改变原有父类的功能
(3)依赖倒置原则:高层模块不应该依赖底层模块,两者都应该依赖接口或抽象类
(4)接口隔离原则:建立单一接口,尽量细化接口
(5)迪米特原则:只关心其它对象能提供哪些方法,不关心过多内部细节
(6)开闭原则:对于拓展是开放,对于修改是封闭的

分类

创建型模式:用于描述 怎样创建对象 将对象的创建与使用分离

  • 单例(饿汉 懒汉)
  • 原型模式: 浅克隆、深克隆
  • 工厂方法 :解偶
  • 抽象工厂
  • 建造者模式

JDK源码中使用到工厂模式的地方

  • Collection.iterator方法(单列集合获取迭代器的方法)
  • DateForamt类中的getInstance()方法 — 工厂模式
  • Calendar类中的getInstance()方法 — 工厂模式

结构型模式:用于描述 如何将类或者对象按照某种布局组成更大的结构,代表有代理、适配器、装饰
代理分为静态代理和动态代理
动态代理分为JDK代理和CGLib代理

行为型模式:用于描述 类和对象之间怎样相互协助共同完成单个对象无法单独完成的任务,以及怎么样分配职能,代表有模板方法模式、策略模式、观察者模式、备忘录模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值