目录
- 前言
- 一、线程几种状态以及什么时候产生调用sleep()后线程处于什么状态?
- 二、loadClass和forName的区别
- 三、字符流和字节流的区别
- 四、观察者模式
- 五、模板模式
- 六、协程是什么?
- 七、RPC框架和普通http有什么区别和优势? 基于Tcp封装还是http封装的?
- 八、RPC是长连接吗?如果要传输一个特别大的文件底层还是基于流吗?NIO是一个什么IO模型?
- 九、GitHub的watch、star和fork的作用
- 十、Exception和Error的区别,OOM是Error还是Exception?
- 十一、拥塞控制算法有哪些?
- 十二、流量控制协议
- 十三、Ping命令做了什么?基于哪一层?
- 十四、哪些类实现了乐观锁?分析他们的源码
- 十五、利用快排的找数组中的第K大(小),要求快排使用非递归方式
- 十六、手写创建索引的MySQL代码
- 十七、让多个线程有顺序得执行一个对象中的多个方法(LeetCode-1114)
- 十八、linux常见指令
- 十九、死锁检测算法?
- 后记
前言
“实战面经”是本专栏的第二个部分,本篇博文是第八篇博文,如有需要,可:
一、线程几种状态以及什么时候产生调用sleep()后线程处于什么状态?
-
线程有:
创建
、运行
、就绪
、等待
、超时等待
、阻塞
和终止
7种状态 -
调用Thread.sleep()后线程会进入阻塞状态。
二、loadClass和forName的区别
-
Class.forName
得到的class是已经初始化完成的; -
ClassLoader.loadClass
得到的class是还没有链接的; -
应用场景:
1) 有的类有静态代码块(如mysql-connector),因此需要使用Class.forName;
2) Spring中由于IoC大量使用LazyLoad技术,即延时加载,在bean真正被调用时才完成初始化。这里用到的是ClassLoader.loadClass。
三、字符流和字节流的区别
-
字节流在操作时不会用到缓冲区(内存),是直接对文件本身进行操作的。而字符流在操作时使用了缓冲区,再通过缓冲区操作文件。
-
字节流对应
InputStream
、OutputStream
,字符流对应Reader
、Writer
四、观察者模式
观察者模式即观察者注册到被观察者上,被观察者发现有新的消息时,推送给所有观察者。
- 观察者接口
public interface Observer {
void update(String message);
}
- 被观察者(可观察的)
public interface Observable {
void register(Observer o);
void unregister(Observer o);
void notifyRegistered();
}
- 服务器(被观察者实现类)
public class Server implements Observable{
private static final LinkedList<Observer> USERS = new LinkedList<>();
@Override
public void register(Observer o) {
USERS.add(o);
}
@Override
public void unregister(Observer o) {
USERS.remove(o);
}
@Override
public void notifyRegistered() {
USERS.forEach(user -> user.update("New Message Recieved."));
}
}
- 用户(观察者实现类)
public class User implements Observer{
private String userName;
public User(String userName){
this.userName = userName;
}
@Override
public void update(String message) {
System.out.println(userName + ": " + message);
}
}
- 测试类
public class Main {
public static void main(String[] args) {
Server server = new Server();
User user1 = new User("User1");
User user2 = new User("User2");
server.register(user1);
server.register(user2);
server.notifyRegistered();
}
}
五、模板模式
子类继承父类套用其方法模板,关键在于父类指定了所有模板方法的执行顺序
- 抽象模板类
public abstract class Template {
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
protected final void execute(){
step1();
step2();
step3();
}
}
- 套用模板的子类
public class TemplateImpl extends Template{
@Override
protected void step1() {
System.out.println("Executing step 1.");
}
@Override
protected void step2() {
System.out.println("Executing step 2.");
}
@Override
protected void step3() {
System.out.println("Executing step 3.");
}
}
- 测试类
public class Main {
public static void main(String[] args) {
TemplateImpl templateImpl = new TemplateImpl();
templateImpl.execute();
}
}
六、协程是什么?
协程用来实现线程内的并发,即在遇到IO的时候通过yield中断并保存上下文环境,并让出CPU去执行其他的子程序,然后在CPU再次切换回来的时候,如果IO结束了就继续执行下面的程序。
优点: 避免了线程切换对CPU资源的耗费,在IO阻塞时充分利用CPU,提高了CPU的利用率。
七、RPC框架和普通http有什么区别和优势? 基于Tcp封装还是http封装的?
-
RPC要比http更快,虽然底层都是socket,但是http协议的信息往往比较臃肿。
-
RPC实现较为复杂,http相对比较简单。
-
RPC要求服务的提供方和消费方使用同样的RPC框架,Http方式更灵活,没有规定API和语言。
-
RPC可以基于TCP也可以基于HTTP,主流的RPC框架基于TCP。
RPC的主要应用如天气预报查询等。
八、RPC是长连接吗?如果要传输一个特别大的文件底层还是基于流吗?NIO是一个什么IO模型?
-
RPC有长连接的实现也有短连接的实现,长连接的框架有
Dubbo
、Netty
,短连接的框架有RESTful
、SOAP
。 -
传输大文件时底层基于缓冲区。
-
NIO是基于缓冲区的同步非阻塞IO,见《实战面经(一)》第十一题
九、GitHub的watch、star和fork的作用
-
watch
类似于关注,该仓库如果有更新,watch
的人会受到邮件。 -
star
类似于喜欢或者点赞,可以在自己的stars
里找到“点赞”过的仓库 -
fork
相当于复制仓库,如果我们发现源仓库的代码有Bug,就可以在自己的拷贝上修改,然后向源仓库发起Pull Request
,如果源仓库的作者同意,就会被merge
。
十、Exception和Error的区别,OOM是Error还是Exception?
-
Exception和Error都继承了Throwable
Exception: 程序可以处理的异常,捕获后可能恢复
Error: 程序无法处理的系统错误,编译器不做检查 -
OOM属于Error,常见的Error有:
1) NoClassDefFoundError: 找不到class定义的异常
2) StackOverflowError: 栈溢出异常
3) OutOfMemoryError: 内存溢出异常
十一、拥塞控制算法有哪些?
-
慢启动: 初始窗口大小
cwnd = 1
,每收到一个ACK就cwnd++
,每过一个RTT(往返时延)就cwnd *= 2
,到达阈值后进入拥塞避免阶段。 -
拥塞避免: 每收到一个ACK, c w n d = c w n d + 1 c w n d cwnd = cwnd + \frac{1}{cwnd} cwnd=cwnd+cwnd1,每过一个RTT就
cwnd++
。 -
拥塞控制: 分两种情况
1) RTO(重传计时器超时): 阈值降为cwnd的一半,cwnd置为1,重新开始慢启动。
2) 收到三个重复的ACK: 阈值降为cwnd的一半,进入快速恢复。 -
快速恢复: cwnd = 阈值 + 3 * MSS(最大报文段长度),重传重复ACK对应的包,如果再收到重复ACK,cwnd++。如果收到新的ACK,cwnd = 阈值,进入拥塞避免。
十二、流量控制协议
-
停等协议: 发送方发一帧,接收方收到后确认,然后发送方再发下一帧。
-
滑动窗口协议: 发送方连续发送多个帧,发送方收到一个帧后确认,发送方如果对连续的帧都收到确认,就向后滑动窗口。
十三、Ping命令做了什么?基于哪一层?
Ping
命令使用ICMP(Internet Control Message Protocol)
协议,向目标主机发送ICMP
报文,通过目标主机的响应来判断其是否可达。ICMP
属于网络层协议。
十四、哪些类实现了乐观锁?分析他们的源码
ConcurrentHashMap
的putVal
方法中调用了casTabAt
方法,当向表中插入键值的时候,如果发现对应的桶是空的,就调用casTabAt
方法,而它又调用了Unsafe
类的compareAndSwapObject
方法将新的Node
添到桶中。
十五、利用快排的找数组中的第K大(小),要求快排使用非递归方式
- 总控函数
public static int findKthMax(int[] nums, int k, int left, int right) {
int index, target = nums.length - k;
while ((index = partition(nums, left, right)) != target){
if (index > target)
right = index - 1;
else
left = index + 1;
}
return nums[index];
}
- 快排划分函数
private static int partition(int[] nums, int left, int right) {
int key = nums[left];
while (left < right) {
while (left < right && key < nums[right])
right--;
if (left < right)
nums[left++] = nums[right];
while (left < right && key > nums[left])
left++;
if (left < right)
nums[right--] = nums[left];
}
nums[left] = key;
return left;
}
注意: 快排划分函数中间有两个if判断
十六、手写创建索引的MySQL代码
- 普通索引:
CREATE INDEX idx_name ON table_name (columns)
- 唯一索引:
CREATE UNIQUE INDEX idx_name ON table_name (columns)
- 添加主键索引:
ALTER TABLE table_name ADD PRIMARY KEY (columns)
十七、让多个线程有顺序得执行一个对象中的多个方法(LeetCode-1114)
wait
和notifyAll
方法
class Foo {
private int counter = 0;
public void first(Runnable printFirst) throws InterruptedException {
synchronized (this) {
printFirst.run();
counter++;
this.notifyAll();
}
}
public void second(Runnable printSecond) throws InterruptedException {
synchronized (this) {
while (counter < 1)
this.wait();
printSecond.run();
counter++;
this.notifyAll();
}
}
public void third(Runnable printThird) throws InterruptedException {
synchronized (this) {
while (counter < 2)
this.wait();
printThird.run();
counter++;
}
}
}
注: 用
notify
不用notifyAll
会发生死锁,比如获取锁的顺序是:3 -> 2 -> 1,此时2和3wait
,1执行完调用notify
,如果唤醒了2皆大欢喜;如果唤醒了3,发现counter < 2,2和3继续wait
,没有线程可以继续唤醒它们。
CountDownLatch
方法
class Foo {
private CountDownLatch latch1;
private CountDownLatch latch2;
public Foo() {
latch1 = new CountDownLatch(1);
latch2 = new CountDownLatch(1);
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
latch1.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
latch1.await();
printSecond.run();
latch2.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
latch2.await();
printThird.run();
}
}
注:
CountDownLatch
的await
方法作用是等待计数为0时再执行后面的代码。
十八、linux常见指令
- 输出文件”file.txt”的第10行
1) sed命令:sed -n '10p' file.txt
2) awk命令:awk 'NR==10' file.txt
3) head和tail组合命令:tail -n+10 file.txt | head -1
其中tail -n+10
显示从第10行开始到文件结束的所有行,head - 1
显示第一行
-
输出文件”file.txt”中符合(xxx) xxx-xxxx或xxx-xxx-xxxx格式的电话号码
grep命令: grep -E ‘(^([0-9]{3}) |^[0-9]{3}-)[0-9]{3}-[0-9]{4}$’ file.txt -
将文件内容转置(按列输出)
awk命令:
awk '{
for (i=1; i <= NF; i++) {
if (NR == 1) {
res[i] = $i
}
else {
res[i] = res[i]" "$i
}
}
} END {
for(j=1; j <= NF; j++) {
print res[j]
}
}' file.txt
其中NF是列号(1 <= NF <= n)
,NR是行号(1 <= NR <= m)
,$i
表示当前行第i列 的内容。
十九、死锁检测算法?
资源分配图法
图中P1
和P2
表示进程,r1
和r2
表示资源,一个圆圈代表这个资源的一个实例。P
到r
的边为请求边,r
到P
的边为分配边。
类似于拓扑排序法,先取一个请求可以得到满足的进程Pi
,消掉它的所有请求边和分配边,以此类推,直到最后图中没有任何边,则表明不会死锁,否则就可能死锁。
后记
本篇面经题量不算法,但涉及的需要写代码的部分占比较大。