【JUC】并发编程学习笔记(三)

news2024/11/16 7:50:09

JUC并发编程

  • 八、ReentrantReadWriteLock 读写锁
    • 8.1、概述
    • 8.2、案例
    • 8.3、读写锁的降级
  • 九、BlockingQueue阻塞队列
    • 9.1、阻塞队列概述
    • 9.2、阻塞队列分类
      • 9.2.1、ArrayBlockingQueue(常用)
      • 9.2.2、LinkedBlockingQueue(常用)
      • 9.2.3、 DelayQueue
      • 9.2.4、 PriorityBlockingQueue
      • 9.2.5、 SynchronousQueue
    • 9.3、阻塞队列方法案例
  • 十、ThreadPool线程池
    • 10.1、概述
    • 10.2、使用
      • 10.2.1、一池N线程
      • 10.2.2、一个任务一个任务执行,一池一线程线程池
      • 10.2.3、根据需求创建线程,可扩容,遇强则强
    • 10.3、参数说明
    • 10.4、线程池底层流程
    • 10.5、自定义线程池
  • 十一、分支合并Fork/Join
  • 十二、异步回调

八、ReentrantReadWriteLock 读写锁

8.1、概述

读锁:共享锁

写锁:独占锁

都会发生死锁
读读不互斥,读写互斥,写写互斥

8.2、案例

不带锁:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache{
    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();
    //放数据
    public void put(String k,Object v) throws InterruptedException {
        System.out.println(Thread.currentThread( ).getName()+"正在放数据"+k);
        TimeUnit.MILLISECONDS.sleep(300);
        map.put(k,v);
        System.out.println(Thread.currentThread( ).getName()+"写完数据"+k);
    }
    //取数据
    public void get(String k) throws InterruptedException {
        Object res=null;
        System.out.println(Thread.currentThread( ).getName()+"正在取数据"+k);
        TimeUnit.MILLISECONDS.sleep(300);
        res=map.get(k);
        System.out.println(Thread.currentThread( ).getName()+"取完数据"+k);
    }
}
public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();

        for (int i = 1; i < 5; i++) {
            final int num=i;
            new Thread(()->{
                try {
                    myCache.put(num+"",num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

        for (int i = 1; i < 5; i++) {
            final int num=i;
            new Thread(()->{
                try {
                    myCache.get(num+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

会出现逻辑错误
在这里插入图片描述
加上读写锁

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{
    //创建map集合
    private volatile Map<String,Object> map = new HashMap<>();
    //创建读写锁对象
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //放数据
    public void put(String k,Object v) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread( ).getName()+"正在放数据"+k);
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(k,v);
            System.out.println(Thread.currentThread( ).getName()+"写完数据"+k);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }
    //取数据
    public void get(String k){
        readWriteLock.readLock().lock();
        try {
            Object res=null;
            System.out.println(Thread.currentThread( ).getName()+"正在取数据"+k);
            TimeUnit.MILLISECONDS.sleep(300);
            res=map.get(k);
            System.out.println(Thread.currentThread( ).getName()+"取完数据"+k);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }
    }
}
public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        for (int i = 1; i < 5; i++) {
            final int num=i;
            new Thread(()->{
                myCache.put(num+"",num);
            },String.valueOf(i)).start();
        }
        for (int i = 1; i < 5; i++) {
            final int num=i;
            new Thread(()->{
                myCache.get(num+"");
            },String.valueOf(i)).start();
        }
    }
}

在这里插入图片描述

8.3、读写锁的降级

读时候,不能写,只有读完成之后,才可以写,写操作的同时可以读。容易造成锁饥饿,一直读,没有写操作。

锁降级的步骤
获取写锁 -->>获取读锁 -->>释放写锁 -->>释放读锁

public static void main( String[] args) {
	//可重入读写锁对象
	ReentrantReadwriteLock rwLock = new ReentrantReadwriteLock();
	ReentrantReadwriteLock.ReadLock readLock = rwLock.readLock();//读锁
	ReentrantReadwriteLock.writeLock writeLock = rwLock.writeLock();//写锁
	//锁降级
	//1获取写锁
	writeLock.lock();
	system.out.println("123");
	//2获取读锁
	readLock.lock();
	system.out.println("456");
	//3释放写锁
	writeLock.unlock();
	//4释放读锁
	readLock.unlock();
}

九、BlockingQueue阻塞队列

9.1、阻塞队列概述

阻塞队列,顾名思义,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;

  • 当队列是空的,从队列中获取元素的操作将会被阻塞
  • 当队列是满的,从队列中添加元素的操作将会被阻塞
  • 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
  • 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办

9.2、阻塞队列分类

9.2.1、ArrayBlockingQueue(常用)

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。”

由数组结构组成的有界阻基队列。

9.2.2、LinkedBlockingQueue(常用)

ArrayBlockingQueue和LinkedBlockingQueue是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。·

一句话总结:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列

9.2.3、 DelayQueue

DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

使用优先级队列实现的延迟无界阻塞队列

9.2.4、 PriorityBlockingQueue

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。

支持优先级排序的无界阻塞队列

9.2.5、 SynchronousQueue

不存储元素的阻塞队列,也即单个元素的队列

9.3、阻塞队列方法案例

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockQueue=new ArrayBlockingQueue<>(3);

        blockQueue.put("a");
        blockQueue.put("b");
        blockQueue.put("c");
        //blockQueue.put("d");会阻塞

        System.out.println(blockQueue.take());
        System.out.println(blockQueue.take());
        System.out.println(blockQueue.take());
        //System.out.println(blockQueue.take());会阻塞
    }
}

十、ThreadPool线程池

10.1、概述

线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

它的主要特点为:

  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的销耗
  2. 提高响应速度:当任务到达时。任务可以不需要等待线程创建就能立即执行
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

10.2、使用

10.2.1、一池N线程

  • 线程池中的线程处于一定的量,可以很好的控制线程的并发量
  • 线程可以重复被使用,在显示关闭之前,都将一直存在
  • 超出—定量的线程被提交时候需在队列中等待.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        //一池5线程
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //10个任务
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread() .getName()+"处理任务ing...");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            executorService.shutdown();
        }
    }
}

10.2.2、一个任务一个任务执行,一池一线程线程池

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //一池1线程
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        //10个任务
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread() .getName()+"处理任务ing...");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            executorService.shutdown();
        }
    }
}

10.2.3、根据需求创建线程,可扩容,遇强则强

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo3 {
    public static void main(String[] args) {
        //根据需求创建线程,可扩容
        ExecutorService executorService = Executors.newCachedThreadPool();
        //10个任务
        try {
            for (int i = 1; i <=100 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread() .getName()+"处理任务ing...");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            executorService.shutdown();
        }
    }
}

10.3、参数说明

newFixedThreadPoolnewSingleThreadExecutornewCachedThreadPool底层都是new了ThreadPoolExecutor

ThreadPoolExecutor有七个参数

参数意义
int corePoolsize核心线程数量
int maximumPoolsize,最大线程数量
long keepAliveTime线程存活时间的值
TimeUnit unit线程存活时间的单位
BlockingQueue workQueue阻塞队列
ThreadFactory threadFactory线程工厂 用于创建线程
RejectedExecutionHandler handler拒绝策略

10.4、线程池底层流程

在这里插入图片描述

  1. 程序在执行execute()时才会真正创建线程
  2. 当任务数量到达corePool【个数:2】时并不会再创建新线程 而是会到阻塞队列里【序号2】
  3. 当阻塞队列快满了【个数:3 最大:4】但运行的线程没有达到最大数【个数:2 最大5】时又来了新的任务会创建新的线程执行刚来的任务【序号3】
  4. 阻塞放不下了,运行的线程也达到最大值 此时再来新任务就会执行拒绝策略【序号4】

拒绝策略:

  • AbortPolicy(默认)︰直接抛出RejectedExecutionException异常阻止系统正常运行
  • callerRunsPolicy :“调用者运行“—种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardoldestPolicy :抛弃队列中等待最久的任务,然后把当前任务加人队列中尝试再次提交当前任务。
  • DiscardPolicy :该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。

10.5、自定义线程池

在这里插入图片描述

import java.util.concurrent.*;

public class ThreadPoolDemo4 {
    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(
                2, 5, 2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        //10个任务
        try {
            for (int i = 1; i <=10 ; i++) {
                //执行
                executorService.execute(()->{
                    System.out.println(Thread.currentThread() .getName()+"处理任务ing...");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭线程池
            executorService.shutdown();
        }
    }
}

十一、分支合并Fork/Join

Fork:把一个复杂任务进行分拆
Join:把分拆任务的结果进行合并

计算1+2+…100 其中查分值不能超过10(二分法)

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

class  MyThread extends RecursiveTask<Integer> {

    private static final Integer VALUE = 10;//拆分差值
    private int begin;
    private int end;
    private int result;

    public MyThread(int begin,int end) {
        this.begin=begin;
        this.end=end;
    }

    @Override
    protected Integer compute() {
        //判断相加两个数值是否大于10
        if((end-begin)<=VALUE) {
            //相加操作
            for (int i = begin; i <=end; i++) {
                result = result+i;
            }
        } else {//进一步拆分
            //获取中间值
            int middle =(end + begin)/2;//拆分
            MyThread thread01 = new MyThread(begin,middle);
            MyThread thread02 = new MyThread(middle+1,end);

            thread01.fork();
            thread02.fork();

            result=thread01.join()+thread02.join();
        }

        return result;
    }
}
public class demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread thread = new MyThread(1,100);

        //创建分支合并池对象
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //加入任务
        ForkJoinTask<Integer> submit = forkJoinPool.submit(thread);
        //获取结果
        Integer result = submit.get();
        System.out.println(result);
        forkJoinPool.shutdown();
    }
}

十二、异步回调

package com.ynx.exarejuc;

import org.apache.coyote.http11.filters.VoidInputFilter;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Completable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //异步调用没有返回值
        CompletableFuture<Void> completableFuture1=CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName()+"completableFuture1");
        });
        completableFuture1.get();

        //异步调用有返回值
        CompletableFuture<Integer> completableFuture2=CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"completableFuture2");
            //int a=1/0;
            return 1024;
        });
        completableFuture2.whenComplete((t,u)->{
            System.out.println("t:::"+t);//返回值
            System.out.println("u:::"+u);//异常的信息
        }).get();
    }
}

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

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

相关文章

混合馈能悬架的设计与仿真(MATLAB)

目 录 摘 要 I Abstract II 第一章 绪论 1 1.1课题研究背景和意义 1 1.2国内外研究现状 2 1.3本文的主要研究内容 4 第二章 混合馈能悬架系统的工作原理 5 2.1混合馈能悬架系统的设计理念 5 2.2馈能系统的原理 6 2.3馈能系统的类型 6 2.4混合馈能悬架系统结构选型 8 2.5本章小结…

技术分享 | 如何确保API 的稳定性与正确性?你只需要这一招

现在&#xff0c;越来越多的 Web 应用转向了RESTful的架构&#xff0c;很多产品和应用暴露给用户的往往就是一组 REST API&#xff0c;这样有一个好处&#xff0c;用户可以根据需要&#xff0c;调用不同的 API&#xff0c;整合出自己的应用出来。从这个角度来讲&#xff0c;Web…

前端字体压缩(免费简单易上手)

场景&#xff1a;前端在开发过程中有时候要用到特殊字体&#xff0c;但如果引用网上下载好的字体&#xff0c;它们都是一个全的字体文件&#xff0c;这种字体文件里往往包含了大量你用不到的文字字符&#xff0c;从而导致你引入的字体文件大小高达1M以上&#xff0c;这会严重影…

C++08函数模板

1.自动推导类型 在C语言和C98中&#xff0c;auto 关键字用于修饰变量(自动存储的局部变量)。 在C11中&#xff0c;赋予了auto 全新的含义&#xff0c;不再用于修饰的变量&#xff0c;而是作为一个类型指示符&#xff0c;指示编译器在编译时推导auto声明的变量的数据类型。 在…

SpirngBoot<读完包你更上一层楼>

目录 一、SpringBoot概念 1.1 什么是SpringBoot 1.2 为什么要学习SpringBoot 1.3 SpringBoot的特点 1.4 总结 二、入门案例 2.1 创建工程 2.1.1 创建一个空工程 2.1.2 工程名为project_test&#xff1a; 2.1.3 设置jdk版本为1.8 2.1.4 新建一个module 2.1.5 填写项…

入职字节外包一个月,我离职了

有一种打工人的羡慕&#xff0c;叫做“大厂”。 真是年少不知大厂香&#xff0c;错把青春插稻秧。 但是&#xff0c;在深圳有一群比大厂员工更庞大的群体&#xff0c;他们顶着大厂的“名”&#xff0c;做着大厂的工作&#xff0c;还可以享受大厂的伙食&#xff0c;却没有大厂…

[附源码]计算机毕业设计springboot吾悦商城管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

老司机带带你,教你学会Java中又骚又暴力的“反射”技术

在Java中有这么一个很骚的技术&#xff0c;几乎贯穿了所有主流的框架&#xff0c;在所有主流框架的底层中你都可以看见它的身影&#xff0c;这个技术就是反射。关于反射&#xff0c;有很多小白会觉得很难&#xff0c;搞不清楚到底是怎么回事&#xff0c;也不知道该怎么用&#…

VS Code快速实现Git PR操作

注意&#xff1a;建议先学习git的基本操作。 安装插件 下图中红圈标记的插件都安装好。 Fork上游仓库 在网页上点击你想要fork的仓库&#xff0c;点击fork 然后该仓库就会fork到你的github账户下面&#xff0c;如下图。 现在可以在你账户下面的repo&#xff08;我们称为下…

[附源码]Python计算机毕业设计Django和vue的茶文化交流平台的设计与实现

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Global Mapper 导出图层功能的妙用(重采样、设置文件类型、切片、按掩膜提取or裁剪……)

许多GIS软件都有导出的功能&#xff0c;但其中大部分的导出功能比较单一直接&#xff0c;仅仅是导出而已&#xff0c;或者最多可以改个导出的格式&#xff0c;改个坐标。但是Global Mapper 不一样&#xff0c;导出功能非常非常多&#xff0c;比如重采样&#xff08;可以设置重采…

Vue3框架的创建的两种种方案(第十二课)

1 VueCLi脚手架的安装 Home | Vue CLI (vuejs.org) 使用方法 | Yarn 中文文档 (bootcss.com) 3 Vite脚手架的安装 Vite | 下一代的前端工具链 4 使用的软件 Visual Studio Code webstorm64.exe IntelliJ IDEA 2022.2.3 HBuilder X 方案一 VueCLi脚手架的安装 1 创…

[附源码]计算机毕业设计在线招聘网站Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

MobileViT

还是vit系列啊 只不过这次是Apple团队出的轻量级、通用且移动友好的网络框架 论文地址&#xff1a;https://arxiv.org/pdf/2110.02178.pdf 轻量级卷积神经网络 (CNN) 是移动视觉任务的事实。他们的空间归纳偏差使他们能够在不同的视觉任务中以较少的参数学习表示。 轻量级卷积…

微服务自动化【集群搭建】

目录 搭建 etcd 集群 etcd构建自身高可用集群主要有三种形式: 1. 静态部署(前提) 2. 集群搭建 3. 集群测试 搭建 etcd 集群 etcd构建自身高可用集群主要有三种形式: 静态发现:预先已知etcd集群中有哪些节点&#xff0c; 在启动时通过--initial-cluster参数直接指定好etc…

[附源码]JAVA毕业设计互联网保险网站(系统+LW)

[附源码]JAVA毕业设计互联网保险网站&#xff08;系统LW&#xff09; 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&a…

JavaWeb(一)

前言 首先深入了解之前&#xff0c;先回顾一些基础知识 C/S & B/S 就比如咱们日常生活中&#xff0c;咱们说的CF是cs游戏&#xff0c;这个cs是什么意思&#xff08;年幼的我也十分痴迷CF游戏&#xff0c;过去式了 hhh&#xff09;这里的cs可不是咱们说的csgo或者cs游戏。…

Vue 官方文档2.x教程学习笔记 1 基础 1.4 模板语法 1.4.1 插值

Vue 官方文档2.x教程学习笔记 文章目录Vue 官方文档2.x教程学习笔记1 基础1.4 模板语法1.4.1 插值1 基础 1.4 模板语法 【介绍】 Vue.js 使用了基于 HTML 的模板语法&#xff0c;允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。 所有 Vue.js 的模板都是合法的 HTML&…

Flink SQL管理平台flink-streaming-platform-web安装搭建-整理

目录 步骤 安装 第二步 下载flink 第三步 安装flink-streaming-patform-web 第四步 配置flink web平台 第五步 运行demo 在Flink学习的入门阶段&#xff0c;非常重要的一个过程就是Flink环境搭建&#xff0c;这是认识FLInk框架的第一步&#xff0c;也是为后续的理论学习和…

全栈性能测试教程之性能测试理论(一) mockserver应用

1、mockServer 1.1什么是mockServer moco替代 Server服务 mocoServer即为测试替身的服务 主要针对于单元测试的应用&#xff0c;主要应用于解除单元测试之间的依赖 1.2mocoServer使用的场景 前端程序员 前端已经写好页面&#xff0c;但是后端的接口没有写好&#xff…