博客记录-day115-Java并发面试题+项目面试题

191 阅读8分钟

一、语雀-Java并发面试题

1、如何理解AQS?

✅如何理解AQS?

image.png

image.png

image.png

2、什么是CAS?存在什么问题?

✅什么是CAS?存在什么问题?

image.png

1. ABA问题

image.png

2. 忙等待

image.png

3、乐观锁与悲观锁

image.png

4、JUC

image.png

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、对象创建

image.png

3、什么是Unsafe?

✅什么是Unsafe?

image.png

4、synchronized和reentrantLock区别?

✅synchronized和reentrantLock区别?

image.png

5、ReentrantLock 如何实现可重入的

image.png

6、CountDownLatch、CyclicBarrier、Semaphore区别?

✅CountDownLatch、CyclicBarrier、Semaphore区别?

image.png

7、父子线程之间怎么共享/传递数据?

✅父子线程之间怎么共享/传递数据?

image.png

1.InheritableThreadLocal

image.png

image.png

8、有三个线程T1,T2,T3如何保证顺序执行?

✅有三个线程T1,T2,T3如何保证顺序执行?

image.png

9、三个线程分别顺序打印0-100

✅三个线程分别顺序打印0-100

image.png

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

image.png

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、大营销还有哪些可以优化的地方

  1. 结构上;公司发展快,业务需求多。可以把积分单独拆分一个系统、返利单独拆分一个系统(并做成任务系统)

  2. 功能上;支持动态调整活动库存,由decr 改为 incr 和总量比。总量可以增加。之后支持失败库存写入队列,那么incr 就是和总量 + 失败队列比。

  3. 针对抽奖概率计算效率低、公平性偏差问题,设计了动态权重算法,根据用户行为数据实时调整中奖概率,并通过预计算抽奖池优化实时开销。

  4. 为解决用户对抽奖规则感知模糊的痛点,创新实现实时概率展示和抽奖回放功能,用户可直观查看剩余奖品、中奖率及历史记录。优化后用户投诉率下降30%,留存率提升20%,活动页面跳出率降低15%。

  5. 通过设计阶梯式抽奖(连续参与解锁高阶奖品)和社交裂变机制(分享获额外次数),结合动画效果与音效增强趣味性