Skip to content

Commit

Permalink
顺丰科技面试
Browse files Browse the repository at this point in the history
  • Loading branch information
itwanger committed Jun 12, 2024
1 parent ce82f0b commit b0f7f9d
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 96 deletions.
30 changes: 19 additions & 11 deletions docs/sidebar/csnotes/java-thread.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
- 实现 Callable 接口;
- 继承 Thread 类。

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动从而执行的
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以理解为任务是通过线程驱动执行的

### 实现 Runnable 接口

需要实现接口中的 run() 方法。
需要实现接口中的 `run()` 方法。

```java
public class MyRunnable implements Runnable {
Expand All @@ -21,7 +21,7 @@ public class MyRunnable implements Runnable {
}
```

使用 Runnable 实例再创建一个 Thread 实例,然后调用 Thread 实例的 start() 方法来启动线程。
使用 Runnable 实例再创建一个 Thread 实例,然后调用 Thread 实例的 `start()` 方法来启动线程。

```java
public static void main(String[] args) {
Expand All @@ -43,6 +43,8 @@ public class MyCallable implements Callable<Integer> {
}
```

来看测试类:

```java
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
Expand All @@ -55,9 +57,11 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc

### 继承 Thread 类

同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
同样也是需要实现 `run()` 方法,因为 Thread 类也实现了 Runable 接口。

![二哥的 Java 进阶之路:Thread](https://cdn.tobebetterjavaer.com/stutymore/java-thread-20240611113746.png)

当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
当调用 `start()` 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 `run()` 方法。

```java
public class MyThread extends Thread {
Expand All @@ -67,6 +71,8 @@ public class MyThread extends Thread {
}
```

启动线程类:

```java
public static void main(String[] args) {
MyThread mt = new MyThread();
Expand All @@ -79,13 +85,15 @@ public static void main(String[] args) {
实现接口会更好一些,因为:

- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
- 类可能只需要可执行就行,继承整个 Thread 类开销过大。

推荐阅读:[室友打了一把王者就学会了 Java 多线程](https://javabetter.cn/thread/wangzhe-thread.html)

## 二、基础线程机制

### Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
Executor 用来管理多个异步任务的执行,这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executor:

Expand All @@ -109,9 +117,9 @@ public static void main(String[] args) {

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。
`main()` 属于非守护线程。

在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。
在线程启动之前使用 `setDaemon()` 方法可以将一个线程设置为守护线程。

```java
public static void main(String[] args) {
Expand All @@ -122,9 +130,9 @@ public static void main(String[] args) {

### sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。
`Thread.sleep(millisec)` 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。
`sleep()` 可能会抛出 InterruptedException,因为异常不能跨线程传播回 `main()` 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

```java
public void run() {
Expand Down
25 changes: 20 additions & 5 deletions docs/sidebar/sanfene/javase.md
Original file line number Diff line number Diff line change
Expand Up @@ -2080,15 +2080,30 @@ System.out.println("fastTime: " + field.getLong(obj));

#### 反射有哪些应用场景?

一般我们平时都是在在写业务代码,很少会接触到直接使用反射机制的场景
①、Spring 框架就大量使用了反射来动态加载和管理 Bean

但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
```java
Class<?> clazz = Class.forName("com.example.MyClass");
Object instance = clazz.newInstance();
```

像 Spring 里的很多 **注解** ,它真正的功能实现就是利用反射
②、Java 的动态代理(Dynamic Proxy)机制就使用了反射来创建代理类。代理类可以在运行时动态处理方法调用,这在实现 AOP 和拦截器时非常有用

就像为什么我们使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
```java
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class<?>[] { MyInterface.class },
handler
);
```

这些都是因为我们可以基于反射操作类,然后获取到类/属性/方法/方法的参数上的注解,注解这里就有两个作用,一是标记,我们对注解标记的类/属性/方法进行对应的处理;二是注解本身有一些信息,可以参与到处理的逻辑中。
③、JUnit 和 TestNG 等测试框架使用反射机制来发现和执行测试方法。反射允许框架扫描类,查找带有特定注解(如 `@Test`)的方法,并在运行时调用它们。

```java
Method testMethod = testClass.getMethod("testSomething");
testMethod.invoke(testInstance);
```

#### 反射的原理是什么?

Expand Down
125 changes: 69 additions & 56 deletions docs/sidebar/sanfene/javathread.md
Original file line number Diff line number Diff line change
Expand Up @@ -2257,94 +2257,107 @@ GitHub 上标星 10000+ 的开源知识库《[二哥的 Java 进阶之路](https

### 39.CountDownLatch(倒计数器)了解吗?

CountDownLatch,倒计数器,有两个常见的应用场景[18]
推荐阅读:[Java并发编程通信工具类 Semaphore、Exchanger、CountDownLatch、CyclicBarrier、Phaser等一网打尽](https://javabetter.cn/thread/CountDownLatch.html)

**场景 1:协调子线程结束动作:等待所有子线程运行结束**
CountDownLatch 是 JUC 包中的一个同步工具类,用于协调多个线程之间的同步。它允许一个或多个线程等待,直到其他线程中执行的一组操作完成。它通过一个计数器来实现,该计数器由线程递减,直到到达零。

CountDownLatch 允许一个或多个线程等待其他线程完成操作。
- 初始化:创建 CountDownLatch 对象时,指定计数器的初始值。
- 等待(await):一个或多个线程调用 await 方法,进入等待状态,直到计数器的值变为零。
- 倒计数(countDown):其他线程在完成各自任务后调用 countDown 方法,将计数器的值减一。当计数器的值减到零时,所有在 await 上等待的线程会被唤醒,继续执行。

例如,我们很多人喜欢玩的王者荣耀,开黑的时候,得等所有人都上线之后,才能开打
当等待多个线程完成各自的启动任务后再启动主线程的任务,就可以使用 CountDownLatch,以王者荣耀为例

![王者荣耀等待玩家确认-来源参考[18]](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/javathread-50.jpeg)
![秦二爷:王者荣耀等待玩家确认](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/javathread-50.jpeg)

CountDownLatch 模仿这个场景(参考[18]):

创建大乔、兰陵王、安其拉、哪吒和铠等五个玩家,主线程必须在他们都完成确认后,才可以继续运行。

在这段代码中,`new CountDownLatch(5)`用户创建初始的 latch 数量,各玩家通过`countDownLatch.countDown()`完成状态确认,主线程通过`countDownLatch.await()`等待。
创建五个线程,分别代表大乔、兰陵王、安其拉、哪吒和铠等五个玩家。每个玩家都调用了`countDown()`方法,表示已经就位。主线程调用`await()`方法,等待所有玩家就位。

```java
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);

Thread 大乔 = new Thread(countDownLatch::countDown);
Thread 兰陵王 = new Thread(countDownLatch::countDown);
Thread 安其拉 = new Thread(countDownLatch::countDown);
Thread 哪吒 = new Thread(countDownLatch::countDown);
Thread= new Thread(() -> {
try {
// 稍等,上个卫生间,马上到...
Thread.sleep(1500);
countDownLatch.countDown();
} catch (InterruptedException ignored) {}
Thread daqiao = new Thread(() -> {
System.out.println("大乔已就位!");
countDownLatch.countDown();
});
Thread lanlingwang = new Thread(() -> {
System.out.println("兰陵王已就位!");
countDownLatch.countDown();
});
Thread anqila = new Thread(() -> {
System.out.println("安其拉已就位!");
countDownLatch.countDown();
});
Thread nezha = new Thread(() -> {
System.out.println("哪吒已就位!");
countDownLatch.countDown();
});
Thread kai = new Thread(() -> {
System.out.println("铠已就位!");
countDownLatch.countDown();
});

daqiao.start();
lanlingwang.start();
anqila.start();
nezha.start();
kai.start();

大乔.start();
兰陵王.start();
安其拉.start();
哪吒.start();
.start();
countDownLatch.await();
System.out.println("所有玩家已经就位");
System.out.println("全员就位,开始游戏");
}
```

**场景 2. 协调子线程开始动作:统一各线程动作开始的时机**

王者游戏中也有类似的场景,游戏开始时,各玩家的初始状态必须一致。不能有的玩家都出完装了,有的才降生。
再比如说,可以使用 CountDownLatch 确保某些操作在一组操作完成之后才开始执行。

所以大家得一块出生,在
![秦二爷:王者荣耀大家一起出生](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/javathread-51.jpeg)

![王者荣耀-来源参考[18]](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/javathread-51.jpeg)

在这个场景中,仍然用五个线程代表大乔、兰陵王、安其拉、哪吒和铠等五个玩家。需要注意的是,各玩家虽然都调用了`start()`线程,但是它们在运行时都在等待`countDownLatch`的信号,在信号未收到前,它们不会往下执行。
五个玩家在等待倒计时结束后,一起出击。

```java
public static void main(String[] args) throws InterruptedException {
private static void waitToFight(CountDownLatch countDownLatch, String name) {
try {
countDownLatch.await(); // 在此等待信号再继续
System.out.println(name + " 收到,发起进攻!");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(name + " 被中断");
}
}

public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);

Thread 大乔 = new Thread(() -> waitToFight(countDownLatch));
Thread 兰陵王 = new Thread(() -> waitToFight(countDownLatch));
Thread 安其拉 = new Thread(() -> waitToFight(countDownLatch));
Thread 哪吒 = new Thread(() -> waitToFight(countDownLatch));
Thread = new Thread(() -> waitToFight(countDownLatch));
Thread daqiao = new Thread(() -> waitToFight(countDownLatch, "大乔"), "Thread-大乔");
Thread lanlingwang = new Thread(() -> waitToFight(countDownLatch, "兰陵王"), "Thread-兰陵王");
Thread anqila = new Thread(() -> waitToFight(countDownLatch, "安琪拉"), "Thread-安琪拉");
Thread nezha = new Thread(() -> waitToFight(countDownLatch, "哪吒"), "Thread-哪吒");
Thread kai = new Thread(() -> waitToFight(countDownLatch, ""), "Thread-凯");

大乔.start();
兰陵王.start();
安其拉.start();
哪吒.start();
.start();
Thread.sleep(1000);
countDownLatch.countDown();
System.out.println("敌方还有5秒达到战场,全军出击!");
}
daqiao.start();
lanlingwang.start();
anqila.start();
nezha.start();
kai.start();

private static void waitToFight(CountDownLatch countDownLatch) {
try {
countDownLatch.await(); // 在此等待信号再继续
System.out.println("收到,发起进攻!");
Thread.sleep(5000); // 模拟准备时间
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
System.out.println("主线程被中断");
}

System.out.println("敌军还有 5 秒到达战场,全军出击!");
countDownLatch.countDown(); // 发出信号
}
```

CountDownLatch 的**核心方法**也不多:

- `await()`:等待 latch 降为 0;
- `boolean await(long timeout, TimeUnit unit)`:等待 latch 降为 0,但是可以设置超时时间。比如有玩家超时未确认,那就重新匹配,总不能为了某个玩家等到天荒地老。
- `countDown()`:latch 数量减 1;
- `getCount()`:获取当前的 latch 数量。
- `CountDownLatch(int count)`:创建一个带有给定计数器的 CountDownLatch。
- `void await()`:阻塞当前线程,直到计数器为零。
- `void countDown()`:递减计数器的值,如果计数器值变为零,则释放所有等待的线程。

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的顺丰科技同学 1 面试原题:并发编程CountDownLatch 和消息队列
### 40.CyclicBarrier(同步屏障)了解吗?

Expand Down
25 changes: 3 additions & 22 deletions docs/sidebar/sanfene/jvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -1000,41 +1000,22 @@ Parallel Scavenge 的特点是什么?

JVM 在做垃圾回收之前,需要先搞清楚什么是垃圾,什么不是垃圾,那么就需要一种垃圾判断算法,通常有引用计数算法、可达性分析算法。

- 引用计数算法是通过在对象头中分配一个空间来保存该对象被引用的次数。
- 可达性分析算法的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,然后向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 之间没有任何引用相连时,即从 GC Roots 到该对象节点不可达,则证明该对象是需要垃圾收集的。

![](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227104036.png)
![二哥的 Java 进阶之路:可达性分析](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227104036.png)

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是进行垃圾回收,如何高效地进行垃圾回收呢?

①、标记清除算法,分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收的标记出来,然后把这些垃圾拎出来清理掉。

![图片来源于小牛肉](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227125304.png)

②、复制算法,在标记清除算法上演化而来的,用于解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

![图片来源于小牛肉](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227125751.png)

标记整理算法,标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。

![图片来源于小牛肉](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227130011.png)

分代收集算法,严格来说并不是一种思想或理论,而是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记清理或者标记整理算法来进行回收。

![](https://cdn.tobebetterjavaer.com/stutymore/gc-20231227131241.png)
可以采用标记清除算法、复制算法、标记整理算法、分代收集算法等。

JVM 提供了多种垃圾回收器,不同的垃圾回收器适用于不同的场景和需求,包括 CMS GC、G1 GC、ZGC 等。

CMS 是第一个关注 GC 停顿时间(STW 的时间)的垃圾收集器,JDK 1.5 时引入,JDK9 被标记弃用,JDK14 被移除。

G1(Garbage-First Garbage Collector)在 JDK 1.7 时引入,在 JDK 9 时取代 CMS 成为了默认的垃圾收集器。

![图片来源于有梦想的肥宅](https://cdn.tobebetterjavaer.com/stutymore/gc-collector-20231228213824.png)
![有梦想的肥宅:G1 收集器](https://cdn.tobebetterjavaer.com/stutymore/gc-collector-20231228213824.png)

ZGC 是 JDK11 推出的一款低延迟垃圾收集器,适用于大内存低延迟服务的内存管理和回收,SPEC jbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。

推荐阅读:[深入理解 JVM 的垃圾收集器:CMS、G1、ZGC](https://javabetter.cn/jvm/gc-collector.html)

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的华为 OD 技术一面遇到的一道原题。
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的美团面经同学 2 Java 后端技术一面面试原题:了解 GC 吗?不可达判断知道吗?
Expand Down
6 changes: 6 additions & 0 deletions docs/sidebar/sanfene/mysql.md
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,12 @@ MySQL 的 InnoDB 存储引擎默认使用 B+ 树来作为索引的数据结构

![三分恶面渣逆袭:索引加快查询远离](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/sidebar/sanfene/mysql-6b9c9901-9bf3-46ed-a5c4-c1b781965c1e.jpg)

可通过 `create index` 创建索引,比如:

```sql
create index idx_name on students(name);
```

> 1. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的腾讯面经同学 23 QQ 后台技术一面面试原题:MySQL 索引,为什么用 B+树
> 2. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小米面经同学 E 第二个部门 Java 后端技术一面面试原题:为什么需要索引
> 3. [Java 面试指南(付费)](https://javabetter.cn/zhishixingqiu/mianshi.html)收录的小公司面经同学 5 Java 后端面试原题:数据库索引讲一下,然后为什么会加快查询速度,我讲到了 B+树,然后问了 B 数与 B+树区别
Expand Down
Loading

0 comments on commit b0f7f9d

Please sign in to comment.