Java高级面试总结

亮点

1、公司系统架构

2、业务架构说明

3、个人其它技术

对方公司了解

 

1、规划阶段

行业选定

公司背景

 

2、执行阶段

跳槽准备

用户包括四个,具体应对策略如下:

技术经理:技术

架构师:谈技术架构,谈业务架构。

cto/ceo:谈业务,谈项目管理。

人事 :职业规划、绩效考核、五险一金(基数)、福利待遇,年终奖。

 

 

3、心态要好

 

 

string为什么是final的?

字符串是恒定的,创建之后它们的值不能被改变

1.线程安全

2.支持字符串常量池数据共享,节省资源,提高效率(因为如果已经存在这个常量便不会再创建,直接拿来用)

 

hashmap原理 深入理解。

synchronized、lock、voilate区别应用场景?(参考Java多线程编程核心技术)

A、修饰同步方法

1、synchronized取得的锁都是对象锁,而不是把一段代码或者方法当作锁,多个

线程访问多个对象,则jvm会创建多个锁,

2、一个对象有同步和非同步方法,同步可以访问,非同步也可以异步访问,如果

访问其他同步方法,则需等待。

3、锁重入

在使用syn时,当一个线程得到一个对象后,再次请求此对象锁时是可以再次得到

该对象的锁的。

这也证明在一个syn方法/块的内部调用本类的其他syn方法/块时,是永远可以得到

锁的。(可重入锁支持父子继承的环境中)

如果不可锁重入的话,就会造成死锁。

 

4、出现异常,锁自动释放

5、同步不具有继承性

 

B、synchronized同步语句块

1、当两个并发线程访问同一个对象中的synchronized(this)同步代码块时,

一段时间内能有一个线程被执行,另一个线程必须等待当前线程执行完

这个代码块以后才能执行该代码块。

2、代码块间的同步性:在使用同步代码块时注意,当一个线程访问对象的一个

同步代码块时,其他线程对同一个对象的其他同步代码块的访问

将被阻塞,这说明synchronized使用的“对象监视器”是一个。

3、代码块也是锁当前对象的

 

4、锁非this对象:优点如果一个类中有很多synchronized方法,这时

能实现同步,但会受到阻塞,所以影响运行效率;

但如果使用同步代码块锁非this对象,则synchronizeed(非this)代码块中de

程序与同步方法是异步的不与其他锁this同步方法争抢this,则可以大大

提高运行效率。

 

C、静态同步synchronized方法与synchronized(class)代码块

关键字synchronized可以应用在static静态方法上,如果这样写,那就是

对当前的*.Java文件对应的Class类进行持锁。

 

 

D、多线程的死锁

不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。

 

Volatile

从公共堆栈中

 

 

 

 

 

 

高并发

秒杀方案

线上堆异常排查方案?

线程池原理

强烈建议程序员使用较为方便的 Executors 工厂方法

  Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)

 Executors.newFixedThreadPool(int)(固定大小线程池)和 

  Executors.newSingleThreadExecutor()(单个后台线程),

它们均为大多数使用场景预定义了设置。

如果设置的 corePoolSize 和 maximumPoolSize 相同,

则创建了固定大小的线程池。

 

BlockingQueue

  • 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
  • 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  • 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 

 

三者区别应用场景

排队有三种通用策略:

  1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。  

 

Redis底层原理

 

 

 

 

 

 

搭建SpringCloud 原理分析

 

 

 

jvm面试准备

 

1、对象分配

栈是运行时的单位,而堆是存储的单位。

1、栈处理逻辑,堆代表了数据,分而治之的思想。

2、堆中的内容可以被多个栈共享。

3、栈运行时,需要进行地址段的划分,只能向上增长。堆可以动态增长,栈只是记录了一个地址。

4、对象的一些属性,其实就是数据,存放在堆中。方法存放在栈中。面向对象的设计。

 

对象的大小,

Object obj =new Object();

一个空对象的大小为8个byte,保存堆中一个没有任何属性的对象的大小。

4byte+8byte =12byte  4byte为栈中存储的空间大小

 

2、对象回收

 

1、按照基本回收策略分

  1. 引用计数
  2. 标记-清除
  3. 复制
  4. 标记-整理

2、按分区对待的方式分

  1. 增量收集
  2. 分代收集

3、按系统线程分

串行收集

并行收集

并发收集

 

分代收集

年轻代

年老代

持久代

年轻代介绍

一个Eden区,两个Survivor区(一般而言)。

大部分对象在Eden区中生成。

年老代介绍

年轻代中经历了N次垃圾回收后仍然存活的对象。

持久代介绍

存放java类,静态文件等。

 

GC触发,两种类型

scavenge GC介绍

新对象生成,在edge申请空间失败时,触发。

FULL GC介绍

年老代被写满

持久代被写满

system.gc()

 

串行收集器

小应用。

并行收集器

对吞吐量有高要求,多CPU,对响应时间无要求的中大型应用。

Parallel Scavenge收集器 吞吐量优先

并发收集

可以保证大部分工作都并发执行(应用不停止),垃圾回收只暂停很少的时间,

次收集器适合比较高的中、大规模应用。

使用-XX:+UseConcMarkSweepGC打开。

 

 

 

 

 

 

对象回收判断 

1、引用计数算法

2、根搜索算法 (GC root tracing)java采用

收集算法

  1. 标记-清除
  2. 复制
  3. 标记-整理
  4. 分代收集

收集器(实现)   

  1. Serial收集 单线程收集器
  2. ParNew收集器 多线程收集器
  3. Parallel Scavenge收集器 吞吐量优先
  4. cms收集器 以最短停顿回收时间为目标的收集器 标记-清除算法

 

 

spring 事务面试准备

事务隔离级别

表示使用底层数据库的默认隔离级别

 

1、一个事务可以读取另一个事务修改但还没有提交的数据

Read uncommitted

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。

影响:出现脏读。

 

2、表示一个事务只能读取另一个事务已经提交的数据

Read committed

读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。

影响:出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。

 

3、Repeatable read

重复读,就是在开始读取数据(事务开启)时,不再允许修改操作

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。

分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

影响:

不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。

4、Serializable 序列化

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

 

 

事务传播行为

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。

创建一个新的事务,如果当前存在事务,则把当前事务挂起。

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

以非事务方式运行,如果当前存在事务,则抛出异常。

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行

  REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。

       NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

       REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

       MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

       SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

       NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

       NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

 

 

视频流技术 面试准备

实时传输协议

RTP(Real-time Transport Protocol)是一个网络传输协议

 

 

 

多线程面试准备

1、start方法和run方法的区别?

start实现了多线程运行,这时无需等待。start()方法启动线程将自动调用 run()方法。

run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。

(wait和sleep的区别)

wait时会释放锁资源但sleep不会释放锁资源,wait通常和notify以及notifyAll结合使用,需要notify或者notifyAll对其进行唤醒,sleep通常在指定的时间内自动唤醒。

线程池原理

当线程池中的线程数小于corePoolSize 时,新提交的任务直接新建一个线程执行任务(不管是否有空闲线程) 

当线程池中的线程数等于corePoolSize 时,新提交的任务将会进入阻塞队列(workQueue)中,等待线程的调度 

当阻塞队列满了以后,如果corePoolSize < maximumPoolSize ,则新提交的任务会新建线程执行任务,直至线程数达到maximumPoolSize 

当线程数达到maximumPoolSize 时,新提交的任务会由(饱和策略)管理

 

 

 

Dubbo面试准备

Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务。

 

 

 

http协议面试准备

http请求由三部分组成,分别是:请求行、消息报头、请求正文

1、常用方法:

GET

POST

PUT 传输文件到URL

delete 删除对应uri文件

2.get和post区别

 get取 post传输;

 get通过地址栏传输,用户可见,post通过请求体,用户不可见;

 get请求不安全,post相对安全

 get不支持中文

3.http请求报文与响应报文格式

a.请求行    状态行

b.首部字段

c.请求内容实体 响应内容实体。

 

6、常见HTTP首部字段

a、通用首部字段(请求报文与响应报文都会使用的首部字段)

Date:创建报文时间

Connection:连接的管理

Cache-Control:缓存的控制

Transfer-Encoding:报文主体的传输编码方式

b、请求首部字段(请求报文会使用的首部字段)

Host:请求资源所在服务器

Accept:可处理的媒体类型

Accept-Charset:可接收的字符集

Accept-Encoding:可接受的内容编码

Accept-Language:可接受的自然语言

c、响应首部字段(响应报文会使用的首部字段)

Accept-Ranges:可接受的字节范围

Location:令客户端重新定向到的URI

Server:HTTP服务器的安装信息

d、实体首部字段(请求报文与响应报文的的实体部分使用的首部字段)

Allow:资源可支持的HTTP方法

Content-Type:实体主类的类型

Content-Encoding:实体主体适用的编码方式

Content-Language:实体主体的自然语言

Content-Length:实体主体的的字节数

Content-Range:实体主体的位置范围,一般用于发出部分请求时使用

 

 

7、HTTP的缺点与HTTPS

a、通信使用明文不加密,内容可能被窃听

b、不验证通信方身份,可能遭到伪装

c、无法验证报文完整性,可能被篡改

HTTPS就是HTTP加上加密处理(一般是SSL安全通信线路)+认证+完整性保护

https协议:

 

http2.0新特点:

 

 

 

 

TCP协议面试准备

tcp/ip协议

(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。 

(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。 

(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下: 

(A)URG:紧急指针(urgent pointer)有效。 

(B)ACK:确认序号有效。 

(C)PSH:接收方应该尽快将这个报文交给应用层。 

(D)RST:重置连接。 

(E)SYN:发起一个新连接。 

(F)FIN:释放一个连接。 

三次握手

建立TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。

第一次握手:客户端发起请求连接,服务端接收,客户端为 SYN_sent状态。

第二次握手:服务端收到数据包,修改标识,服务端进入SYN_recd

第三次握手:客户端收到后检查标识,发送服务端 ,服务端比较正确建立成功。

 

四次挥手

终止TCP连接,需要客户端和服务端总共发送4个包以确认连接的端开。

第一次挥手:CLient发起一个fin,

第二次挥手:服务端收到fin后,发送确认信号给客户端。

第三次挥手:服务端发送一个fin,用来关闭数据传输。

第四次挥手:客户端收到fin后,客户端进入time_wait,发送ACK给服务端,服务端关闭。 

(2)为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

 

TCP粘包/拆包的解决办法

TCP是一个流协议,就是没有界限的一长串二进制数据。

发生TCP粘包或拆包有很多原因,现列出常见的几点,可能不全面,欢迎补充,

1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。

2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

粘包、拆包解决办法

通过以上分析,我们清楚了粘包或拆包发生的原因,那么如何解决这个问题呢?解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下几个:

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

等等。

 

 

 

 

说说 RPC 的实现原理

底层jdk动态代理,字节码实现。

1.先说出代理模式

2、引出动态代理

3、说一下rpc

1)服务消费方(client)调用以本地调用方式调用服务;

2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;

3)client stub找到服务地址,并将消息发送到服务端;

4)server stub收到消息后进行解码;

5)server stub根据解码结果调用本地的服务;

6)本地服务执行并将结果返回给server stub;

7)server stub将返回结果打包成消息并发送至消费方;

8)client stub接收到消息,并进行解码;

9)服务消费方得到最终结果。

确定消息数据结构

消息里为什么要有requestID?

client线程每次通过socket调用一次远程接口前,生成一个唯一的ID,即requestID(requestID必需保证在一个Socket连接里面是唯一的),一般常常使用AtomicLong从0开始累计数字生成唯一ID;

2)将处理结果的回调对象callback,存放到全局ConcurrentHashMap里面put(requestID, callback);

3)当线程调用channel.writeAndFlush()发送消息后,紧接着执行callback的get()方法试图获取远程返回的结果。在get()内部,则使用synchronized获取回调对象callback的锁,再先检测是否已经获取到结果,如果没有,然后调用callback的wait()方法,释放callback上的锁,让当前线程处于等待状态。

4)服务端接收到请求并处理后,将response结果(此结果中包含了前面的requestID)发送给客户端,客户端socket连接上专门监听消息的线程收到消息,分析结果,取到requestID,再从前面的ConcurrentHashMap里面get(requestID),从而找到callback对象,再用synchronized获取callback上的锁,将方法调用结果设置到callback对象里,再调用callback.notifyAll()唤醒前面处于等待状态的线程。

 

 

 

 

微服务与 SOA 的区别

 

 

接口幂等性实现方法?

接口可重复调用,在调用方多次调用的情况下,接口最终得到的结果是一致的。

幂等与你是不是分布式高并发还有JavaEE都没有关系。

关键是你的操作是不是幂等的。

一个幂等的操作典型如:

把编号为5的记录的A字段设置为0

这种操作不管执行多少次都是幂等的。

一个非幂等的操作典型如:

把编号为5的记录的A字段增加1

这种操作显然就不是幂等的。

1.要做到幂等性,从接口设计上来说不设计任何非幂等的操作即可。

譬如说需求是:

当用户点击赞同时,将答案的赞同数量+1。

改为:

当用户点击赞同时,确保答案赞同表中存在一条记录,用户、答案。

赞同数量由答案赞同表统计出来。

2.对于写入类接口,可以参考使用数据库唯一键实现事务幂等性。

3.预更新状态字段添加

4.全局唯一业务主键

5.去重表

6.版本号控制

7、加一个标志位结合缓存使用

public void doTestForInsert(Order order){

       String key=null;

       boolean needDel=false;

       try{

           Order orderInDB = orderDao.findByName(order.getOrderNo());

           //查DB,如果数据库已经有则抛出异常

           if(null != orderInDB)

              throw new Exception("the order has been exist!");

           key=order.getOrderNo();

           //插缓存,原子性操作,插入失败 表明已经存在

           if(!MemcacheUtil.add(key, order, MemcacheUtil.getExpiry(UNIT.MINUTE, 1)))

              throw new Exception("the order has been exist!");

           needDel=true;

           //插DB

           orderDao.save(order);

       }catch (Exception e) {

           e.printStackTrace();

       }finally{

           if(needDel)

              MemcacheUtil.del(key);

       }

    }

 

数据库层面:加索引,

消息实现幂等性?

重复消息是SOA服务实现中非常常见的问题,你永远不要指望调用方每次请求消息不一样,对于读操作,重复消息可能无害,可对于写操作很可能就是灾难。

可以通过幂等(Idempotent)模式处理重复的消息,基本处理思路是:

1、调用者给消息一个唯一请求ID标识。ID标识一个工作单元,这个工作单元只应执行一次,工作单元ID可以是Schema的一部分,也可以是一个定制的SOAP Header,服务的Contract 可以说明这个唯一请求ID标识是必须的;

2、接收者在执行一个工作单元必须先检验该工作单元是否已经执行过。检查是否执行的逻辑通常是根据唯一请求ID ,在服务端查询请求是否有记录,是否有对应的响应信息,如果有,直接把响应信息查询后返回;如果没有,那么就当做新请求去处理。

  1. 查询操作

查询一次和查询多次,在数据不变的情况下,查询结果是一样的。select是天然的幂等操作

  1. 删除操作

删除操作也是幂等的,删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个)

3.唯一索引,防止新增脏数据

比如:支付宝的资金账户,支付宝也有用户账户,每个用户只能有一个资金账户,怎么防止给用户创建资金账户多个,那么给资金账户表中的用户ID加唯一索引,所以一个用户新增成功一个资金账户记录

  1. token机制,防止页面重复提交
  2. 悲观锁

获取数据的时候加锁获取

select * from table_xxx where id='xxx' for update;

注意:id字段一定是主键或者唯一索引,不然是锁表,会死人的

悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用

  1. 乐观锁

乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高。

  1. 分布式锁

还是拿插入数据的例子,如果是分布是系统,构建全局唯一索引比较困难,例如唯一性的字段没法确定,这时候可以引入分布式锁,通过第三方的系统(redis或zookeeper),在业务系统插入数据或者更新数据,获取分布式锁,然后做操作,之后释放锁,这样其实是把多线程并发的锁的思路,引入多多个系统,也就是分布式系统中得解决思路。

  1. select + insert

并发不高的后台系统,或者一些任务JOB,为了支持幂等,支持重复执行,简单的处理方法是,先查询下一些关键数据,判断是否已经执行过,在进行业务处理,就可以了

注意:核心高并发流程不要用这种方法

  1. 状态机幂等

在设计单据相关的业务,或者是任务相关的业务,肯定会涉及到状态机(状态变更图),就是业务单据上面有个状态,状态在不同的情况下会发生变更,一般情况下存在有限状态机,这时候,如果状态机已经处于下一个状态,这时候来了一个上一个状态的变更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。

  1. 对外提供接口的api如何保证幂等

如银联提供的付款接口:需要接入商户提交付款请求时附带:source来源,seq序列号

source+seq在数据库里面做唯一索引,防止多次付款,(并发时,只能处理一个请求)

  1.  

一。IO请求的两个阶段:

1.等待资源阶段:IO请求一般需要请求特殊的资源(如磁盘、RAM、文件),当资源被上一个使用者使用没有被释放时,IO请求就会被阻塞,直到能够使用这个资源。

2.使用资源阶段:真正进行数据接收和发生。

二。在等待数据阶段,IO分为阻塞IO和非阻塞IO。

1.阻塞IO: 资源不可用时,IO请求一直阻塞,直到反馈结果(有数据或超时)。

2.非阻塞IO:资源不可用时,IO请求离开返回,返回数据标识资源不可用

三。在使用资源阶段,IO分为同步IO和异步IO。

1.同步IO:应用阻塞在发送或接收数据的状态,直到数据成功传输或返回失败。

2.异步IO:应用发送或接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或接收,并返回成功或失败的信息给应用。

 

分布式框架设计中的服务降级

业务高峰期,为了保证核心服务,需要停掉一些不太重要的业务,eg 商品评论、论坛或者粉丝积分等

另外一些场景就是某些服务不可用时,又不能直接让整个流程失败就本地Mcok(模拟)实现,做流程放通

eg 用户登录余额鉴权服务不能正常工作,需要做业务放通,记录消费话单允许用户继续访问,而不是返回失败

为了保证以上两种场景的正常服务,服务需要有降级

服务降级主要包括容错降级和屏蔽降级

屏蔽降级:1)throw null 不发起远程调用,直接返回空

         2)throw exception 不发起远程调用,直接抛出指定异常

         3)execute bean 不发起远程调用,直接执行本地模拟接口实现

 

服务降级是可逆操作,当系统压力恢复到一定值不需要降级服务时,要重新发起远程调用,服务状态改为正常

 

容错降级:非核心服务不可调用时,可以对故障服务做业务放通,保证主流程不受影响

1)RPC异常:通常指超时、消息解码异常、流控异常、系统拥塞保护异常等

2)Service异常 eg登录校验异常、数据库操作失败异常等

 

容错降级和屏蔽降级的区别在于:

1)触发条件不同:屏蔽降级往往是人工根据系统的运行,手动设置

                容错降级是根据服务调用返回的结果,结合当前的服务级别,自动匹配触发

2)作用不同:容错降级是服务不可用时,让消费者执行业务放通

            屏蔽降级主要目的是将原属于降级业务的资源调配出来供核心业务使用

3)调用机制不同,一个发起远程服务调用,一个只做本地调用

 

执行业务放通的Mock接口往往放在消费者端

1)执行业务放通时可能会依赖本地的资源,包括但不限于消费者依赖的服务、数据结构、数据库等资源,这些事消费者私有的,服务提供者不可见,若放到服务提供者,会造成资源依赖,导致系统耦合

2)不同的消费者消费同一个服务时,对失败之后的处理策略存在差异,若都搬到服务提供者的Mock接口,会造成代码臃肿,难以维护

服务降级的优先级是  消费者配置策略>服务提供者配置策略

屏蔽降级高于容错降级

 

实现服务降级需要考虑几个问题

1)那些服务是核心服务,哪些服务是非核心服务

2)那些服务可以支持降级,那些服务不能支持降级,降级策略是什么

3)除服务降级之外是否存在更复杂的业务放通场景,策略是什么?

 

 

(1)JAVA NIO方面的,特别是NIO的原理性知识,Linux的epoll机制为什么能提高NIO的性能。这方面谈的相当多,自己比较了解,应该还回答的不错。      (2)JAVA多线程方面的,线程池,concurrent包(特别是concurrenthashmap的高性能原理),锁机制(可重入锁、偏向锁、自旋锁、锁消除等等)。这方面也应该回答的不错,有个问题打错了,好像是 synchronized 是否是可重入的。

 

作者:tony

链接:https://www.zhihu.com/question/55622227/answer/145446038

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

redis集群和哨兵环境的搭建。

 

 

 

区块链技术学习

  • 交易(Transaction):一次操作,导致账本状态的一次改变,如添加一条记录;
  • 区块(Block):记录一段时间内发生的交易和状态结果,是对当前账本状态的一次共识;
  • 链(Chain):由一个个区块按照发生顺序串联而成,是整个状态变化的日志记录。

 

服务认证(JWT)

JWT(JSON Web Token), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

 

 

 

传输非对称加密

RSA加密方式是: 

(1)乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。 

(2)甲方获取乙方的公钥,然后用它对信息加密。 

(3)乙方得到加密后的信息,用私钥解密。

 

一致性hash算法实现原理

1、解决分布式Cache中提出的,设计目标是为了解决因特网中的热点(Hot spot)问题

2、负载均衡中要求资源被均匀的分布到所有节点上,并且对资源的请求能快速路由到对应的节点上

现在一致性hash算法在分布式系统中也得到了广泛应用,研究过memcached缓存数据库的人都知道,memcached服务器端本身不提供分布式cache的一致性,而是由客户端来提供,具体在计算一致性hash时采用如下步骤:

  1. 首先求出memcached服务器(节点)的哈希值,并将其配置到0~232的圆(continuum)上。
  2. 然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
  3. 然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上。
  4. 虚拟节点方式。

 

 

Spring事务实现原理之threadLocal

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。

ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。 

 

spring事务使用threadlocal

将connection绑定到当前线程来保证这个线程中的数据库操作用的是同一个connection。

//④从ThreadLocal中获取线程对应的  连接

如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,

并将其保存到线程本地变量中。

//③直接返回线程本地变量  

 

hashmap实现原理

1、定义:哈希表(hash table)也叫散列表。

在哈希表中进行添加,删除,查找等操作,性能十分之高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。

2、哈希冲突解决方案:HashMap即是采用了链地址法,也就是数组+链表的方式

3、哈希表的主干就是数组。

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。

4、HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的

在Java 8 中,如果一个桶中的元素个数超过 TREEIFY_THRESHOLD(默认是 8 ),就使用红黑树来替换链表,从而提高速度。

 

大数据快速查找

bitmap

2bitmap

在2.5亿个整数中找出不重复的整数,内存不足以容纳这2.5亿个整数。 

方案1:采用2-Bitmap(每个数分配2bit,00表示不存在,01表示出现一次,10表示多次,11无意义)进行,共需内存 内存,还可以接受。然后扫描这2.5亿个整数,查看Bitmap中相对应位,如果是00变01,01变10,10保持不变。所描完事后,查看bitmap,把对应位是01的整数输出即可。 

具体的过程如下:

   扫描着3亿个整数,组BitMap,先查看BitMap中的对应位置,如果00则变成01,是01则变成11,是11则保持不变,当将3亿个整数扫描完之后也就是说整个BitMap已经组装完毕。最后查看BitMap将对应位为01的整数输出即可。

 

数据库索引

组合索引

a,b,c

(前导列索引 a)最左原则

 

 

二叉树

树中每个节点最多只能有两个子节点,这样的树就称为“二叉树”

 

二叉搜索树

左小于父节点值,右大于等于父节点值。

 

 

红黑树

解决不平衡树

五个特点

根总是黑色的。

如果节点是红色的,子节点一定时黑色的

每个节点不是红色的,就是黑色的。

从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点。

 

 

冒泡排序

我们的冒泡排序之所以叫做冒泡排序,正是因为这种排序算法的每一个元素都可以像小气泡一样,根据自身大小,一点一点向着数组的一侧移动。

由于该排序算法的每一轮要遍历所有元素,轮转的次数和元素数量相当,所以时间复杂度是O(N^2) 

冒泡排序第一版:

 

public class BubbleSort {

private static void sort(int array[])

{

    int tmp  = 0;

 

for(int i = 0; i < array.length; i++){

        for(int j = 0; j < array.length - i - 1; j++)

        {

            if(array[j] > array[j+1])

            {

                tmp = array[j];

                array[j] = array[j+1];

                array[j+1] = tmp;

            }

        }

    }

}

public static void main(String[] args){

    int[] array = new int[]{5,8,6,3,9,2,1,7};

    sort(array);

    System.out.println(Arrays.toString(array));

}

}

外部循环控制所有的回合,内部循环代表每一轮的冒泡处理,先进行元素比较,再进行元素交换。

如果我们能判断出数列已经有序,并且做出标记,剩下的几轮排序就可以不必执行,提早结束工作。

 

 

冒泡排序第二版

 

 

public class BubbleSort {

private static void sort(int array[])

{

    int tmp  = 0;

    for(int i = 0; i < array.length; i++)

    {

        //有序标记,每一轮的初始是true

        boolean isSorted = true;

        for(int j = 0; j < array.length - i - 1; j++)

        {

            if(array[j] > array[j+1])

            {

                tmp = array[j];

                array[j] = array[j+1];

                array[j+1] = tmp;

                //有元素交换,所以不是有序,标记变为false

                isSorted = false;

            }

       }

        if(isSorted){

            break;

        }

    }

}

public static void main(String[] args){

    int[] array = new int[]{5,8,6,3,9,2,1,7};

    sort(array);

    System.out.println(Arrays.toString(array));

}

}

 

这一版代码做了小小的改动,利用布尔变量isSorted作为标记。如果在本轮排序中,元素有交换,则说明数列无序;如果没有元素交换,说明数列已然有序,直接跳出大循环。

我们可以在每一轮排序的最后,记录下最后一次元素交换的位置,那个位置也就是无序数列的边界,再往后就是有序区了。

 

 

冒泡排序第三版

 

 

这一版代码中,sortBorder就是无序数列的边界。每一轮排序过程中,sortBorder之后的元素就完全不需要比较了,肯定是有序的。

 

public class BubbleSort {

private static void sort(int array[])

{

    int tmp  = 0;

    //记录最后一次交换的位置

    int lastExchangeIndex = 0;

    //无序数列的边界,每次比较只需要比到这里为止

    int sortBorder = array.length - 1;

    for(int i = 0; i < array.length; i++)

    {

        //有序标记,每一轮的初始是true

        boolean isSorted = true;

        for(int j = 0; j < sortBorder; j++)

        {

            if(array[j] > array[j+1])

            {

                tmp = array[j];

                array[j] = array[j+1];

                array[j+1] = tmp;

                //有元素交换,所以不是有序,标记变为false

                isSorted = false;

                //把无序数列的边界更新为最后一次交换元素的位置

                lastExchangeIndex = j;

            }

        }

        sortBorder = lastExchangeIndex;

        if(isSorted){

            break;

        }

    }

}

 

public static void main(String[] args){

    int[] array = new int[]{3,4,2,1,5,6,7,8};

    sort(array);

    System.out.println(Arrays.toString(array));

}

}

鸡尾酒排序

 

 

 

 

 

 

 

 

 

KMP算法

KMP算法要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题

 

 

进程间通信

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有

管道(包括无名管道和命名管道)、

消息队列、

信号量、

共享存储、

Socket、

Streams等。

其中 Socket和Streams支持不同主机上的两个进程IPC。

 

FIFO,也称为命名管道,它是一种文件类型。

特点

  1. FIFO可以在无关的进程之间交换数据,与无名管道不同。
  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。 
  3. 消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

 

五种通讯方式总结

 

1.管道:速度慢,容量有限,只有父子进程能通讯    

 

2.FIFO:任何进程间都能通讯,但速度慢    

 

3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题    

 

4.信号量:不能传递复杂消息,只能用来同步    

 

5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

 

进程线程区别

1.定义

进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

线程:进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.

 

2.关系

一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

 

3.区别

  进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

2) 线程的划分尺度小于进程,使得多线程程序的并发性高。

3) 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

4) 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

5) 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

 

4.优缺点

线程和进程在使用上各有优缺点:

线程执行开销小,但不利于资源的管理和保护;

而进程正相反。

同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

 

hashcode()和equals()的作用、区别、联系

1.equal()相等的两个对象他们的hashCode()肯定相等,也就是用equal()对比是绝对可靠的。

 2.hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。

 

String 为什么是最终类型

Hashmap原理 以及扩容

多线程区别三个?

线程池原理?

 

String 原理分析

4种情况分析:

直接赋值字符串连接时,考虑编译期,和运行期

如果在编译期值可以被确定,那么就使用已有的对象,否则

创建新的对象。

 

        String s1 = "hi";//直接赋值会在字符串常量池(堆),创建

        String s2 = new String("hi");//new 申请内存空间 创建了2个对象 

        System.out.println(s1==s2);//false  首地址指向不一样

 

        //String 原理分析

        //4种情况分析:

        //直接赋值字符串连接时,考虑编译期,和运行期

        //如果在编译期值可以被确定,那么就使用已有的对象,否则创建新的对象。

        String a = "a";   

        String a1 = a +1;//编译此行时,a被看做变量,变量只有在运行时,才会有值

        String a2 ="a1";

        System.out.println(a1==a2);//false

 

        final String b = "b";  //final修饰的变量为常量 

        String b1 = b+1;//此时编译时,b被看做常量

        String b2 = "b1";

        System.out.println(b1==b2);//true

 

        String c = getC();//方法只会在运行时,有值

        String c1 = c+1;

        String c2 = "c1";

        System.out.println(c1==c2);//false

 

        final String d = getD();//同上

        String d1 = d +1;

        String d2 = "d1";

        System.out.println(d1==d2);//false

 

    }

 

    public static String getC(){

        return "c";

    }

    public static String getD(){

        return "d";

    }

 

<<,有符号左移位,将运算数的二进制整体左移指定位数,低位用0补齐。

>>,有符号右移位,将运算数的二进制整体右移指定位数,整数高位用0补齐,负数高位用1补齐(保持负数符号不变)。

>>>,无符号右移位,不管正数还是负数,高位都用0补齐(忽略符号位)

 

 

 

 

你可能感兴趣的:(技术,Java高级,Java面试)