Java多线程<三>常见的多线程设计模式

多线程的设计模式

两阶段线程终止

image-20220522205120212

  • park方法

image-20220522205104932

  • interrupted() 会让他失效。

  • 使用volatile关键字进行改写

image-20220522210245081

单例模式 双锁检测

保护性暂停

image-20220529184223468

  • 实现1:
package threadBase.model;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理*/
public class GuardedObject {private Object response;// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}}return response;    // 这里自然释放锁}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}
}
  • 实现2:设置超时
package threadBase.model;import java.util.concurrent.ThreadLocalRandom;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理***/
public class GuardedObject {private Object response;public Object get(long timeout) {synchronized (this) {long begin = System.currentTimeMillis();long passedTime = 0;while(response == null) {long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒if (waitTime <= 0) {break;}try {this.wait(waitTime);} catch (Exception e) {e.printStackTrace();}System.out.println("被唤醒了");passedTime = System.currentTimeMillis() - begin;}return response;}}// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}return response;    // 这里自然释放锁}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}public static void main(String[] args) {GuardedObject go = new GuardedObject();new Thread(()-> {// 等待两秒Object response = go.get(2000);System.out.println("结果是" + response);}).start();new Thread(()-> {try {// 3s才进行返回Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}go.complete(new Object());}).start();// 虚假唤醒new Thread(()->{try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}go.complete(null);}).start();}
}
  • Join的实现原理

image-20220529191720553

弱鸡版本的生产者消费者

  • 保护性暂停协议的扩展。
  • 解决线程之间的同步问题。
package threadBase.model;import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadLocalRandom;/*** @author: Zekun Fu* @date: 2022/5/29 19:01* @Description:* 保护性暂停,* Future 中get方法的实现原理** 写信者和收信者1:1实现模型。* 写信的人和收信的人是一一对应的关系。* 两个线程通过一个唯一的id确定彼此的身份。** 1. 解耦了收信人线程和写信人线程* 2. 防止写信人线程用完对象不销毁。* 3. MailBoxes获取之后应该销毁对象。*** 多个线程之间速度不匹配,所以使用消息队列。也就是生产者消费者模型。* 这里的停止等待也可以看作是一种一对一的解决速度不匹配问题的机制。** 加上MailBox并没有改变这个的本质,只是方便编码了而已,就是把future* 放入了一个公共的消息队列,然后消费者进行取出。** 可以看作是弱鸡版的生产者消费者模型。* 具有缓解速度不匹配问题的机制,但是必须要实现一对一的模型。***/class MailBoxes {           // 消息队列机制private static Map<Integer, GuardedObject> boxes = new Hashtable<>();private static int id = 1;// 产生唯一的idprivate static synchronized int generateId() {return id++;}public static GuardedObject createGuardedObject() {GuardedObject go = new GuardedObject(generateId());boxes.put(go.getId(), go);return go;}public static Set<Integer> getIds() {return boxes.keySet();}public static GuardedObject getGuardedObject(int id) {return boxes.remove(id);        // 使用remove方法,防止内存泄漏}
}/*
*
*   消费者线程消费特定的future也就是GuardObject。
* */class ReadPeople extends Thread{    // 生产者线程@Overridepublic void run() {//  收信// 1. 创建GuardedObject guardedObject = MailBoxes.createGuardedObject();System.out.println(Thread.currentThread().getName() + "等待收信 id:" + guardedObject.getId());Object mail = guardedObject.get(5000);System.out.println(Thread.currentThread().getName() + "受到信:" + guardedObject.getId() + " 内容:" + mail);}
}/*
*
* 生产者线程,生产特定的Future
**/
class WriteMan extends Thread{          // 消费者线程private int id;private String mail;WriteMan(int id, String mail) {this.id = id;this.mail = mail;}@Overridepublic void run() {GuardedObject guardedObject = MailBoxes.getGuardedObject(id);System.out.println(Thread.currentThread().getName() + "写信 id = " + id +" 内容:" + mail);guardedObject.complete(mail);}
}public class GuardedObject {        // future任务机制private int id;private Object response;public GuardedObject(int id) {this.id = id;}public int getId() {return id;}public Object get(long timeout) {synchronized (this) {long begin = System.currentTimeMillis();long passedTime = 0;while(response == null) {long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒if (waitTime <= 0) {break;}try {
//                    System.out.println("等待中..");  // 如果被虚假唤醒this.wait(waitTime);} catch (Exception e) {e.printStackTrace();}
//                System.out.println("被唤醒了"); // 如果被虚假唤醒passedTime = System.currentTimeMillis() - begin;}return response;}}// 获取结果public Object get() {synchronized (this) {// 等待结果while (response == null) {try {this.wait();} catch (Exception e) {e.printStackTrace();}}return response;    // 这里自然释放锁}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}public static void main(String[] args) throws InterruptedException {
//        GuardedObject go = new GuardedObject(1);
//        new Thread(()-> {
//            // 等待两秒
//            Object response = go.get(2000);
//            System.out.println("结果是" + response);
//        }).start();
//
//        new Thread(()-> {
//            try {
//                // 3s才进行返回
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(new Object());
//        }).start();
//
//        // 虚假唤醒
//        new Thread(()->{
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(null);
//
//        }).start();// 模拟写信和收信的过程// 1. 三个收信的人 (消费者)for (int i = 0; i < 3; i++) {ReadPeople p = new ReadPeople();p.start();}// 2. 三个写信人 (必须对应三个写信者)Thread.sleep(1000);for (int idx: MailBoxes.getIds()) {new WriteMan(idx, "内容" + idx).start();}}
}

生产者消费者模型(终止协议修改)

1. 手动枷锁实现

  • 首先枷锁中的wait应该使用while循环
  • 其次MsgQue应该抛出异常,而不是捕捉。这样终止线程的决定权就在线程那里,而不是必须等消费完最后一个才进行。
  • 而放入队列的异常可以放在MsgQue或者消费者线程中,因为不管怎么样,生产的都需要放入队列中。
package threadBase.model;import java.util.LinkedList;
import java.util.List;
import java.util.Queue;/*** @author: Zekun Fu* @date: 2022/5/31 15:48* @Description: 生产者消费者,使用自己加锁实现*/
public class MessageQue {LinkedList<Message>que = new LinkedList<>();int capcity;public MessageQue(int capcity) {this.capcity = capcity;}public void put(Message x) throws InterruptedException{synchronized (que) {while (que.size() >= capcity) {System.out.println("队列已满," + Thread.currentThread().getName() + "等待");que.wait();}que.addLast(x);         // 尾部添加que.notifyAll();}}public Message get() throws InterruptedException{synchronized (que) {while (que.size() == 0) {que.wait();}Message msg = que.removeFirst();        // 头部取出que.notifyAll();return msg;}}public static void main(String[] args) throws InterruptedException {MessageQue mq = new MessageQue(2);for (int i = 0; i < 3; i++) {int idx = i;new Thread(()->{System.out.println("生产者线程" + idx + "生产完成");try {mq.put(new Message("消息" + idx));} catch (InterruptedException e) {e.printStackTrace();}},"生产者线程" + i).start();}Thread t = new Thread(()-> {Thread cur = Thread.currentThread();while (true) {if (cur.isInterrupted()) {break;}try {Thread.sleep(1000);     // 每隔1s消费一次Message msg = mq.get();System.out.println("消费" + msg.getMsg());} catch (InterruptedException e) {cur.interrupt();System.out.println("停止消费");}}}, "消费者线程");t.start();Thread.sleep(4000);t.interrupt();}
}class Message {private String msg;public Message(String mgs) {this.msg = mgs;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}
}

2. 使用Semphore实现

package threadBase.model;import java.util.LinkedList;
import java.util.concurrent.Semaphore;/*** @author: Zekun Fu* @date: 2022/5/31 16:30* @Description: 使用Semaphore实现****/
public class MessageQue2 implements MesQ{private Semaphore mutex = new Semaphore(1);private Semaphore full;private Semaphore empty;LinkedList<Message>list = new LinkedList<>();public MessageQue2(int capcity) {full = new Semaphore(capcity);empty = new Semaphore(0);}@Overridepublic void put(Message msg) throws InterruptedException {full.acquire();mutex.acquire();list.addLast(msg);mutex.release();empty.release();}@Overridepublic Message get() throws InterruptedException {empty.acquire();mutex.acquire();Message ans = list.removeFirst();mutex.release();full.release();return ans;}public static void main(String[] args) {Test.testMesQ(new MessageQue2(2));}
}

哲学家进餐问题

  • 参考博客

哲学家进餐问题:

  1. 哲学家进餐,不应该有阻塞的线程,因为线程阻塞的条件是等待其他线程的完成通知。

1. 锁住房子破坏了请求保持

请求与保持,就是线程在运行期间,拥有一部分资源,但是仍旧需要更多的资源才能继续运行。

简单的办法,就是采用静态分配的方式,首先把所有的资源都分配好,程序就不会在进行请求了。

简单办法2,就是只有当能够同时拿到两双筷子的时候,才进行分配,如果没有同时拿到两双筷子,就直接放下。

缺点就是:如果程序运行时间很长,而某些资源虽然用的很快就用完了,但是也得等到程序运行完成之后才能进行释放。导致资源利用效率很低。同时也会导致某些进程的饥饿。

第二种的缺点很明显,拿起筷子和放下筷子都是需要消耗cpu和存储资源的,如果拿起放下时间很长,那么就会导致性能低下,资源利用效率低,同时有可能导致活锁问题。

Java中可以使用Mutex加上Synchornized进行资源的静态分配。也就是先设置一个房间。只允许其中的一部分人进去。

img

import java.util.concurrent.Semaphore;public class MealOfPhilosopher {static Semaphore[] chopsticks = new Semaphore[5];static Semaphore mutex = new Semaphore(1);static {for (int i = 0; i < 5; i++) {chopsticks[i] = new Semaphore(1);}}public static void main(String[] args) {for (int i = 0; i < 5; i++) {int j = i;new Thread(()->{while (true) {try {mutex.acquire();chopsticks[j].acquire();System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");chopsticks[(j + 1) % 5].acquire();System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");mutex.release();System.out.println(Thread.currentThread().getName() + "正在吃饭。");chopsticks[j].release();System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");chopsticks[(j + 1) % 5].release();System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");System.out.println(Thread.currentThread().getName() + "正在思考。");} catch (InterruptedException e) {e.printStackTrace();}}}, "哲学家" + i + "号").start();}}}

2. 规定顺序破坏了循环等待

方法是,申请资源一定要按照某种顺序进行。比如设备的id进行从小到达的申请这种。

缺点是,资源的扩展性不好,如果新来了资源,上面的申请逻辑就需要改变。同时因为线程申请资源和使用资源的顺序可能不一致,从而导致请求到的资源无法投入使用的情况,从而导致资源的利用效率低

实现方法1:奇数的只能拿左手边的,偶数的只能拿右手边的

实现方法2:

img

3. 尝试上锁破坏了不可剥夺

img

缺点是,代价很大,可能线程已经运行了一半了,又得重新运行。

实现就是,使用tryAquire的方式获取锁。而不是aquire的方式。

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;public class MealOfPhilosopher {static final Semaphore[] chopsticks = new Semaphore[5];static final Semaphore mutex = new Semaphore(1);static {for (int i = 0; i < 5; i++) {chopsticks[i] = new Semaphore(1);}}public static void main(String[] args) {for (int i = 0; i < 5; i++) {int j = i;new Thread(()->{while (true) {try {System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");if (!chopsticks[j].tryAcquire(10, TimeUnit.SECONDS))System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他左边的" + j + "号筷子");System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");if (!chopsticks[(j + 1) % 5].tryAcquire(10, TimeUnit.SECONDS))System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他右边的" + (j + 1) % 5 + "号筷子");System.out.println(Thread.currentThread().getName() + "正在吃饭。");System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");chopsticks[j].release();System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");chopsticks[(j + 1) % 5].release();System.out.println(Thread.currentThread().getName() + "正在思考。");} catch (InterruptedException e) {e.printStackTrace();}}}, "哲学家" + i + "号").start();}}}
  • 实现而使用Reentrantlock
package threadBase.model;import leetcode.Philosophiers;import java.util.TreeMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;/*** @author: Zekun Fu* @date: 2022/6/1 14:59* @Description: 哲学家进餐问题,* 破坏不可剥夺 -- 使用tryLock* 破坏请求保持 -- 静态分配* 破坏循环等待 -- 规定顺序。可能会导致某一个线程饥饿。*      因为有的线程和两个人竞争,而有的线程在一个时刻只和一个人竞争**      实现细节:*      1. 筷子可以继承锁变量*      2. 可以使用Semphore实现*      3. 可以使用ReentrantLock 实现的可以说是无锁的,因为线程一直处于就绪和执行装态。**      4. 为什么不进入等待队列中呢?*          因为这不是一个同步问题,没有线程之间的协作,没有一个线程通知另外一个线程这种事情。*          也就是说,不会有人通知他醒过来。*          所以他需要不断的死循环去尝试,去抢筷子。**/class Chopstic extends ReentrantLock {}
public class Philosopher extends Thread {Chopstic left;Chopstic right;public Philosopher(Chopstic left, Chopstic right) {this.left = left;this.right = right;}@Overridepublic void run() {Thread t = Thread.currentThread();while(true) {if (t.isInterrupted()) {System.out.println(t.getName() + "嗝屁了");break;}if (left.tryLock()) {       // 如果拿到了左筷子try {if (right.tryLock()) {  // 尝试拿右筷子, 没拿到try {eat();} finally {right.unlock();     // 如果拿到了,吃饭,放下锁}}}finally {left.unlock();}}try {Thread.sleep(1000);} catch (InterruptedException e) {t.interrupt(); // 重新设置打断标记}}}public void eat() {Thread t = Thread.currentThread();System.out.println(t.getName() + "正在吃饭...");}public static void main(String[] args) throws InterruptedException {Chopstic[] chopstics = new Chopstic[5];for (int i = 0; i < 5; i++) chopstics[i] = new Chopstic();String[] names = {"阿基米德","柏拉图","牛顿","柯西","亚里士多德"};Philosopher[] ps = new Philosopher[5];for (int i = 0; i < 5; i++) {Philosopher p = new Philosopher(chopstics[i], chopstics[(i + 1) % 5]);p.setName(names[i]);p.start();ps[i] = p;}Thread.sleep(10000);for (int i = 0; i < 5; i++) {ps[i].interrupt();}}
}

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

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

相关文章

打砖块,Android休闲小游戏开发

A. 项目描述 《打砖块》是一款经典的休闲小游戏 &#xff0c;结合了经典的图形和音效&#xff0c;给玩家带来了轻松愉快的游戏体验。 该游戏操作简单易上手。玩家只需通过触摸屏幕控制底部的“拍子”左右移动&#xff0c;以反弹“小球” 击碎 顶部的砖块。玩家可以根据球的角度…

基于JAVA+SSM+VUE的前后端分离的大学竞赛管理系统

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着互联网技术的快速…

青龙面板的安装

一、安装docker 首先&#xff0c;需要在服务器上安装docker。 没有服务器的可以使用虚拟机&#xff0c;或申请一台三丰云的免费云服务器体验一下&#xff0c;独立IP地址&#xff0c;送免备案服务&#xff0c;可以满足基本的使用&#xff0c;三丰云上还有免费虚拟主机等其他免费…

ES6之解构赋值详解

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

Springboot整合MybatisPlus的基本CRUD

目录 前言1. 搭建项目2. 基本的CRUD 前言 发现项目框架是MybatisPlus的&#xff0c;由于个人使用该框架的CRUD比较少 对此学习过程中&#xff0c;从零到有开始搭建学习还是比较重要的&#xff0c;感悟会比较多 关于各个类的使用&#xff0c;可看如下文章&#xff1a; 剖析Ja…

Java—AOP案例-记录操作日志

简介&#xff1a;上一篇文章“JAVA语言—AOP基础”已经详细的介绍了AOP的各个功能接口&#xff0c;已经使用步骤&#xff0c;这篇文章就是基于此来做的一个小案例。案例的功能是记录登录的用户对于数据库表的相关信息进行增、删、查、改的操作记录下来&#xff0c;并且存储到数…

腾讯云轻量应用服务器详细介绍(全网超详细说明)

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别优惠&#xff0c;轻量2核2G3M带宽62元一年、2核2G4M优惠价118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;756元3年、4核8G12M带宽646元15个月等&#xf…

微信小程序开发系列-08自定义组件模版特性

微信小程序开发系列目录 《微信小程序开发系列-01创建一个最小的小程序项目》《微信小程序开发系列-02注册小程序》《微信小程序开发系列-03全局配置中的“window”和“tabBar”》《微信小程序开发系列-04获取用户图像和昵称》《微信小程序开发系列-05登录小程序》《微信小程序…

点成案例 | 如何利用细胞计数仪在单细胞测序中评估细胞

一、概述 单细胞测序技术能够用来表征异常细胞群&#xff0c;分析稀有细胞和细胞图谱网络&#xff0c;发现异质性等。由于单细胞测序巨大的应用潜力&#xff0c;目前此技术正在经历爆炸性增长。然而&#xff0c;单细胞测序需要成本和时间的大量投资。为了确保时间和资源的投资…

正确的认识 字节码文件

上一篇中认识了JVM的基本组成&#xff0c;我们说JVM只认识字节码文件。那么在字节码文件进入JVM之前&#xff0c;我们先认识了解字节码文件长什么样&#xff0c;我们作为工程师不需要去死扣底层的理论知识&#xff0c;但是我们只是需要正确的打开字节码文件 知道里面有哪些部分…

[Angular] 笔记 22:ElementRef

chatgpt: ElementRef 是 Angular 中的一个类&#xff0c;它用于包装对 DOM 元素的引用。它允许开发者直接访问与 Angular 组件关联的宿主 DOM 元素。 当在 Angular 中需要直接操作 DOM 元素时&#xff0c;可以使用 ElementRef。通常情况下&#xff0c;最好避免直接操作 DOM&a…

Prism介绍

Prism介绍 Prism是一个框架&#xff0c;用于在WPF、Xamarin Forms、Uno Platform和WinUI中构建松散耦合、可维护和可测试的XAML应用程序。 设计目标 为了实现下列目的&#xff1a; 创建能够由模块组成的程序&#xff0c;这些模块能够被单独地编写、组装、部署&#xff0c;并且对…

十三:爬虫-Scrapy框架(下)

一&#xff1a;各文件的使用回顾 1.items的使用 items 文件主要用于定义储存爬取到的数据的数据结构&#xff0c;方便在爬虫和 Item Pipeline 之间传递数据。 items.pyimport scrapyclass TencentItem(scrapy.Item):# define the fields for your item here like:title scr…

jmeter函数助手-常用汇总

一.函数助手介绍 1.介绍及作用 介绍&#xff1a; jmeter自带的一个特性&#xff0c;可以通过指定的函数规则创建后进行调用该函数&#xff0c;在后续接口请求参数中进行调用 作用 &#xff08;1&#xff09;做参数化。 2.如何使用 jmeter工具栏-->工具-->函数助手…

LabVIEW在大型风电机组状态监测系统开发中的应用

LabVIEW在大型风电机组状态监测系统开发中的应用 风电作为一种清洁能源&#xff0c;近年来在全球范围内得到了广泛研究和开发。特别是大型风力发电机组&#xff0c;由于其常常位于边远地区如近海、戈壁、草原等&#xff0c;面临着恶劣自然环境和复杂设备运维挑战。为了提高风电…

DockerCompose - 容器编排、模板命令、compose命令、Pottainer 可视化界面管理(一文通关)

目录 一、DockerCompose 容器编排 1.1、简介 1.2、Docker-Compose 安装 1.2.1、在线安装 1.2.2、离线安装 1.3、docker-compose.yml 中的模板命令 前置说明 模板命令 1.4、DockerCompse 命令 前置说明 up down exec ps restart rm top pause暂停 和 unpause恢…

linux下的进程布局与ububtu操作系统下的proc文件夹学习笔记一

相关内容我写在公众号&#xff0c;写的挺详细的&#xff0c;欢迎关注我的公众号。请使用鼠标右键&#xff0c;新建标签页打开&#xff0c;直接点击显示参数错误&#xff0c;不知道怎么回事&#xff1f;linux下的进程布局与ububtu操作系统下的proc文件夹学习笔记 (qq.com)https:…

Windows下配置GCC(MinGW)环境

一、下载并安装MinGW 步骤1&#xff1a;下载MinGW安装器 前往MinGW的官方下载源&#xff0c;通过以下链接可以获取到最新版的MinGW安装程序&#xff1a; 网页地址&#xff1a;https://sourceforge.net/projects/mingw/files/ [MinGW 下载地址](https://sourceforge.net/proj…

二级路由的配置以及注意项

二级路由 比如说LayOut组件是父亲&#xff0c;LayOut和ArtComp是儿子&#xff0c;那我们怎么给儿子配路由呢&#xff1f; 1、首先在router下的index.js导入组件&#xff0c;配置规则&#xff0c;详细如下 // 导入路由相关组件 import LayOut from /views/LayOut import UserC…

页面布局--Flexbox的自动边距

标题页面布局–Flexbox的自动边距 通过简单的margin:auto&#xff0c;我们就能实现元素的多种对齐方式。 假设我们在盒子模型里有四个元素&#xff1a; 先给容器使用flex布局&#xff1a; .container {display: flex;justify-content: flex-start;align-items: center;gap: 6…