软件开发工程师面试题目及答案(面试软件工程师的问题)

一 计算机网络

1. TCP三次握手, 四次挥手, TIME_WAIT状态.

三次握手:

(1)客户端发出连接请求报文, SYN=1(同步位), seq=x(初始序号),客户进程进入SYN-SENT状态

(2)服务器端收到请求报文, 发送确认报文, 确认报文中SYN=1,ACK=1, 确认号ack=x+1,同时选择一个初始序号seq=y, 服务器进程进入SYN-RCVD状态

(3)客户端进程收到确认后, 再发出确认, 确认报文ACK置1, 确认号ack=y+1, 自己的序号是seq=x+1, 客户端进程进入EXTABLISHED状态(已连接),服务器端进程收到报文后进入EXTABLISHED状态

补充 :

为什么要三次握手,而不是二次握手呢?

采用三次握手的目的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误

四次挥手 :

(1)客户端进程发出连接释放报文段, 并停止发送数据, 将FIN置1, seq=u, 进入FIN-WAIT-1状态

(2)服务器端进程收到释放报文段发出确认, ACK置为1, ack=u+1, seq=v,进入CLOSE-WAIT状态, 此时客户端到服务器端的连接就释放了, 服务器需通知高应用层的进程(不能向客户端进程发送数据了), 客户端收到确认报文后进入FIN-WAIT-2状态并等待服务器发出连接释放报文段.

(3)若服务器没有数据发送给客户端进程了, 发出释放报文段, FIN=1, 序号seq=w, 客户端还得继续重复发送过的报文段确认号ack=u+1, 之后服务器端进程进入LAST-ACK状态.

(4)客户端进程收到释放报文段,发出确认, ACK置1, ack=w+1, seq=u+1然后客户端进程进入TIME-WAIT状态, 客户端需等待2MSL时间后才进入CLOSED状态, 服务器端收到确认报文段进入CLOSED状态.

(关于需要经过2MSL时间的说明:1.保证确认报文能够到达服务器, 如果收不到服务器会超时重传连接释放报文段. 2防止失效的连接请求报文到达的情况)

2.拥塞控制,流量控制

(1)拥塞控制: 就是防止过多的数据注入到网络中, 这样可以使网络中的路由器或链路不致过载(是一个全局性的过程)

(2)流量控制 : 往往是指点对点通信量的控制, 是个端对端的问题, 通过抑制发送端发送数据的速率, 以便接收端来得及接受

问题:理想状态下吞吐量(网络输出的分组数目)是随着网络设备提供的负载(即单位时间内输入给网络的分组数目)增大而增大(比例近似1:1), 当吞吐量饱和之后, 吞吐量趋于平衡, 而实际是, 随着负载增加吞吐量反而越来越小

(3)几种拥塞控制方法

四种算法: 慢开始, 拥塞避免, 快重传和快恢复

先假定条件如下:

数据总是单方面传送, 另一方传送确认

接收方有足够缓存空间, 发送窗口的大小由网络的拥塞程度来决定

1).慢开始和拥塞避免

发送方维持一个拥塞窗口cwnd(大小取决于网络拥塞程度, 并动态变化), 发送方窗口等于拥塞窗口, 如果接收方接受能力不行, 发送窗口可能小于拥塞窗口

慢开始:

有小到大增大发送窗口(即有小到大增大拥塞窗口), 刚开始cwnd = 2MSS, 以后每接受一个报文增加一个MSS的数值(简单起见, 以报文段的个数作为窗口大小, TCP是使用字节作为窗口单位)

例如 :

初始设置cwnd = 1, 发送报文M1;

收到报文M1确认报文, cwnd = 2, 发送M2, M3

收到M2, M3确认, cwnd = 4, 发送M4-M7….

每经过一个传输轮次, 拥塞串口cwnd加倍

为了防止拥塞, 设置一个慢开始门限(ssthresh):

当cwnd < ssthresh, 使用上述慢开始算法

当cwnd > ssthresh, 停止使用慢开始算法而改用拥塞避免算法

当cwnd = ssthresh, 即可使用慢开始算法, 也可以使用拥塞避免算法

拥塞避免算法:

让拥塞窗口cwnd缓慢的增大, 每经过一个往返时间RRT就把cwnd + 1, 而不是加倍, 增长缓慢

慢开始判断只要出现拥塞(拥塞判断条件:发送方没有按时收到确认), 就把慢开始门限ssthresh设置为拥塞时发送窗口的一半(ssthresh = cwnd / 2 > 2? cwnd / 2 : 2), 然后拥塞控制窗口设置为1, 执行慢开始算法(让中间网络设备有足够时间把队列中的积压分组处理完毕)

2).快重传和快恢复

快重传算法是 : 要求接收方收到一个失序的报文段后就立即发出重复确认(不要发送数据时才捎带确认, 为的是让发送方及早知道报文段没有到达对方), 若接收方收到三个重复确认就立即重传对方未接收到的报文段

与快重传配合使用的算法是快恢复:

a)当连续接收到三个重复确认, 慢开始门限ssthresh减半(接下去并不是执行慢开始算法)

b)因为能接收到连续的重复确认(即表明并没有发生严重的拥塞), 不采用慢开始(即把cwnd置为1)而是把拥塞窗口置为慢开始门限减半后的数值执行拥塞避免算法, 使拥塞窗口缓慢地线性增大

3).总结回归实际

刚开始假定接收方拥有足够的缓存空间, 所以发送窗口仅依赖于拥塞窗口, 但实际上发送窗口也收到接受串口rwnd的限定

即发送方窗口上限 = Min[rwnd, cwnd]

二.Linux专区

1.Linux中用过的命令,优盘插入Linux操作系统后,是如何处理的

(1). 插入U盘

#fdisk –l (查看当前U盘的设备名称 ) 可以清楚看到,识别当前的U盘的文件名为“/dev/sdb1”

(2). 挂载U盘

#mount -t vfat /dev/sdb1 /mnt vfat表示所有的FAT文件系统

当前U盘使用的是FAT文件系统,若使用的是NTFS文件系统则使用 mount –t ntfs /dev/sdb1 /mnt

(3).读取

#ls /mnt/ 可以看到U盘里面的东东了

(4).卸载

#umount /dev/sdb1

三.数据库专区

**1.MySQL的索引实现及好处,为什么用自增列作为主键?

此处引用一篇博客能很好的奠定基础:https://www.cnblogs.com/bonelee/p/6225211.html

优点

有了索引.对于记录数量很多的表,可以提高查询速度.

缺点

索引是占用空间的.

索引会影响update insert delete速度

索引最好创建在:

索引要创建在where和join用到的字段上

**

2.数据库四大特性, 数据库事务的隔离级别

事务的四大特性(ACID):

(1)原子性(Atomicity)

(2)一致性(Consistency)

(3)隔离性(Isolation)

(4)持久性(Durability)

不考虑事务隔离的特性将引发下列问题:

(1)脏读:

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据

(2)不可重复读:

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了

(3)虚读(幻读):

幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读.

事务的隔离级别:

① Serializable (串行化):确保事务可以从一个表中读取相同的行, 事务持续期间禁止对该表进行插入, 更新和删除操作, 可避免脏读、不可重复读、幻读

② Repeatable read (可重复读):确保事务可以从同一个字段中读取相同的值, 事务持续期间禁止其他事务对该字段的更新, 但无法禁止其他事务的插入删除操作, 可避免脏读、不可重复读, 但无法避免幻读

③ Read committed (读已提交):只允许事务读取已提交的事务,可避免脏读的发生但无法避免不可重复读和幻读

④ Read uncommitted (读未提交):最低级别,任何情况都无法保证, 允许事务读取其他事务未提交的数据, 出现脏读, 不可重复读, 幻读

以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然级别越高,执行效率就越低。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)

可参考博文 :

https://blog.csdn.net/qq_33290787/article/details/51924963

https://www.cnblogs.com/ForeverLover/p/4866354.html

3.数据库的优化

(1)选取最适用的字段属性(应该尽量把字段设置为NOTNULL)

(2)使用连接(JOIN)来代替子查询(Sub-Queries)连接(JOIN)..之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作

(3)使用联合(UNION)来代替手动创建的临时表

(4)事务

可以使一整个的SQL语句块都得到执行或者都不执行(从状态到状态的一致性转变) .补充:事务性的独占性导致在高并发状态性能较差

(5)锁定表

有些情况下我们可以通过锁定表的方法来获得更好的性能

(6)使用外键

锁定表的方法可以维护数据的完整性,但是它却不能保证数据的关联性。这个时候我们就可以使用外键

(7)使用索引 (索引查看语句 : show index from table_name; )

作用:索引是提高数据库性能的常用方法,它可以令数据库服务器以比没有索引快得多的速度检索特定的行,尤其是在查询语句当中包含有MAX(),MIN()和ORDERBY这些命令的时候,性能提高更为明显.

作用对象(列):一般说来,索引应建立在那些将用于JOIN,WHERE判断和ORDERBY排序的字段上。尽量不要对数据库中某个含有大量重复的值的字段建立索引。对于一个ENUM类型的字段来说,出现大量重复值是很有可能的情况

(8)优化的查询语句

绝大多数情况下,使用索引可以提高查询的速度,但如果SQL语句使用不恰当的话,索引将无法发挥它应有的作用

(9)慢查询

https://www.cnblogs.com/kerrycode/p/5593204.html

4.MyIsam与Innodb分别使用场景

1)区别

(1)事务处理

MyIsam是非事务安全型, 而InnoDB是事务安全型的(支持事务处理等高级处理)

(2)锁机制不同:

MyISAM是表级锁,而InnoDB是行级锁;

(3)select ,update ,insert ,delete 操作:

MyISAM:如果执行大量的SELECT,MyISAM是更好的选择

InnoDB:如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表

(4)查询表的行数不同:

MyISAM:select count() from table,MyISAM只要简单的读出保存好的行数,注意的是,当count()语句包含 where条件时,两种表的操作是一样的

InnoDB : InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行

(5)外键支持:

mysiam表不支持外键,而InnoDB支持

2) 为什么MyISAM会比Innodb 的查询速度快。

INNODB在做SELECT的时候,要维护的东西比MYISAM引擎多很多;

(1)数据块,INNODB要缓存,MYISAM只缓存索引块, 这中间还有换进换出的减少;

(2)innodb寻址要映射到块,再到行,MYISAM 记录的直接是文件的OFFSET,定位比INNODB要快

(3)INNODB还需要维护MVCC一致;虽然你的场景没有,但他还是需要去检查和维护

MVCC ( Multi-Version Concurrency Control )多版本并发控制

3) 应用场景

MyISAM适合:(1)做很多count 的计算;(2)插入不频繁,查询非常频繁;(3)没有事务。

InnoDB适合:(1)可靠性要求比较高,或者要求事务;(2)表更新和查询都相当的频繁,并且行锁定的机会比较大的情况。

5.规范化(第一方式…第四范式)

1)函数依赖

如果存在关系r, 有相同的x则唯一确定一个y, 则称y函数依赖与x

非平凡依赖 : x -> y, y是x的非空子集

平凡依赖 : x -> y, y是x的子集

若在R(U)中, 如果X->Y, 并对于任意一个 真子集Z不能确定Y, 则称Y对X为完全函数依赖, 否则称为部分函数依赖;

若有X->Y(Y 不是X的真子集, 且X不依赖于Y), Y->W(且W不是Y的真子集), 则称W对X为传递函数依赖

2)码

如果K是 R< U, F >属性或者属性集合, 而且U完全函数依赖于K, 则K为R的候选码(而当U是部分函数依赖于K, 则称K为超码)

若候选码有多个, 选定其中一个为主码

包含在候选码中的属性称为主属性, 不包含在候选码中的属性称为非主属性

3)范式

1NF : 满足最低要求的称为第一范式

2NF : 若R属于1NF, 且每一个非主属性完全函数依赖于任一个候选码, 则R属于2NF

3NF : 若R< U, F >属于1NF, 属性Y及非主属性Z(Y不是X的子集), 使得X->Y, Y->Z成立, 且X不依赖于Y, 则称R< U, F >属于3NF(翻译 : 如果R属于第三范式, 则每一个非主属性既不传递依赖于码, 也不部分依赖于码)

BCNF : 关系模式R< U, F >属于1NF, 若X->Y, 且Y不是X的子集时X必含有码, 则R< U, F >属于BCNF

解析 :

非主属性对每一个码都是完全函数依赖

所有的主属性对每一个不包含它的码也是完全函数依赖

没有任何一组属性完全依赖于非码的一组属性

4NF : 限制关系模式中的属性之间不允许非平凡且非函数依赖的多值依赖

6.数据库查询不走索引的情况

1)单键值的b树索引列上存在null值, 因为HASHSET中不能存储空值导致Count(*)不能走索引.(B-tree索引is null不会走,is not null会走,位图索引 is null,is not null 都会走索隐列避免空列,一般选非空的列)

解决 : 需要加上NOT NULL或者把列的属性改为not null才能走索引

2)在索引列上进行函数运算或者加减乘除运算都会导致不走索引, 因为索引的存储的值只是列的原值而不是计算结果的值.

解决 : 可以在索引列上改用简历基于函数的索引

3)使用or会导致不走索引, 除非是or的每个列都建立索引

4)隐式转换导致不走索引, 原理与在索引上进行函数运算一样

5)表的数据库小或者需要选择大部分数据, 不走索引(例如查询查询数量超过表的一部分, MySQL30%, Oracle20%)

6)在索引列使用!= 或者<>(不等于), 可能导致不走索引, 也可能走INDEX FAST FULL SCAN

7)建立组合索引, 但查询并未使用组合索引的第一列, 此处有一个INDEX SKIP SCAN概念(最左前缀原则)

8)使用 like ‘%xxx’, 百分号在前导致不走索引, 但是使用 like ‘xxx%’可以走索引, like ‘%xxx%’也不走索引

9)使用not in, not exist不走索引

解决 : 可以尝试吧子查询的not in或者not exist改成左联接的方式

10)没有查询条件, 查询条件没索引

7.MVCC : https://blog.csdn.net/whoamiyang/article/details/51901888

四.框架专区

1.mybatis的缓存,以及设计一个缓存过期后缓存的清理模块

默认同一个sqlSession中开启一级缓存, 需要手动配置开启二级缓存(同一个namespace下的sqlSession共享的二级缓存)

2.Spring是如何处理循环依赖的?

3.SpringMVC与Struts2的比较总结

框架机制:

(1)Struts2采用Filter(StrutsPrepareAndExecuteFilter)实现,SpringMVC(DispatcherServlet)则采用Servlet实现

(2)Filter在容器启动之后即初始化;服务停止以后坠毁,晚于Servlet。Servlet在是在调用时初始化,先于Filter调用,服务停止后销毁

拦截机制:

Struts2:

a、Struts2框架是类级别的拦截,每次请求就会创建一个Action,和Spring整合时Struts2的ActionBean注入作用域是原型模式prototype(否则会出现线程并发问题),然后通过setter,getter吧request数据注入到属性。

b、Struts2中,一个Action对应一个request,response上下文,在接收参数时,可以通过属性接收,这说明属性参数是让多个方法共享的。

c、Struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也就无法用注解或其他方式标识其所属方法了

SpringMVC:

a、SpringMVC是方法级别的拦截,一个方法对应一个Request上下文,所以方法直接基本上是独立的,独享request,response数据。而每个方法同时又何一个url对应,参数的传递是直接注入到方法中的,是方法所独有的。处理结果通过ModeMap返回给框架。

b、在Spring整合时,SpringMVC的Controller Bean默认单例模式Singleton,所以默认对所有的请求,只会创建一个Controller,有应为没有共享的属性,所以是线程安全的,如果要改变默认的作用域,需要添加@Scope注解修改。

性能方面:

SpringMVC实现了零配置,由于SpringMVC基于方法的拦截,有加载一次单例模式bean注入。而Struts2是类级别的拦截,每次请求对应实例一个新的Action,需要加载所有的属性值注入,所以,SpringMVC开发效率和性能高于Struts2。

拦截方面:

Struts2有自己的拦截Interceptor机制,SpringMVC这是用的是独立的Aop方式,这样导致Struts2的配置文件量还是比SpringMVC大。

4.Mybatis关于乐观锁的实现

为表的字段添加一个version字段, 在执行更新的时候验证版本号

5.Mybatis中关于#{}与${}区别

ups_role_permission_dataparams

where role_id = #{roleId,jdbcType=INTEGER}

在这里用到了#{},使用#时:

1)用来传入参数,sql在解析的时候会加上” “,当成字符串来解析 ,如这里 role_id = “roleid”;

2)#{}能够很大程度上防止sql注入;

延伸:

1)用传入数据直接显示在生成的sql中,如上面的语句,用roleid=传入数据直接显示在生成的sql中,如上面的语句,用roleid={roleId,jdbcType=INTEGER},那么sql在解析的时候值为role_id = roleid,执行时会报错;

2)方式无法防止sql注入;3)方式无法防止sql注入;3)一般用入传入数据库对象,比如数据库表名;

4)能用#{}时尽量用#{};

注意:

mybaties排序时使用order by 动态参数时需要注意,使用${}而不用#{};

6.Spring IOC原理

https://blog.csdn.net/it_man/article/details/4402245

五.java基础

1.BIO与NIO的区别,NIO中select的作用?NIO中的Channel的设计模式?

IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO

连接建立的时候先在selector上注册, 等到真正请求进来的时候selector会轮训每个连接会被分配到线程进行处理

2.HashMap的JDK1.7,1.8实现,为什么要用红黑书替换,说一说红黑树?

即使使用再好的hash函数也可能会有大量的数据集中在几个桶中, 在同一个桶中的话查找性能会大大降低.

3.JDBC中PreparedStatement和Statement的区别?

PreparedStatement是java.sql包下面的一个接口,用来执行SQL语句查询,通过调用connection.preparedStatement(sql)方法可以获得PreparedStatment对象。数据库系统会对sql语句进行预编译处理(如果JDBC驱动支持的话),预处理语句将被预先编译好,这条预编译的sql查询语句能在将来的查询中重用,这样一来,它比Statement对象生成的查询速度更快.

(1)PreparedStatement可以写动态参数化的查询

(2)PreparedStatement比 Statement 更快(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)

(3)PreparedStatement可以防止SQL注入式攻击

参考:http://www.importnew.com/5006.html

4.java 线程池

(1)创建:

可以通过ThreadPoolExecutor来创建一个线程

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,

keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);

参数的意思:

corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程

runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

PriorityBlockingQueue:一个具有优先级得无限阻塞队列

maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助

RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

CallerRunsPolicy:只用调用者所在线程来运行任务。

DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

DiscardPolicy:不处理,丢弃掉。

当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)

线程池的关闭:通过调用线程池的shutdown或shutdownNow方法来关闭线程池

5.java反射

参考博客 : https://www.cnblogs.com/whoislcj/p/6038511.html

6.关于java序列化反序列化的一些事

推荐博客:http://blog.csdn.net/qq_16628781/article/details/70049623

7.重写equal()的同时也要重写hashcode()的原因:

http://www.cnblogs.com/happyPawpaw/p/3744971.html

8.CAS操作(Compare And Swap)

关于CAS操作与ABA问题解决(博文) :

https://www.cnblogs.com/549294286/p/3766717.html

9.Java集合类

9.1)List

9.1.1)ArrayList

底层是使用对象数组实现, 使用下标遍历更快

1)默认初始容量 : 10

2)容量扩展 : 1.5倍

9.1.2)LinkedList

底层是使用双向链表实现, 使用迭代器/foreach遍历更快

链表无所谓初始容量, 也不存在容量扩展

9.2)Map

9.2.1)HashMap

底层是hash数组加链表或者红黑树(jdk1.8之后, 链表长度大于8改用红黑树实现)

原理 : 通过散列函数hash函数计算出key值在hash数组中的位置, 再遍历该位置对应的链表或者红黑树中key值所在的节点

1)关于重写equals方法必须重写hashcode方法, 因为必须满足key值相等的时候hashcode必须相等, 这是重写hashcode方法的约束

2)默认初始容量 : 16

3)默认加载因子 : 0.75(当 The next size value at which to resize (capacity * load factor), size + 1 > 容量 * 加载因子的时候)

3)hash函数 : (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

9.2.2) ConcurrentHashMap

区别于HashMap的是使用了锁分段技术

ConcurrentHashMap的锁分段技术:假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

9.2.3)TreeMap

底层使用红黑树实现

9.3)Set

9.3.1)HashSet

底层是HashMap

它是如何保证元素唯一性的呢?依赖的是元素的hashCode方法和euqals方法

9.3.2)TreeSet

底层是TreeMap

它的排序是如何进行的呢?通过compareTo或者compare方法中的来保证元素的唯一性.元素是以红黑树的形式存放的.

10.可重入锁, 公平锁非公平锁

参考博文 : http://www.cnblogs.com/dolphin0520/p/3923167.html

11.volatile原理 :

JMM(java内存模型) :

Java内存模型规定所有的变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(类似于前面的高速缓存. 线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作. 并且每个线程不能访问其他线程的工作内存.

11.1)volatile 的做法 : 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值.

11.2)另外的做法 : 通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中, 因此可以保证可见性.

12.HashMap 多线程导致循环链

参考博文 : https://www.cnblogs.com/andy-zhou/p/5402984.html

13.HashMap中为什么长度必须为2的幂(取余的效率不如位运算)

所以初始化HashMap指定的容量最好是2的幂次, 避免重新计算

参考博文 : https://blog.csdn.net/zjcjava/article/details/78495416

14.HashMap多线程的Fail-Fast机制

例如 : 一个线程通过Iterator迭代器进行遍历的时候另一个线程对Hashmap进行修改, 则会导致遍历失败抛出ConcurrentModificationException

参考博文 : https://www.cnblogs.com/alexlo/archive/2013/03/14/2959233.html

15.内存对其原则及其作用

1)参考博文 : https://blog.csdn.net/chy19911123/article/details/48894579

16.wait和sleep的区别

wait方法属于Object类, sleep属于线程类. 当线程调用Object.wait(), 当前线程会释放出对该Object的锁, 并等待其他线程的notify之后才能进入锁对象等待锁定池或者wait的时候设置时间,时间到了之后也可以进入锁对象的等待锁定池. 当线程调用sleep(), 调用线程进入睡眠当不会释放锁的占有.

六 . 设计模式

1. 单例模式

1.1)饿汉模式

1.2)懒汉模式(使用双重检测, 防止并发下多个线程同时实例化)

七. Web

1.如何判断两次登陆为同一个用户

1.1)单服务器中 : 使用cookie, 客户端登录验证之后把状态存放于request的session中(服务器上), 第二次请求时从request中的session查看状态是否为登录态. 但是并不适用于多系统服务器群(因为cookie的作用域仅仅是对应的域名)

1.2)单点登录SSO(Single Sign On)

参考博客 : https://www.cnblogs.com/ywlaker/p/6113927.html

八. Redis

1. 关于Redis的分布式锁的实现 : https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

软件工程师面试常见题目

本文由 @职涯君 发布于 职涯宝 ,未经作者许可,禁止转载,欢迎您分享文章

发表评论

登录后才能评论
小程序
小程序
微信客服
微信客服
QQ客服 建站服务
分享本页
返回顶部