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

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

文章目录

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

前言

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

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

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

问题描述

java">// 假设我们有如下代码,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() {
            @Override
            public void run() {
                // todo 代码写在这
            }
        });
    }

    private void loadFromServer(Callback callback) {
        mServerExecutor.execute(new Runnable() {
            @Override
            public void run() {
                // mock
                try {
                    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);
    }
}

测试类

java">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() {
                    @Override
                    public 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来实现。

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

java">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() {
            @Override
            public void run() {
                // 双重检查加锁
                if (!mHasLoadFromServer) {
                    synchronized (Main.this) {
                        if (!mHasLoadFromServer) {
                            loadFromServer(new Callback() {
                                @Override
                                public 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() {
            @Override
            public void run() {
                // mock
                try {
                    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

问题

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

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

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

修改:

java">public void queryData(Callback callback) {
        if (callback == null) {
            return;
        }

        if (mData != null) {
            callback.onSuccess(mData);
            return;
        }

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

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

其他解法

java">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() {
            @Override
            public void run() {
                if (mData == null) {
                    loadFromServer(new Callback() {
                        @Override
                        public 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() {
            @Override
            public void run() {
                // mock
                try {
                    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.niftyadmin.cn/n/5273941.html

相关文章

postman和代码里面如何增加鉴权

postman配置toekn: 方法1&#xff1a; 方法2&#xff1a; get请求在postman中使用的时候&#xff0c;authorization中带bearer token&#xff0c;那么使用Python构造get请求时&#xff0c;该token应该怎么带入呢&#xff1f; 解决方法如下&#xff1a; url_user "htt…

NPDP证书含金量高吗?跟PMP相比含金量怎么样?

两个证方向不太一样&#xff0c;含金量都挺高的&#xff0c;具体怎么选呢&#xff1f;接着往下看~ PS&#xff1a;不想看长篇大论的&#xff0c;来找我&#xff0c;直接把你的经历甩出来&#xff0c;我帮你判断~ 一、产品经理跟项目经理的区别 表面上&#xff0c;项目经理和产…

Docker 核心技术

Docker 定义&#xff1a;于 Linux 内核的 Cgroup&#xff0c;Namespace&#xff0c;以及 Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;属于操作系统层面的虚拟化技术&#xff0c;由于隔离的进程独立于宿主和其它的隔离的进程&#xff0c;因此也称其为容器Docke…

JVM-3-OutOfMemoryErrory内存溢出

堆溢出 Java堆用于储存对象实例&#xff0c;我们只要不断地创建对象&#xff0c;并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象&#xff0c;那么随着对象数量的增加&#xff0c;总容量触及最大堆的容量限制后就会产生内存溢出异常。 将堆的最小值-Xms…

WTN6系列语音芯片:PWM与DAC音频输出在PCB设计中的优势

随着科技的飞速发展&#xff0c;语音芯片在电子产品中的应用越来越广泛。其中&#xff0c;唯创知音的WTN6系列语音芯片凭借其卓越的性能和多样的功能&#xff0c;受到了市场的热烈欢迎。特别是其支持PWM和DAC两种音频输出方式的特点&#xff0c;使得工程师在PCB设计时能够更加灵…

生日蜡烛C语言

分析&#xff1a;假设这个人只能活到100岁&#xff0c;如果不这样规定的话&#xff0c;那么这个人就可以假设活到老236岁&#xff0c;直接一次吹236个蜡烛&#xff0c;我们就枚举出所以情况&#xff0c;从一岁开始。 #include <stdio.h> int f(int a,int b){//计算从a到…

大数据技术15:大数据常见术语汇总

前言&#xff1a;大数据的出现带来了许多新的术语&#xff0c;但这些术语往往比较难以理解。因此&#xff0c;通过本文整理了大数据开发工程师经常会接触到的名词和概念&#xff0c;了解这些专有名词对于数据研发和数据分析时的人员协作及研发都有很高的作用。 一、数据中台相关…

Gin之GORM的表关联查询操作详解

前期工作&#xff1a; 先查看下要操作的两张表&#xff1a; carton carton_cate //关系如下&#xff1a; // 一个章节对应一个动漫&#xff08;一对一&#xff1b;两种方法&#xff1a;belong to&#xff1b;has one&#xff09; // 一个动漫可以对应多个章节&#xff08;一…