真实并发编程问题-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);}
}

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

总结

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/237218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity中Shader缩放矩阵

文章目录 前言一、直接相乘缩放1、在属性面板定义一个四维变量&#xff0c;用xyz分别控制在xyz轴上的缩放2、在常量缓存区申明该变量3、在顶点着色器对其进行相乘&#xff0c;来缩放变换4、我们来看看效果 二、使用矩阵乘法代替直接相乘缩放的原理1、我们按如下格式得到缩放矩阵…

C#基础——文件、文件夹操作和序列化存储

文件操作、文件夹操作和序列化存储 1、文件操作 如果要对文件进行操作&#xff0c;首先需要先引入IO命名空间 using System.IO;File 类位于 System.IO 命名空间中&#xff0c;用于执行文件级别的操作。它提供了一组静态方法&#xff0c;用于创建、复制、删除、移动和读取文件…

漫谈UNIX、Linux、UNIX-Like

漫谈UNIX、Linux、UNIX-Like 使用了这么多年Redhat、Ubuntu等Linux、Windows、Solaris操作系统&#xff0c;你是否对UNIX、Unix-Like&#xff08;类UNIX&#xff09;还是不太清楚&#xff1f;我以前一直认为Unix-Like就等于Linux。其实&#xff0c;由UNIX派生出来而没有取得UN…

java实现回文数算法

判断一个数是否为回文数可以使用以下算法&#xff1a; 将数字转化为字符串&#xff1b;初始化左右两个指针&#xff0c;分别指向字符串的首尾&#xff1b;循环比较左右指针指向的字符&#xff0c;如果相等则继续比较&#xff0c;直到左右指针相遇或者发现不相等的字符为止&…

ES集群G1回收器,堆空间无法被回收问题

ES堆空间不足的问题&#xff0c;困扰了我有两年的时间。dump堆去分析&#xff0c;也未能分析出来&#xff0c;堆到底是被什么占用了。 我把堆空间给了31.9G&#xff0c;这是指针压缩生效的临界值&#xff0c;如果再大就指针压缩失效了。 痛苦的是&#xff0c;随着时间的增长。堆…

mysql复习笔记05(小滴课堂)

mysql的慢查询日志开启与问题定位 一张数据库数据很大的表。 查询一条数据&#xff0c;很快就查询出来了。 根据不同的条件&#xff0c;查到的数据相同&#xff0c;但是查询所花费的时间却是不同的。 使用命令查询慢查询日志是否开启&#xff0c;目前它是关闭着的。 开启日志。…

利用prometheus+grafana进行Linux主机监控

文章目录 一.架构说明与资源准备二.部署prometheus1.上传软件包2.解压软件包并移动到指定位置3.修改配置文件4.编写启动脚本5.启动prometheus服务 三.部署node-exporter1.上传和解压软件包2.设置systemctl启动3.启动服务 四.部署grafana1.安装和启动grafana2.设置prometheus数据…

Java研学-HTTP 协议

一 概述 1 概念和作用 概念&#xff1a;HTTP 是 HyperText Transfer Protocol (超文本传输协议)的简写&#xff0c;它是 TCP/IP 协议之上的一个应用层协议。简单理解就是 HTTP 协议底层是对 TCP/IP 协议的封装。   作用&#xff1a;用于规定浏览器和服务器之间数据传输的格式…

【源码解析】聊聊ReentrantReadWriteLock是如何实现的读写锁

为什么需要读写锁 在并发编程领域&#xff0c;有多线程进行提升整体性能&#xff0c;但是却引入了共享数据安全性问题。基本就是无锁编程下的单线程操作&#xff0c;有互斥同步锁操作&#xff0c;但是性能不高&#xff0c;并且同一时刻只有一个线程可以操作资源类。但是对于大…

[SWPUCTF 2021 新生赛]gift_F12

打开环境 题目有提示&#xff08;F12&#xff09;&#xff0c;那就查看一下源代码 直接滑到最后 看提示猜测&#xff0c;flag就在源代码里了 ctrlf查找flag 最后得到flag&#xff0c;改一下形式就可以了

ORACLE总结

例1&#xff1a; 现在有 医嘱主表ORDER_MAIN(ID,ORDER_ID,ORDER_NAME) A表 和医嘱执行表ORDER_EXEC(ID,ORDER_ID,PLAN_TIME) B表 两个表是用ORDER_ID关联起来的&#xff0c;A表一个ORDER_ID对应一条数据&#xff0c;B表一个ORDER_ID对应多个字段&#xff0c;要求根据ORDER_…

网络技术基础与计算思维实验教程_2.4_跨交换机VLAN配置实验

实验内容 实验目的 实验原理 实验步骤 构建 在工作区放置交换机然后单击 选择config , 把交换机的默认名改为switch1 再放置两个交换机 再放置终端 放置三台与交换机1相连的终端 再放置三台与交换机3相连的终端 再放置两台与交换机2相连的终端 用直通线连接 然后用交叉线互联交…

java8流库之Stream.iterate

简介 java.util.stream.Stream 下共有两个 iterate iterate(T seed, final UnaryOperator<T> f)iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> f) 该方法产生一个无限流&#xff0c;它的元素包含seed&#xff0c;在seed上调用f产生的…

Matlab论文插图绘制模板第131期—函数等高线图

在之前的文章中&#xff0c;分享了Matlab函数折线图的绘制模板&#xff1a; 函数三维折线图&#xff1a; 函数网格曲面图&#xff1a; 函数曲面图&#xff1a; 进一步&#xff0c;再来分享一下函数等高线图。 先来看一下成品效果&#xff1a; 特别提示&#xff1a;本期内容『数…

【Week-P2】CNN彩色图片分类-CIFAR10数据集

文章目录 一、环境配置二、准备数据三、搭建网络结构四、开始训练五、查看训练结果六、总结3.1 ⭐ torch.nn.Conv2d()详解3.2 ⭐ torch.nn.Linear()详解3.3 ⭐torch.nn.MaxPool2d()详解3.4 ⭐ 关于卷积层、池化层的计算4.2.1 optimizer.zero_grad()说明4.2.2 loss.backward()说…

MyBatis Plus使用遇到的问题

如果想使用Mapper的xxxById()方法&#xff0c;实体类的主键上面必须加上TableId注解&#xff0c;如果不加&#xff0c;会报错 2023-12-21 22:48:33.526 WARN 11212 --- [ main] c.b.m.core.injector.DefaultSqlInjector : class com.example.mybatisplusdemo.dom…

ubuntu18.04 64 位安装笔记——备赛笔记——2024全国职业院校技能大赛“大数据应用开发”赛项——任务2:离线数据处理

进入VirtuakBox官网&#xff0c;网址链接&#xff1a;Oracle VM VirtualBoxhttps://www.virtualbox.org/ 网页连接&#xff1a;Ubuntu Virtual Machine Images for VirtualBox and VMwarehttps://www.osboxes.org/ubuntu/ 将下发的ds_db01.sql数据库文件放置mysql中 12、编写S…

无约束优化问题求解笔记(2):最速下降法

目录 3. 最速下降法3.1 最速下降法的基本思想3.2 基于精确搜索的最速下降法3.3 基于精确搜索的最速下降法的程序实现3.4 基于精确搜索的最速下降法的缺点 Reference 3. 最速下降法 3.1 最速下降法的基本思想 最速下降法是典型的线搜索方法. 设 f f f 是 R n \mathbb{R}^n R…

vue2与vue3的区别

首先了解什么是vue3 Vue.js 3.0&#xff08;简称Vue 3&#xff09;是一个流行的JavaScript框架Vue.js的最新版本。Vue 3于2020年9月正式发布&#xff0c;带来了许多令人兴奋的新特性和改进。下面是Vue 3的一些主要特点&#xff1a; 更快的性能&#xff1a;Vue 3通过全新的响应…

【redis】redis系统实现发布订阅的标准模板

目录 简介参数配置代码模板 简介 Redis发布订阅功能是Redis的一种消息传递模式&#xff0c;允许多个客户端之间通过消息通道进行实时的消息传递。在发布订阅模式下&#xff0c;消息的发送者被称为发布者&#xff08;publisher&#xff09;&#xff0c;而接收消息的客户端被称为…