一、语雀-Java并发面试题
1、如何理解AQS?
2、什么是CAS?存在什么问题?
1. ABA问题
2. 忙等待
3、乐观锁与悲观锁
4、JUC
public class AtomicInteger extends Number implements java.io.Serializable {
private volatile int value;
public final int get() {
return value;
}
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
}
5、对象创建
3、什么是Unsafe?
4、synchronized和reentrantLock区别?
✅synchronized和reentrantLock区别?
5、ReentrantLock 如何实现可重入的
6、CountDownLatch、CyclicBarrier、Semaphore区别?
✅CountDownLatch、CyclicBarrier、Semaphore区别?
7、父子线程之间怎么共享/传递数据?
1.InheritableThreadLocal
8、有三个线程T1,T2,T3如何保证顺序执行?
9、三个线程分别顺序打印0-100
1. Synchronized
public class SortTest {
private static final Object LOCK = new Object();
private static volatile int count = 0;
private static final int MAX = 100;
public static void main(String[] args) {
Thread thread = new Thread(new Seq(0));
Thread thread1 = new Thread(new Seq(1));
Thread thread2 = new Thread(new Seq(2));
thread.start();
thread1.start();
thread2.start();
}
static class Seq implements Runnable {
private final int index;
public Seq(int index) {
this.index = index;
}
@Override
public void run() {
while (count < MAX) {
synchronized (LOCK) {
try {
while (count % 3 != index) {
LOCK.wait();
}
if(count <=MAX){
System.out.println("Thread-" + index + ": " + count);
}
count++;
LOCK.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
2. ReentrantLock+Condition
public class Test {
private static final int WORKER_COUNT = 3;
private static int countIndex = 0;
private static final ReentrantLock LOCK = new ReentrantLock();
public static void main(String[] args){
final List<Condition> conditions = new ArrayList<>();
for(int i=0; i< WORKER_COUNT; i++){
// 为每一个线程分配一个condition
Condition condition = LOCK.newCondition();
conditions.add(condition);
Worker worker = new Worker(i, conditions);
worker.start();
}
}
static class Worker extends Thread{
int index;
List<Condition> conditions;
public Worker(int index, List<Condition> conditions){
super("Thread-"+index);
this.index = index;
this.conditions = conditions;
}
private void signalNext(){
int nextIndex = (index + 1) % conditions.size();
conditions.get(nextIndex).signal();
}
@Override
public void run(){
while(true) {
//锁住 保证操作间同时只有一个线程
LOCK.lock();
try {
// 如果当前线程不满足打印条件,则等待
if (countIndex % 3 != index) {
conditions.get(index).await();
}
if (countIndex > 100) {
// 唤醒下一个线程,保证程序正常退出
signalNext();
// 退出循环 线程运行结束
return;
}
System.out.println((this.getName() + " " + countIndex));
// 计数器+1
countIndex ++;
// 通知下一个干活
signalNext();
}catch (Exception e){
e.printStackTrace();
}finally {
LOCK.unlock();
}
}
}
}
}
二、大营销面试题
1. 你的项目工程是怎么搭建的?
项目的搭建类似于使用 start.spring.io 脚手架创建的,在我们的项目中,使用了统一 maven-archetype-plugin 插件,自定义了一套 DDD 工程骨架脚手架。同类项目都是使用这套脚手架创建项目,因为脚手架定义了统一的版本标准和对应配套的开发环境,所以使用起来更加容易。【如果延伸提问,你还了解哪种搭建脚手架的方式,可以回答 FreeMarker (opens new window)】
2. 在你的这个项目中,都用到了什么开发工具?项目是怎么部署的
使用到了 IntelliJ IDEA、WebStorm、Navicat、ApiPost、Docker、SwitchHosts、Termius(SSH)等,项目上线的时候使用了2个方式,一个是本地构建前后端镜像,PUSH 到 Docker Hub,再通过编写 docker compose 脚本,在云服务器部署。另外一个是搭建 Jenkins 配置部署流水线的方式进行部署。
3. 整个项目开发过程中,你熟练的掌握了哪些技术栈
在整个项目开发中熟练的使用了 SpringBoot、MyBatis、Redisson 等技术框架,在编程功能实现,熟练的结合与 Spring 容器,通过 Map 自动装配 Java 语言实现的策略模式。以及在项目中较多的使用了 Redisson 框架,向 Redis 写入 key-value、queue、map、lock 等数据类型,实现业务功能。
4. 在项目开发过程中,你有遇到过哪些运行时异常,怎么排查解决的
如刚开始项目开发引入脚手架以外的组件,进行调试的时候,因为Jar版本不同。出现过编译通过,调用的时候方法不存在。通过 Maven Helper 插件,检查到有其他组件多引入了相同Jar包。另外还有一些如开发调试中发现的空指针问题,如查后需要增加空对象判断。此外其他一些更多是功能流程实现细节上,如项目中的规则树节点判断流程问题,抛出一些自定义的异常。这些通过在方法上断点调试逐步的解决。
5. 因为你的项目是前后端分离的,接口跨域怎么做的?
首先我们知道,Web跨域(Cross-Origin Resource Sharing,CORS)是一种安全机制,用于限制一个域下的文档或脚本如何与另一个源的资源进行交互。这是一个由浏览器强制执行的安全特性,旨在防止恶意网站读取或修改另一个网站的数据,这种攻击通常被称为“跨站点脚本”(Cross-Site Scripting,XSS)。
所以在我的前后端分离项目中,通过配置 @CrossOrigin 注解来解决跨越问题。开发阶段 Access-Control-Allow-Origin: *、上线阶段 Access-Control-Allow-Origin: https://gaga.plus
6. 看到你的项目用到了 DDD,这也是我们很感兴趣的技术点,你可以介绍下你在使用 DDD 做这个项目时,都运用了 DDD 哪些知识。
DDD 是一种软件设计方法,软件设计方法涵盖了范式、模型、框架、方法论等内容,而 DDD 的很多规范约定,都是为了提高工程交付质量。如几个很重要的知识点;框架分层结构、领域、实体、聚合、值对象、依赖倒置等。它所有的手段,都希望以一个功能逻辑的实现为聚合,将功能所需的对象、接口、逻辑,按照领域划分到自己的领域内。
就像在这个项目中,我负责实现的抽奖中的策略,就是一个独立的领域模型。在这个领域中我需要提供策略的装载、随机数算法计算、抽奖模板调用(含责任链和规则树)功能,这样一个领域就像划分好的一个独立个体,它拥有属于它的对象信息(实体、值对象、聚合),当需要使用数据库资源、缓存资源,以及外部接口资源的时候,都通过依赖倒置进行调用。也就是说,我的领域不做其他模块的引入,而是领域只负责业务功能实现,所需的所有数据,则由外部接口通过依赖倒置提供。
7. 你的抽奖流程中,哪些被定义为值对象,哪些被定义为实体对象
在 DDD 的规范定义中,值对象通常用于描述对象属性的值,不具备唯一ID,不影响数据变化。如;数据库中字段的枚举值、业务流程中属性对象。如抽奖流程中,RuleLimitTypeVO 规则限定方式的枚举值对象、还有 RuleTreeVO 规则树值对象。而那些实体对象,则具备唯一ID,会影响到最后的写库动作。如;抽奖发起实体、奖品信息实体对象。并且我们可以把一些和实体对象相关的功能聚合到对象内,这样的通用性会更好,避免所有调用方都需要自己编写逻辑。
8. 关于访问数据层的依赖倒置,是怎么使用的,有什么好处,你可以描述下吗
DDD 中的依赖倒置是一个非常好的设计,尤其是与 MVC 结构对比的时候,MVC 的贫血模型结构设计,数据库持久化对象,很容易被当做业务对象使用,这样后期非常难维护。但在 DDD 的分层结构用,是以 domain 领域实现为核心,一个 domain 领域下所需的外部服务,都由领域层定义接口,让基础层做具体实现。而数据库持久化操作,定义的 PO 对象,就被这样的方式被限定在基础层了,外部是没法引入使用的,也就天然的防止了数据库持久化对象进入业务中。
9、大营销还有哪些可以优化的地方
-
结构上;公司发展快,业务需求多。可以把积分单独拆分一个系统、返利单独拆分一个系统(并做成任务系统)
-
功能上;支持动态调整活动库存,由decr 改为 incr 和总量比。总量可以增加。之后支持失败库存写入队列,那么incr 就是和总量 + 失败队列比。
-
针对抽奖概率计算效率低、公平性偏差问题,设计了动态权重算法,根据用户行为数据实时调整中奖概率,并通过预计算抽奖池优化实时开销。
-
为解决用户对抽奖规则感知模糊的痛点,创新实现实时概率展示和抽奖回放功能,用户可直观查看剩余奖品、中奖率及历史记录。优化后用户投诉率下降30%,留存率提升20%,活动页面跳出率降低15%。
-
通过设计阶梯式抽奖(连续参与解锁高阶奖品)和社交裂变机制(分享获额外次数),结合动画效果与音效增强趣味性