前言
最近面试遇到的一些面试题,归纳了一些比较有代表性的基础题,这里只归纳了基础题,一部分题给出了答案。问到多次的问题我会在题目后面标注。这次面试频率问到最高的问题就是关于多线程和单点登录的(每次面试都问了),还有就是你做过的项目里面的技术栈也是面试官的突破口。
注:某些答案在后面表明了“粗略回答”的,都是需要你根据答案再去了解更深的原理。
正文
1.http协议下层协议是什么?TCP协议和UDP协议最本质的区别?socket套接字的作用?
- http属于应用层协议,他的下层是传输层(包括TCP,UDP都是传输层协议)
网际层协议:IP协议、ICMP协议、ARP协议、RARP协议。
传输层协议:TCP协议、UDP协议。
应用层协议:FTP、Telnet、SMTP、HTTP、RIP、NFS、DNS。 - TCP面向连接(传输前需要在发送端和接受端建立连接);支持IP环境下的可靠传输(可靠的原因:能保证字节流无差错的传递给接受端,而且在发送端传递数据包给接受端时,接受端也会回传数据包;这样能保证数据正确性和数据顺序);用于大规模数据传输(采用流模式);速度慢花销大(建立连接需要花费时间和资源)。多用于文件,图片,远程登录等传输。
- UDP面向非连接;传输不可靠(传输时不需要和接受端建立连接,发送数据时不管接收端是否能接受);用于小规模数据的传输;速度快;多用于即时通讯,直播等。
- socket套接字,用于描述一个IP地址和一个端口,在双向通讯中(发起方和接受方),应用程序通过“套接字”向网络发送请求和应答。
2.springMVC、springboot、mybatis等框架的优缺点(mybatis和hibernate的区别问到了两次)
3.公司单点登录的实现(多次)?
e.g. 这里主要是考察你是否熟悉单点登录的原理和公司实现单点登录的方案。
4.数据库事务是什么?数据库的隔离级别有几种?
- 事务就是把多件事情当做一件事情来处理。是数据库操作的最小单元,是作为单个逻辑工作单元执行的一系列操作;要么都执行、要么都不执行;事务是一组不可再分割的操作集合。具有原子性、一致性、隔离性、持久性。(粗略回答)
- Serializable(串行化):可避免脏读、不可重复读、幻读的发生
Repeatable read(可重复读):可避免脏读、不可重复读的发生。
Read committed(读已提交):可避免脏读的发生
Read uncommitted(读未提交):最低级别,任何情况下都无法保证。(粗略回答)
5.Servlet生命周期及工作原理是什么?
6.HTTP Request进入到Tomcat中执行,请求处理流程如何?如何找到对应的Application并进行请求处理?
7.一个表名为table的表有三个字段,id,name,sex,id为唯一主键,需要一个delete语句保证执行后,表中name+sex的值唯一(sql题)。
delete from table where id not in (select min(id) from table group by name,sex);
8.excption和error的区别?
9.session和cookie的区别?如何做到会话保持?
10.将一个变量传入一个方法的形参,这个方法会改变变量的值并返回,请问这是值传递还是引用传递?
11.分别说下GET,POST,PUT,DELETE的意思?
- GET: 请求指定的页面信息,并返回实体主体。
- HEAD: 只请求页面的首部。
- POST: 请求服务器接受所指定的文档作为对所标识的URI的新的从属实体。
- PUT: 从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE: 请求服务器删除指定的页面。
- PATCH: 实体中包含一个表,表中说明与该URI所表示的原内容的区别。
12.ddl和dml的区别?
ddl 数据定义语言,用于直接对数据库的对象(database,table)进行操作,例如增删表/列等(drop,alter,create);
dml 数据操纵语言,用于对表的数据进行操作,例如(select,delete,insert,update)
13.一张分数表(table),学号(number),分数(score),查出平均分大于90的人的学号和平均分数?
- select avg(score), number from table group by number having avg(score) > 90
14.数据库内联、左联和右联的区别?
- INNER JOIN(内联):两个表a,b 相连接,取出符合连接条件的数据,数据集C
- LEFT JOIN(左联):先返回左表的所有行,再加上符合连接条件的匹配行,数据集A1+数据集C
- RIGHT JOIN(右联):先返回右表的所有行,再加上符合连接条件的匹配行,数据集B1+数据集C
15.一个线程执行完,怎么通知其他线程执行?一个线程执行完之后如何通知其他线程执行(如何实现线程通信)?
这里列举了三种方式,应该有更好的实现方式(比如condition和blockingqueue)。
- 用volatile关键字修饰一个共享变量,让所有的线程监听这个共享变量,当这个共享变量发生变化的时候,线程感知到其变化并执行相应的业务。
public class test {
static volatile boolean notice = false;
public static void main(String[] args) {
List list = new ArrayList();
Thread a = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
//保证b线程能抢夺到执行机会
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println("线程A向list中添加数据,list中元素个数为:" + list.size());
if (list.size() == 5) {
notice = true;
}
}
});
Thread b = new Thread(() -> {
while (true) {
if (notice) {
System.out.println("线程B收到通知,开始执行自己的业务...");
list.add("b");
System.out.println("list中元素个数为:" + list.size());
break;
}
}
});
//先启动b
b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.start();
}
}
- 使用wait()、notify()
public class test {
public static void main(String[] args) {
//创建锁对象
Object lock = new Object();
List list = new ArrayList<>();
Thread a = new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
list.add("a");
System.out.println("A线程向list中添加数据,list数据个数为:" + list.size());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5) {
try {
//将a线程放入等待池,让出执行机会
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread b = new Thread(() -> {
synchronized (lock) {
System.out.println("线程b获取到执行机会,开始执行任务。。。");
list.add("b");
System.out.println("list数据个数为:" + list.size());
lock.notify();
}
});
//先启动a
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b.start();
}
}
- 使用JUC(java util concurrent)工具类的CountDownLatch,CountDownLatch多用于主线程需要等待子线程执行完毕后再执行的场景。
CountDownLatch构造器的计数值(new CountDownLatch(1)的“1”)实际为闭锁时需要等待执行的线程数,这个值在初始化设置后不能进行更改;
主线程先调用CountDownLatch.await(),使主线程阻塞,直至子线程全部执行完毕。
子线程执行完毕后调用CountDownLatch.countDown()方法来表示自己执行完毕;每调用一次countDown()方法,开锁前需要执行操作的线程数count减1,当count=0时,主线程就会通过阻塞方法await(),恢复执行任务。所以count值也可以被看作是主线程需要等待执行的子线程数。
public class test {
public static void main(String[] args) {
//1表示闭锁时需要等待执行的线程数量,等同于子线程数量
CountDownLatch countDownLatch = new CountDownLatch(1);
List list = new ArrayList<>();
Thread a = new Thread(() -> {
for(int i = 0; i < 10; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("a");
System.out.println("线程A向list添加数据,list数据个数为:" + list.size());
if(list.size() == 5){
countDownLatch.countDown();
}
}
});
Thread b = new Thread(() -> {
while (true){
if(list.size() != 5){
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程b取得执行机会,开始执行任务....");
list.add("b");
System.out.println("list数据个数为:" + list.size());
break;
}
});
//先启动b
b.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.start();
}
}
16.oom如何调优?说下jvm的理解
17.两个对象多对多如何设计表结构
建立中间表,存入两个对象表关联的值(粗略回答)。
18.线程生命周期
- 新建状态
- 就绪状态
线程调用start()方法,线程就位于可运行池中,等待获取cpu执行权。 - 运行状态
就绪状态的线程获取到执行机会,调用线程对象的run()方法。 - 阻塞状态
线程因为某些原因暂时放弃了对cpu的执行权,暂时停止运行。阻塞状态分三种:
等待阻塞:调用wait()方法后,线程释放其所持有的锁,被jvm放入等待池,只有调用notify()和notifyAll()方法时线程才能再次进入就绪状态,等待被调用。
同步阻塞:当其他线程持有锁时,jvm会把当前线程设置为阻塞状态,只有线程获取到“同步锁”时,线程才能转入就绪状态。
其他阻塞:调用sleep()或join()或者发出I/O请求,jvm会将线程设置为阻塞状态,只有当sleep()超时,join()等待其他线程终止或超时,I/O处理完毕,线程才能转入就绪状态。 - 死亡状态
线程执行完毕或因为异常退出了run()方法,线程生命周期就结束了。
19.接触过跨域问题吗?分布式系统如何解决跨域问题?
- 跨域问题是指浏览器对js的同源限制,比如在a.com不能调用b.com的资源。解决方案:jsonp,利用iframe,script,img不受同源限制来实现(粗略回答)。