当前位置: 首页 > news >正文

真实并发编程问题-1.钉钉面试题

  • 👏作者简介:大家好,我是爱吃芝士的土豆倪,24届校招生Java选手,很高兴认识大家
  • 📕系列专栏:Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术
  • 🔥如果感觉博主的文章还不错的话,请👍三连支持👍一下博主哦
  • 🍂博主正在努力完成2023计划中:源码溯源,一探究竟
  • 📝联系方式:nhs19990716,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬👀

文章目录

  • 前言
  • 问题描述
  • 解决思路
    • 常规解法
      • 问题
    • 其他解法
  • 总结

前言

学完了并发编程,是否真的能够灵活应用其思想呢?

实践才是检验真理的唯一标准,好记性不如烂笔头。

下面就让我以我一个朋友社招面试钉钉的一道面试题来讲解下并发编程的实际应用吧。

问题描述

// 假设我们有如下代码,query 是公共方法会提供给任意业务方调用,请完成 query 方法
// 要求:多线程情况下 loadFromServer 调用次数最多只执行一次,且每次调用query方法要有回调回来的数据
public class Main {private Executor mExecutor = Executors.newFixedThreadPool(4);private Executor mServerExecutor = Executors.newFixedThreadPool(4);private Data mData;public void queryData(Callback callback) {if (callback == null) {return;}mExecutor.execute(new Runnable() {@Overridepublic void run() {// todo 代码写在这}});}private void loadFromServer(Callback callback) {mServerExecutor.execute(new Runnable() {@Overridepublic void run() {// mocktry {Thread.sleep(5000L);} catch (InterruptedException e) {throw new RuntimeException(e);}if (callback != null) {callback.onSuccess(new Data());}}});}public static class Data {}public interface Callback {void onSuccess(Data data);}
}

测试类

public class Test {private static volatile int cnt = 0;public static void main(String[] args) throws InterruptedException {Main main = new Main();for (int i = 0 ; i < 5; i++) {new Thread(() -> {main.queryData(new Main.Callback() {@Overridepublic void onSuccess(Main.Data data) {if (data == null) {System.out.println("data is null");} else {System.out.println("getData is " + data);}++cnt;}});}).start();}Thread.sleep(20000L);System.out.println("cnt = " + cnt);}}

这道题的本质就是说,多线程情况下 loadFromServer 调用次数最多只执行一次,且每次调用query方法要有回调回来的数据,光从题意上我们能够很清楚的想到思路,这并不难。

解决思路

常规解法

首先能想到的是,这一看不就是很像多线程情况下的单例模式?其能保证多线程情况下 loadFromServer 调用次数最多只执行一次,但是还需要每次调用query方法要有回调回来的数据,也就是说,假如一次来了五个调用,那么其他调用要等loadFromServer 调用过一次之后,才能够返回,这不就是典型的线程同步问题,可以使用CountDownLatch来实现。

基于此分析,那么我们针对这个问题就非常清晰了,这也是立马能想到的解法之一了。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;// 假设我们有如下代码,query 是公共方法会提供给任意业务方调用,请完成 query 方法
// 要求:多线程情况下 loadFromServer 调用次数最多只执行一次,且每次调用query方法要有回调回来的数据
public class Main {private Executor mExecutor = Executors.newFixedThreadPool(4);private Executor mServerExecutor = Executors.newFixedThreadPool(4);private Data mData;// 定义一个 volatile 变量来保证线程可见性和禁止指令重排序private volatile boolean mHasLoadFromServer = false;private CountDownLatch mLatch = new CountDownLatch(1);public void queryData(Callback callback) {if (callback == null) {return;}mExecutor.execute(new Runnable() {@Overridepublic void run() {// 双重检查加锁if (!mHasLoadFromServer) {synchronized (Main.this) {if (!mHasLoadFromServer) {loadFromServer(new Callback() {@Overridepublic void onSuccess(Data data) {mData = data;mLatch.countDown();}});mHasLoadFromServer = true;try {mLatch.await(); // 等待 loadFromServer 执行完成} catch (InterruptedException e) {e.printStackTrace();}}}}callback.onSuccess(mData);}});}private void loadFromServer(Callback callback) {mServerExecutor.execute(new Runnable() {@Overridepublic void run() {// mocktry {Thread.sleep(5000L);} catch (InterruptedException e) {throw new RuntimeException(e);}mData = new Data();if (callback != null) {callback.onSuccess(mData);}}});}public static class Data {}public interface Callback {void onSuccess(Data data);}
}

运行结果:

getData is Main$Data@17f2e0c9
getData is Main$Data@17f2e0c9
getData is Main$Data@17f2e0c9
getData is Main$Data@17f2e0c9
getData is Main$Data@17f2e0c9
cnt = 5

问题

我们重点看一下这块的代码

public void queryData(Callback callback) {if (callback == null) {return;}mExecutor.execute(new Runnable() {@Overridepublic void run() {// 双重检查加锁if (!mHasLoadFromServer) {synchronized (Main.this) {if (!mHasLoadFromServer) {loadFromServer(new Callback() {@Overridepublic void onSuccess(Data data) {mData = data;mLatch.countDown();}});mHasLoadFromServer = true;try {mLatch.await(); // 等待 loadFromServer 执行完成} catch (InterruptedException e) {e.printStackTrace();}}}}callback.onSuccess(mData);}});}

我们每次其实都需要进行一个锁的判断,假如说后续,如果后续mData不为null,其实是可以直接调用返回的,并不需要进行判断和锁的竞争,这也是性能并不好的情况。

修改:

public void queryData(Callback callback) {if (callback == null) {return;}if (mData != null) {callback.onSuccess(mData);return;}mExecutor.execute(new Runnable() {@Overridepublic void run() {// 双重检查加锁if (!mHasLoadFromServer) {synchronized (Main.this) {if (!mHasLoadFromServer) {loadFromServer(new Callback() {@Overridepublic void onSuccess(Data data) {mData = data;mLatch.countDown();}});mHasLoadFromServer = true;try {mLatch.await(); // 等待 loadFromServer 执行完成} catch (InterruptedException e) {e.printStackTrace();}}}}callback.onSuccess(mData);}});}

但是该方法可能还存在问题,假如在等待的过程中,积攒了太多太多的请求,那么我们集成进行回调的时候,可能超过我们服务器所能承受的阈值,那么可能会影响影响,为此可以采用其他解法来实现。

其他解法

public class Main {private Executor mExecutor = Executors.newFixedThreadPool(4);private Executor mServerExecutor = Executors.newFixedThreadPool(4);private Data mData;private volatile boolean mIsLoading = false;private List<Callback> mCallbacks = new ArrayList<>();public void queryData(Callback callback) {if (callback == null) {return;}if (mData != null) {callback.onSuccess(mData);return;}synchronized (this) {if (mIsLoading) {// 数据正在加载中,等待回调// 将回调函数添加到数据加载完成后的回调列表中mCallbacks.add(callback);return;}mIsLoading = true;mCallbacks.add(callback);}mExecutor.execute(new Runnable() {@Overridepublic void run() {if (mData == null) {loadFromServer(new Callback() {@Overridepublic void onSuccess(Data data) {System.out.println("loadFromServer");mData = data;notifyCallbacks(data);}});} else {// 数据已经加载完成,直接返回callback.onSuccess(mData);}}});}private void loadFromServer(Callback callback) {mServerExecutor.execute(new Runnable() {@Overridepublic void run() {// mocktry {Thread.sleep(5000L);} catch (InterruptedException e) {throw new RuntimeException(e);}if (callback != null) {callback.onSuccess(new Data());}}});}private void notifyCallbacks(Data data) {synchronized (this) {for (Callback callback : mCallbacks) {callback.onSuccess(data);}mCallbacks.clear();}}public static class Data {}public interface Callback {void onSuccess(Data data);}
}

这种解法是采用一种回调集合的方法,假如说等待回调的请求过多,完全可以采用生产者消费者的思想来实现,基于回调集合,等到将来回调的时候,根据实际的一个性能阈值从回调集合中进行回调,使得系统能够稳定的运行。

总结

其实这个问题不仅仅想说一些解法的小细节,还是想说,其实这个面试题,更像是真实业务模型中抽取出来的,很偏向于业务开发,当我们学习完并发编程的时候,能够学习这样真实的业务模型,并能针对不同的场景进行分析,就能够触类旁通,更好的将并发编程的解决思路应用于实际问题的解决中去。

相关文章:

真实并发编程问题-1.钉钉面试题

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理、数据库技术&#x1f525;如果感觉博主的文章还不错的…...

基于vue+element-plus+echarts制作动态绘图页面(柱状图,饼图和折线图)

前言 我们知道echarts是一个非常强大的绘图库&#xff0c;基于这个库&#xff0c;我们可以绘制出精美的图表。对于一张图来说&#xff0c;其实比较重要的就是配置项&#xff0c;填入不同的配置内容就可以呈现出不同的效果。 当然配置项中除了样式之外&#xff0c;最重要的就是…...

2312llvm,02前端

前端 编译器前端,在生成目标相关代码前,把源码变换为编译器的中间表示.因为语言有独特语法和语义,所以一般,前端只处理一个语言或一组类似语言. 比如Clang,处理C,C,objective-C源码. 介绍Clang Clang项目是C,C,Objective-C官方的LLVM前端.Clang的官方网站在此. 实际编译器(…...

【MATLAB源码-第101期】基于matlab的蝙蝠优化算BA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 蝙蝠算法&#xff08;BA&#xff09;是一种基于群体智能的优化算法&#xff0c;灵感来源于蝙蝠捕食时的回声定位行为。这种算法模拟蝙蝠使用回声定位来探测猎物、避开障碍物的能力。在蝙蝠算法中&#xff0c;每只虚拟蝙蝠代表…...

【数据结构】二叉树的模拟实现

前言:前面我们学习了堆的模拟实现&#xff0c;今天我们来进一步学习二叉树&#xff0c;当然了内容肯定是越来越难的&#xff0c;各位我们一起努力&#xff01; &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:数据结构 &#x1f448; &…...

open3d bug:pcd转txt前后位姿发生改变

1、open3d bug&#xff1a;pcd转txt前后位姿发生改变 open3d会对原有结果进行一个微小位姿变换 import open3d as o3d import numpy as np# 读取PCD点云文件 pcd o3d.io.read_point_cloud(/newdisk/darren_pty/zoom_centered_s2.pcd)# 获取点云坐标 points pcd.points# 指定…...

持续集成交付CICD:Jenkins使用GitLab共享库实现基于Ansible的CD流水线部署前后端应用

目录 一、实验 1.部署Ansible自动化运维工具 2.K8S 节点安装nginx 3.Jenkins使用GitLab共享库实现基于Ansible的CD流水线部署前后端应用 二、问题 1.ansible安装报错 2.ansible远程ping失败 3. Jenkins流水线通过ansible命令直接ping多台机器的网络状态报错 一、实验 …...

OpenAI 疑似正在进行 GPT-4.5 灰度测试!

‍ 大家好&#xff0c;我是二狗。 今天&#xff0c;有网友爆料OpenAI疑似正在进行GPT-4.5灰度测试&#xff01; 当网友询问ChatGPT API调用查询模型的确切名称是什么时&#xff1f; ChatGPT的回答竟然是 gpt-4.5-turbo。 也有网友测试之后发现仍然是GPT-4模型。 这是有网友指…...

DC-6靶场

DC-6靶场下载&#xff1a; https://www.five86.com/downloads/DC-6.zip 下载后解压会有一个DC-3.ova文件&#xff0c;直接在vm虚拟机点击左上角打开-->文件-->选中这个.ova文件就能创建靶场&#xff0c;kali和靶机都调整至NAT模式&#xff0c;即可开始渗透 首先进行主…...

单片机应用实例:LED显示电脑电子钟

本例介绍一种用LED制作的电脑电子钟&#xff08;电脑万年历&#xff09;。其制作完成装潢后的照片如下图&#xff1a; 上图中&#xff0c;年、月、日及时间选用的是1.2寸共阳数码管&#xff0c;星期选用的是2.3寸数码管&#xff0c;温度选用的是0.5寸数码管&#xff0c;也可根据…...

会议剪影 | 思腾合力受邀出席首届CCF数字医学学术年会

首届CCF数字医学学术年会&#xff08;CCF Digital Medicine Symposium&#xff0c;DMS&#xff09;于2023年12月15日-17日在苏州CCF业务总部召开。这次会议的成功召开&#xff0c;标志着数字医学领域进入了一个新的时代&#xff0c;计算机技术和人工智能在医学领域的应用和发展…...

node.js mongoose中间件(middleware)

目录 简介 定义模型 注册中间件 创建doc实例&#xff0c;并进行增删改查 方法名和注册的中间件名相匹配 执行结果 分析 错误处理中间件 手动抛出错误 注意点 简介 在mongoose中&#xff0c;中间件是一种允许在执行数据库操作前&#xff08;pre&#xff09;或后&…...

[Toolschain cpp ros cmakelist python vscode] 记录写每次项目重复的设置和配置 不断更新

写在前面 用以前的设置&#xff0c;快速配置项目&#xff0c;以防长久不用忘记&#xff0c;部分资料在资源文件里还没有整理 outline cmakelist 复用vscode 找到头文件vscode debug现有代码直接关联远端gitros杂记repo 杂记glog杂记 cmakelist 复用 包含了根据系统路径找库…...

【每日OJ—有效的括号(栈)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1、有效的括号题目&#xff1a; 1.1方法讲解&#xff1a; 1.2代码实现&#xff1a; 总结 前言 世上有两种耀眼的光芒&#xff0c;一种是正在升起的太阳&#…...

.gitignore和git lfs学习

The ninth day——12.18 1. .gitignore 忽略规则优先级 从命令行中读取可用的忽略规则当前目录定义的规则父级目录定义的规则&#xff0c;依次递推$GIT_DIR/info/exclude 文件中定义的规则core.excludesfile中定义的全局规则 忽略规则匹配语法 空格不匹配任意文件&#xff…...

2023-12-18 C语言实现一个最简陋的B-Tree

点击 <C 语言编程核心突破> 快速C语言入门 C语言实现一个最简陋的B-Tree 前言要解决问题:想到的思路:其它的补充: 一、C语言B-Tree基本架构: 二、可视化总结 前言 要解决问题: 实现一个最简陋的B-Tree, 研究B-Tree的性质. 对于B树, 我是心向往之, 因为他是数据库的基…...

vite与webpack?

vite对比react-areate-app 1、构建速度 2、打包速度 3、打包文件体积...

距离矩阵路径优化Python Dijkstra(迪杰斯特拉)算法和冲突驱动子句学习

Dijkstra算法 Dijkstra 算法是一种流行的寻路算法&#xff0c;通常用于基于图的问题&#xff0c;例如在地图上查找两个城市之间的最短路径、确定送货卡车可能采取的最短路径&#xff0c;甚至创建游戏地图。其背后的直觉基于以下原则&#xff1a;从起始顶点访问所有相邻顶点&am…...

Selenium安装WebDriver:ChromeDriver与谷歌浏览器版本快速匹配_最新版120

最近在使用通过selenium操作Chrome浏览器时&#xff0c;安装中遇到了Chrome版本与浏览器驱动不匹配的的问题&#xff0c;在此记录安装下过程&#xff0c;如何快速找到与谷歌浏览器相匹配的ChromeDriver驱动版本。 1. 确定Chrome版本 我们首先确定自己的Chrome版本 Chrome设置…...

系统架构设计师教程(七)系统架构设计基础知识

系统架构设计基础知识 7.1 软件架构概念7.1.1 软件架构的定义7.1.2 软件架构设计与生命周期需求分析阶段设计阶段实现阶段构件组装阶段部署阶段后开发阶段 7.1.3 软件架构的重要性 7.2 基于架构的软件开发方法7.2.1 体系结构的设计方法概述7.2.2 概念与术语7.2.3 基于体系结构的…...

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

如何在看板中有效管理突发紧急任务

在看板中有效管理突发紧急任务需要&#xff1a;设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP&#xff08;Work-in-Progress&#xff09;弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中&#xff0c;设立专门的紧急任务通道尤为重要&#xff0c;这能…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

稳定币的深度剖析与展望

一、引言 在当今数字化浪潮席卷全球的时代&#xff0c;加密货币作为一种新兴的金融现象&#xff0c;正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而&#xff0c;加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下&#xff0c;稳定…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

Visual Studio Code 扩展

Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后&#xff0c;命令 changeCase.commands 可预览转换效果 EmmyLua…...

Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解

文章目录 一、开启慢查询日志&#xff0c;定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...