• 正文
  • 相关推荐
申请入驻 产业图谱

影石今年校招薪资真的杀疯了

10/30 13:17
304
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

面试刷题网站:xiaolincoding.com

大家好,我是小林。

影石今年校招薪资是真的杀疯了!

有同学爆料说,拿到了影石开发岗的 SP offer,给的?32k×15,这算下来年薪差不多 50 万了。估计去年入职的兄弟看到得哭,这薪资直接把他们狠狠倒挂了。

影石 Insta360 本来是专门做全景运动相机的,不过今年也出了无人机, 这明摆着是要跟大疆掰掰手腕啊。再加上影石今年薪资给得这么意外,基本能跟大疆打平,说不定就是想跟大疆抢应届生人才。

咱们干开发的,平时大多会优先选互联网公司,很少去硬件类公司做开发。主要还是因为互联网公司薪资普遍高,校招起步基本都 35 万以上年薪。

但现在看影石这种做硬件的公司,也能开这么高的薪资,甚至比不少互联网公司还多,这其实是好事。以后想拿高薪开发岗,不用只盯着互联网了,多了条路可选。

不过影石是近几年才发展快起来的,可能很多同学都不太了解。论公司名气,确实没互联网大厂那么响。

但要是互联网大厂给你开 35 万的白菜 offer,影石给你 32k×15( 48 万),你会怎么选择?要是我的话,大概率会选影石,没啥别的原因,就是钱给得多啊。

毕竟刚毕业出来,起薪差这 13 万可不是小数目,不管是攒钱还是后面跳槽谈薪,起点都不一样。而且影石这薪资都快追上大厂 SP 了,拿着白菜价的钱去卷大厂,还不如选个给钱更实在的,你说对吧?

而且在影石干个几年,之后再跳回互联网公司也没问题。关键是第一份工作起薪高,后面跳槽收益就很明显。干过社招跳槽的同学都知道,下家公司跟你谈薪,基本都是按你上一家的薪资来涨,一般能涨个 20%-30%。

那咱再说说,影石 Insta360 的面试难度到底咋样?

这次给大家看的是影石校招面经,整体主要考 Java 并发、JVM、Redis 这些知识点,最后也少不了算法题。果然啊,薪资开得高的公司,面试都逃不掉手撕算法这一环。

影石Insta360(Java 一面)

1. synchronized锁升级介绍一下?

具体的锁升级的过程是:无锁->偏向锁->轻量级锁->重量级锁

无锁:这是没有开启偏向锁的时候的状态,在JDK1.6之后偏向锁的默认开启的,但是有一个偏向延迟,需要在JVM启动之后的多少秒之后才能开启,这个可以通过JVM参数进行设置,同时是否开启偏向锁也可以通过JVM参数设置。

偏向锁:这个是在偏向锁开启之后的锁的状态,如果还没有一个线程拿到这个锁的话,这个状态叫做匿名偏向,当一个线程拿到偏向锁的时候,下次想要竞争锁只需要拿线程ID跟MarkWord当中存储的线程ID进行比较,如果线程ID相同则直接获取锁(相当于锁偏向于这个线程),不需要进行CAS操作和将线程挂起的操作。

轻量级锁:在这个状态下线程主要是通过CAS操作实现的。将对象的MarkWord存储到线程的虚拟机栈上,然后通过CAS将对象的MarkWord的内容设置为指向Displaced Mark Word的指针,如果设置成功则获取锁。在线程出临界区的时候,也需要使用CAS,如果使用CAS替换成功则同步成功,如果失败表示有其他线程在获取锁,那么就需要在释放锁之后将被挂起的线程唤醒。

重量级锁:当有两个以上的线程获取锁的时候轻量级锁就会升级为重量级锁,因为CAS如果没有成功的话始终都在自旋,进行while循环操作,这是非常消耗CPU的,但是在升级为重量级锁之后,线程会被操作系统调度然后挂起,这可以节约CPU资源。

了解完 4 种锁状态之后,我们就可以整体的来看一下锁升级的过程了。

线程A进入 synchronized 开始抢锁,JVM 会判断当前是否是偏向锁的状态,如果是就会根据 Mark Word 中存储的线程 ID 来判断,当前线程A是否就是持有偏向锁的线程。如果是,则忽略 check,线程A直接执行临界区内的代码。

但如果 Mark Word 里的线程不是线程 A,就会通过自旋尝试获取锁,如果获取到了,就将 Mark Word 中的线程 ID 改为自己的;如果竞争失败,就会立马撤销偏向锁,膨胀为轻量级锁。

后续的竞争线程都会通过自旋来尝试获取锁,如果自旋成功那么锁的状态仍然是轻量级锁。然而如果竞争失败,锁会膨胀为重量级锁,后续等待的竞争的线程都会被阻塞。

2. synachronized锁一个.class和锁一个对象的区别是什么?什么场景下会用到.class?

首先,锁一个普通对象(比如this或者某个实例对象)时,这个锁是 “对象级别的”。意思是,只有当多个个线程竞争同一个对象实例的锁时,才会产生互斥。

public?class?ObjectLockDemo?{
? ??// 实例变量(每个对象单独拥有)
? ??privateint?instanceCount =?0;

? ??// 锁当前对象(this)
? ??public?synchronized?void?addInstance()?{
? ? ? ? instanceCount++;
? ? ? ? System.out.println(Thread.currentThread().getName() +?":实例变量值="?+ instanceCount);
? ? }

? ??public?static?void?main(String[] args)?{
? ? ? ??// 创建两个不同的对象
? ? ? ? ObjectLockDemo obj1 =?new?ObjectLockDemo();
? ? ? ? ObjectLockDemo obj2 =?new?ObjectLockDemo();

? ? ? ??// 线程1操作obj1
? ? ? ??new?Thread(() -> {
? ? ? ? ? ??for?(int?i =?0; i <?3; i++) {
? ? ? ? ? ? ? ? obj1.addInstance();
? ? ? ? ? ? }
? ? ? ? },?"线程A").start();

? ? ? ??// 线程2操作obj2
? ? ? ??new?Thread(() -> {
? ? ? ? ? ??for?(int?i =?0; i <?3; i++) {
? ? ? ? ? ? ? ? obj2.addInstance();
? ? ? ? ? ? }
? ? ? ? },?"线程B").start();
? ? }
}

运行结果(可能的情况):线程 A 和线程 B 可以同时执行,因为它们锁的是不同的对象(obj1obj2),各自的instanceCount独立增长。这说明对象锁只对同一实例的线程互斥,不同实例互不影响

而锁Class对象(比如synchronized(XXX.class))时,这个锁是 “类级别的”。因为每个类在 JVM 中只有一个Class对象(全局唯一),所以不管有多少个该类的实例,所有线程竞争这个Class锁时都会互斥。也就是说,哪怕是不同的对象实例,只要它们属于同一个类,争夺这个类的锁时也会排队。这种锁保护的是 “类的静态变量”,因为静态变量是类共享的,不属于任何实例,需要一个全局唯一的锁来保证线程安全。

public?class?ClassLockDemo?{
? ??// 静态变量(全类共享)
? ??privatestaticint?staticCount =?0;

? ??// 锁Class对象(ClassLockDemo.class)
? ??public?void?addStatic()?{
? ? ? ??synchronized?(ClassLockDemo.class)?{?// 类级锁
? ? ? ? ? ? staticCount++;
? ? ? ? ? ? System.out.println(Thread.currentThread().getName() +?":静态变量值="?+ staticCount);
? ? ? ? }
? ? }

? ??public?static?void?main(String[] args)?{
? ? ? ??// 创建两个不同的对象
? ? ? ? ClassLockDemo obj1 =?new?ClassLockDemo();
? ? ? ? ClassLockDemo obj2 =?new?ClassLockDemo();

? ? ? ??// 线程1操作obj1
? ? ? ??new?Thread(() -> {
? ? ? ? ? ??for?(int?i =?0; i <?3; i++) {
? ? ? ? ? ? ? ? obj1.addStatic();
? ? ? ? ? ? }
? ? ? ? },?"线程C").start();

? ? ? ??// 线程2操作obj2
? ? ? ??new?Thread(() -> {
? ? ? ? ? ??for?(int?i =?0; i <?3; i++) {
? ? ? ? ? ? ? ? obj2.addStatic();
? ? ? ? ? ? }
? ? ? ? },?"线程D").start();
? ? }
}

运行结果(必然的情况):线程 C 和线程 D 会排队执行,因为它们锁的是同一个ClassLockDemo.class对象(全局唯一),staticCount会从 1 依次增长到 6。这说明类锁对所有实例的线程都互斥,因为锁的是类的全局唯一资源

至于什么场景用Class锁,最典型的就是操作静态变量的时候。比如单例模式的初始化:

public?class?Singleton?{
? ??privatestatic?Singleton instance;

? ??// 双重检查锁中用Class锁保证全局唯一实例
? ??public?static?Singleton?getInstance()?{
? ? ? ??if?(instance ==?null) {
? ? ? ? ? ??synchronized?(Singleton.class)?{?// 类锁:防止多线程同时进入创建实例
? ? ? ? ? ? ? ??if?(instance ==?null) {
? ? ? ? ? ? ? ? ? ? instance =?new?Singleton();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??return?instance;
? ? }
}

这里用Singleton.class作为锁,确保无论多少个线程调用getInstance(),同一时间只有一个线程能创建实例,保证单例的唯一性。

3. 单例了解吗?说说懒汉式单例?

单例模式是一种确保一个类在整个程序中只有一个实例,并提供全局访问点的设计模式。

懒汉式单例是其中一种实现,核心特点是 “用到的时候才创建实例”,而不是程序启动时就初始化,这样可以节省资源(如果这个实例一直没被用到,就不会占用内存)。

最基础的懒汉式单例实现很简单:把构造方法私有化(防止外部new对象),在类内部定义一个静态的自身实例,然后提供一个静态方法获取这个实例,只有当第一次调用这个方法时,才会创建实例,之后再调用就直接返回已创建的实例。比如:

public?class?LazySingleton?{
? ??// 私有静态实例,一开始为null
? ??privatestatic?LazySingleton instance;

? ??// 私有构造方法,阻止外部创建实例
? ??private?LazySingleton()?{}

? ??// 静态方法,获取实例
? ??public?static?LazySingleton?getInstance()?{
? ? ? ??if?(instance ==?null) {?// 第一次调用时创建实例
? ? ? ? ? ? instance =?new?LazySingleton();
? ? ? ? }
? ? ? ??return?instance;
? ? }
}

但这个基础版本有个严重问题:多线程环境下不安全。比如两个线程同时调用getInstance(),都判断instance == null,就会各自创建一个实例,导致单例被破坏(出现多个实例)。

所以实际用的懒汉式需要解决线程安全问题,最常见的改进是加锁。比如在getInstance()方法上用synchronized修饰:

public?static?synchronized?LazySingleton?getInstance()?{
? ??if?(instance ==?null) {
? ? ? ? instance =?new?LazySingleton();
? ? }
? ??return?instance;
}

加锁后,同一时间只有一个线程能进入方法,避免了并发创建的问题。但缺点是每次调用getInstance()都要加锁解锁,哪怕实例已经创建好了,会有性能损耗(虽然影响不大,但可以优化)。

更优的方案是 “双重检查锁”(DCL),既保证线程安全又减少锁开销:

public?class?LazySingleton?{
? ??// 注意这里要用volatile修饰,防止指令重排序
? ??privatestaticvolatile?LazySingleton instance;

? ??private?LazySingleton()?{}

? ??public?static?LazySingleton?getInstance()?{
? ? ? ??// 第一次检查:如果实例已存在,直接返回,避免加锁
? ? ? ??if?(instance ==?null) {
? ? ? ? ? ??// 加锁:只在实例未创建时才加锁
? ? ? ? ? ??synchronized?(LazySingleton.class)?{
? ? ? ? ? ? ? ??// 第二次检查:防止多线程等待锁时,已有线程创建了实例
? ? ? ? ? ? ? ??if?(instance ==?null) {
? ? ? ? ? ? ? ? ? ? instance =?new?LazySingleton();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??return?instance;
? ? }
}

这里的关键点:

双重检查:第一次判断避免不必要的锁,第二次判断防止多线程竞争锁时重复创建。

volatile修饰实例:因为new LazySingleton()不是原子操作(分 “分配内存→初始化对象→赋值给引用” 三步),可能发生指令重排序,导致其他线程拿到 “未初始化完成的实例”,

volatile

    可以禁止重排序,保证安全性。

4. 双重检测加几次锁?

双重检测锁(DCL)中,只加一次锁

它的核心逻辑是通过 “两次判断 + 一次加锁” 来平衡线程安全和性能:

第一次判断?instance == null:如果实例已经创建,直接返回,避免进入加锁逻辑,减少不必要的锁开销(这是性能优化的关键)。当第一次判断发现实例未创建时,才进入?synchronized 同步块 —— 这里只加一次锁,确保同一时间只有一个线程能执行后续的实例创建逻辑。

进入同步块后,第二次判断?instance == null:防止多个线程在等待锁的过程中,已有线程完成了实例创建,避免重复创建。

比如代码:

public?static?LazySingleton?getInstance()?{
? ??if?(instance ==?null) { ?// 第一次判断(无锁)
? ? ? ??synchronized?(LazySingleton.class)?{ ?// 只加这一次锁
? ? ? ? ? ??if?(instance ==?null) { ?// 第二次判断(锁内)
? ? ? ? ? ? ? ? instance =?new?LazySingleton();
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ??return?instance;
}

整个过程中,只有当实例未创建且多个线程竞争时,才会触发一次加锁。一旦实例创建完成,后续调用都会在第一次判断时直接返回,不会再进入加锁逻辑。这种设计既保证了线程安全,又最大限度减少了锁的使用频率,是懒汉式单例中高效的实现方式。

5. 双重检测synachronized锁的是什么?为什么?

在双重检测锁(DCL)中,synchronized锁的是当前类的 Class 对象(如LazySingleton.class)。这是由单例模式的特性和锁的作用范围决定的。

public?class?LazySingleton?{
? ??privatestaticvolatile?LazySingleton instance;

? ??private?LazySingleton()?{}

? ??public?static?LazySingleton?getInstance()?{
? ? ? ??if?(instance ==?null) { ?// 第一次检查(无锁,提升性能)
? ? ? ? ? ??// 锁的是当前类的Class对象,全局唯一
? ? ? ? ? ??synchronized?(LazySingleton.class)?{ ?
? ? ? ? ? ? ? ??if?(instance ==?null) { ?// 第二次检查(防止重复创建)
? ? ? ? ? ? ? ? ? ? instance =?new?LazySingleton();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ??return?instance;
? ? }
}

这里synchronized (LazySingleton.class)明确指定了锁对象是LazySingleton类的Class对象。正因为这个锁是全局唯一的,才能保证:当多个线程同时通过第一次检查(发现instancenull)时,只有一个线程能进入同步块创建实例,其他线程在同步块外等待,直到实例创建完成后,再通过第二次检查发现instance已存在,从而避免重复创建。

所以,双重检测锁中synchronized必须锁当前类的 Class 对象,原因是:

    它是全局唯一的锁,能覆盖所有线程对单例实例的竞争;与静态单例变量的生命周期匹配,确保对静态资源的并发控制有效;符合单例模式 “与类绑定” 的语义,且实现简洁。

6. JVM怎么保证一个类只加载一次?

JVM 保证一个类只被加载一次,核心依赖于类加载机制中的双亲委派模型和同步控制,两者配合确保了类加载的唯一性。

首先,双亲委派模型规定:当一个类加载器需要加载类时,会先委派给父加载器,只有父加载器无法加载时,才由自己加载。这种层级委派机制本身就避免了重复加载 。

比如java.lang.String,无论哪个类加载器触发加载,最终都会委派给顶层的启动类加载器,而启动类加载器一旦加载过,就会将结果缓存,后续再请求加载时直接返回缓存的类对象,不会重复加载。

其次,即使多个线程同时请求加载同一个类,JVM 也会通过同步机制控制加载过程。在类加载的 “加载” 阶段(通过findClassloadClass方法),JVM 会对每个类的加载过程加锁:当一个线程正在加载类 A 时,其他线程如果也请求加载类 A,会被阻塞等待,直到第一个线程完成加载(无论成功或失败),其他线程再检查缓存中是否已有该类,有则直接使用,不会重复执行加载逻辑。

举个例子:线程 1 和线程 2 同时通过AppClassLoader请求加载User类。根据双亲委派,两者都会先让父加载器尝试加载,假设父加载器都无法加载,最终回到AppClassLoader。此时AppClassLoader在加载User时会触发内部同步锁,线程 1 先获取锁开始加载,线程 2 被阻塞;线程 1 加载完成后,User类的信息会被存入 JVM 的方法区(类缓存),线程 2 获得锁后,检查到缓存中已有User类,直接返回,不会再次加载。

另外,类加载器的命名空间机制也起到辅助作用:同一个类由不同类加载器加载会被视为不同的类,但双亲委派确保了 “同一类名 + 同一类加载器” 的组合只会出现一次,进一步保证了类的唯一性。

7. 说说threadLocal?key是什么?

ThreadLocal是Java中用于解决线程安全问题的一种机制,它允许创建线程局部变量,即每个线程都有自己独立的变量副本,从而避免了线程间的资源共享和同步问题。

从内存结构图,我们可以看到:

Thread类中,有个ThreadLocal.ThreadLocalMap 的成员变量。ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,keyThreadLocal本身,value是ThreadLocal的泛型对象值。

ThreadLocal的作用

线程隔离ThreadLocal为每个线程提供了独立的变量副本,这意味着线程之间不会相互影响,可以安全地在多线程环境中使用这些变量而不必担心数据竞争或同步问题。

降低耦合度:在同一个线程内的多个函数或组件之间,使用ThreadLocal可以减少参数的传递,降低代码之间的耦合度,使代码更加清晰和模块化。

性能优势:由于ThreadLocal避免了线程间的同步开销,所以在大量线程并发执行时,相比传统的锁机制,它可以提供更好的性能。

ThreadLocal的原理

ThreadLocal的实现依赖于Thread类中的一个ThreadLocalMap字段,这是一个存储ThreadLocal变量本身和对应值的映射。每个线程都有自己的ThreadLocalMap实例,用于存储该线程所持有的所有ThreadLocal变量的值。

当你创建一个ThreadLocal变量时,它实际上就是一个ThreadLocal对象的实例。每个ThreadLocal对象都可以存储任意类型的值,这个值对每个线程来说是独立的。

当调用ThreadLocalget()方法时,ThreadLocal会检查当前线程的ThreadLocalMap中是否有与之关联的值。如果有,返回该值;如果没有,会调用initialValue()方法(如果重写了的话)来初始化该值,然后将其放入ThreadLocalMap中并返回。

当调用set()方法时,ThreadLocal会将给定的值与当前线程关联起来,即在当前线程的ThreadLocalMap中存储一个键值对,键是ThreadLocal对象自身,值是传入的值。

当调用remove()方法时,会从当前线程的ThreadLocalMap中移除与该ThreadLocal对象关联的条目。

可能存在的问题

当一个线程结束时,其ThreadLocalMap也会随之销毁,但是ThreadLocal对象本身不会立即被垃圾回收,直到没有其他引用指向它为止。

因此,在使用ThreadLocal时需要注意,如果不显式调用remove()方法,或者线程结束时未正确清理ThreadLocal变量,可能会导致内存泄漏,因为ThreadLocalMap会持续持有ThreadLocal变量的引用,即使这些变量不再被其他地方引用。

因此,实际应用中需要在使用完ThreadLocal变量后调用remove()方法释放资源。

8. 讲讲反射的原理,用过反射吗?

反射是 Java 中一种在运行时动态获取类信息、调用类的方法或访问属性的机制,简单说就是 “在程序运行时,不用知道类的具体信息,也能操作它的成员”。

它的原理可以从 “类的元数据” 角度理解:当一个类被加载到 JVM 后,JVM 会为这个类创建一个唯一的Class对象(包含类的所有信息,比如类名、方法、属性、构造函数等)。

反射的核心就是通过这个Class对象,反向获取类的结构信息,并动态操作。比如,我们可以通过Class.forName("com.example.User")获取User类的Class对象,然后用它创建实例、调用方法,哪怕编译时根本不知道User类的存在。

具体步骤大致是:

获取目标类的Class对象(有三种方式:类名.class对象.getClass()Class.forName("全类名"));通过Class对象提供的方法(如getMethod()getField())获取要操作的方法、属性或构造函数;调用对应方法的invoke()(方法)、set()/get()(属性)等 API,动态执行操作。

举个简单例子,用反射调用User类的setName方法:

// 1. 获取Class对象
Class<?> userClass = Class.forName("com.example.User");
// 2. 创建实例(相当于new User())
Object user = userClass.getConstructor().newInstance();
// 3. 获取setName方法(参数为方法名和参数类型)
Method setNameMethod = userClass.getMethod("setName", String.class);
// 4. 调用方法(参数为实例对象和方法参数)
setNameMethod.invoke(user,?"张三");

实际开发中用过反射的场景不少,比如:

框架底层:Spring 的 IOC 容器就是通过反射创建 Bean 实例的,根据配置文件中的类名动态加载类并实例化;

ORM 框架:MyBatis 在映射数据库字段到 Java 对象时,会用反射设置对象的属性值;

工具类:比如实现一个通用的对象拷贝工具,通过反射遍历源对象的属性,复制到目标对象,不用为每个类写重复代码。

不过反射也有缺点,比如性能比直接调用差(因为需要动态解析类信息),而且会绕过编译期的类型检查,可能带来安全问题(比如访问私有成员),所以一般在框架或工具类中使用,业务代码中尽量避免。

9. 不同作用域的注解有哪些?

注解的作用域(即注解 的生命周期或保留范围)由@Retention元注解指定,它决定了注解在程序的哪个个阶段有效。根据RetentionPolicy的不同,主要分为以下三种作用域:

SOURCE(源码级):这种注解只在源码源码阶段有效,编译成字节码(.class 文件)后就会被丢弃,不会进入现在字节码中。作用是通常用于源码检查或生成辅助代码,比如编译器通过注解做语法校验。

典型例子:

@Override(标记方法重写,编译器会检查是否真的重写了父类方法,若未重写则报错)、@SuppressWarnings(告诉编译器忽略特定警告,如未使用变量的警告)。

CLASS(字节码级):注解会保留到字节码文件中,但不会被加载到 JVM 的运行时内存中。这是默认的作用域(如果不指定@Retention,默认就是 CLASS)。作用是主要供字节码处理工具(如类加载器、字节码增强工具)使用,在类加载阶段做一些处理。典型例子:一些字节码增强框架(如 AspectJ 的部分注解),会在编译后、类加载前解析字节码中的注解,动态修改类结构。

RUNTIME(运行时级):注解会一直保留到程序运行时,被加载到 JVM 中,因此可以通过反射机制在运行时获取注解信息。作用是这是最常用的作用域,用于运行时动态处理逻辑,比如框架通过反射读取注解配置,实现依赖注入、权限校验等。典型例子:Spring 的@Autowired(运行时通过反射找到匹配的依赖并注入)、@RequestMapping;Junit 的@Test(运行时识别测试方法并执行)。

简单说,三种作用域的核心区别是 “存活阶段”:SOURCE 只在源码,CLASS 到字节码,RUNTIME 到运行时。实际开发中,根据需求选择:做源码校验用 SOURCE,字节码处理用 CLASS,运行时动态逻辑用 RUNTIME(最常见)。

10. Redis集群模式下会不会出现多线程问题?

不会,Redis 的核心处理逻辑是单线程的 。

每个 Redis 节点在处理客户端请求时,会用一个主线程按顺序执行命令,避免了多线程上下文切换的开销,同时天然保证了单个节点内命令执行的原子性(一个命令执行完才会处理下一个)。即使在集群模式下,每个节点仍是独立的单线程处理模型,节点内部的命令执行是串行(串行)的,不会出现多个线程同时操作同一数据的情况。

其次,Redis 集群通过分片(Sharding)将数据分散到不同节点:每个 key 通过哈希计算确定属于哪个槽(slot),而每个槽只由集群中的一个主节点负责管理。这意味着,同一个 key 在集群中只会被一个节点处理,不会同时分布在多个节点上。当多个客户端并发操作同一个 key 时,这些请求最终会路由到同一个节点,由该节点的单线程按顺序处理,自然避免了跨节点的并发冲突。

不过,集群模式下可能存在一些 “分布式场景下的并发问题”,但这不属于多线程问题,而是分布式一致性问题。比如:

    • 主从同步延迟:主节点处理写命令后,同步到从节点可能有短暂延迟,此时从节点读取可能拿到旧值,但这是数据同步的时序问题,和多线程无关;集群脑裂:网络分区导致主节点被误判为下线,从节点升级为主节点,可能出现两个主节点短暂并存,但通过min-replicas-to-write等配置可降低风险,这是分布式集群的容错问题。

11. 算法

    算法:给定一个无序数组,返回数组排序后相邻元素的最大差值,要求时间复杂度、空间复杂度为o(n)

相关推荐