JAVA 异步编程(异步,线程,线程池)一

目录

1.概念

1.1 线程和进程的区别

1.2 线程的五种状态

1.3 单线程,多线程,线程池

1.4 异步与多线程的概念

2. 实现异步的方式

2.1 方式1 裸线程(Thread)

2.1 方式2 线程池(Executor)

2.1.1 源码分析

2.1.2  线程池创建(Executors)

 2.1.3 阻塞主线程获取子线程返回值

1.线程池awaitTermination轮询

2.线程池future.get

3. CountDownLatch类的await(推荐)

4.线程池invokeAll 

5. ExecutorCompletionService(强烈推荐)

2.1.4 非阻塞主线程获取子线程返回值

1. Future接口:

2. CompletableFuture类:

2.3 方式3 ForkJoinPool

2.4 方式4 Spring的Async注解

4. 总结:


1.概念

1.1 线程和进程的区别

        进程:是一个动态的过程,是一个活动的实体,简单来说,一个应用程序的运行就可以看作是一个进程。可以说,进程中包含了多个可以同时运行的线程。

线程(Thread):是运行中实际任务的执行者,线程是操作系统能够进行运算调度的最小单位。它被包装在进程中,是进程中的实际运行单位。每个线程都有自己的程序计数器、堆栈和局部变量,但它们共享进程的代码和内存。

1.2 线程的五种状态

  1. 新建(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new Thread()

  2. 就绪(Runnable):也被称为“可执行”状态,当线程对象调用start()方法(启动线程)后,线程即进入就绪状态。处于就绪状态的线程,只是说明了该线程可以运行,但还没有真正运行,等待CPU分配时间片。

  3. 运行(Running):当CPU开始调度处于就绪状态的线程时,线程进入运行状态,真正开始执行线程代码。

  4. 阻塞(Blocked):线程在运行过程中可能因为各种原因导致无法继续执行,比如等待I/O操作结果,或者尝试获得一个同步监视器而失败,这时它就会进入阻塞状态。

  5. 死亡(Dead):线程执行完毕或者因异常退出run()方法后,线程就进入死亡状态。

1.3 单线程,多线程,线程池

单线程: 顾名思义是只有一条线程在执行任务,在工作中很难遇到。

多线程: 是创建多条线程同时执行任务。

线程池(ThreadPool): 是一种管理线程的机制,它能够复用线程,避免因频繁创建和销毁线程导致的性能问题。通过设置线程池的大小,可以有效管理线程的运行。

1.4 异步与多线程的概念

        异步和多线程并不是同一关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是调用者发送一个请求给被调用者,而调用者不用等待请求结果的返回,可以去做其他事。实现异步可以使用多线程或交给其他进程来处理。

2. 实现异步的方式

Util类:

public class Util {

    /**
     * sleep
     *
     * @param milliseconds
     */
    public static void mySleep(int milliseconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(milliseconds);
        } catch (InterruptedException e) {

        }
    }

    /**
     * print log
     * @param message
     */
    public static void printfLog(String message) {
        LocalDateTime localDateTime = LocalDateTime.now();
        String dateString = localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
        System.out.println(String.format("%s - %s", dateString, message));
    }
}

2.1 方式1 裸线程(Thread)

        使用“原汁原味”的裸线程(Thread)。Java线程本质上被映射到操作系统线程,并且每个线程对象对应着一个计算机底层线程。

JVM管理着线程的生存期,而且只要你不需要线程间通讯,你也不需要关注线程调度。

每个线程有自己的栈空间,它占用了JVM进程空间的指定一部分。

线程的接口相当简明,你只需要提供一个Runnable,调用.start()开始计算。没有现成的API来结束线程,你需要自己来实现,通过类似boolean类型的标记来通讯。

    private static void threadMethod(List<Integer> list) {
        while (list.get(0) > 0) {
            Util.mySleep(Double.valueOf(Math.random() * 100).intValue());
            Integer result = list.get(0);
            result--;
            list.add(0, result);
        }
    }
    public static void threadTest() {
        Util.printfLog("主线程开始");
        //线程外的变量只读,值类型只能显示的生命final
        //这里正常应该会输出list result=0,但是这里因为时多个线程同时操作一个变量导致线程不安全,输出list result为负值
        List<Integer> list = new ArrayList<>(1);
        list.add(100);
        for (int i = 0; i < 100; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    threadMethod(list);
                    Util.printfLog("子线程结束!currentName = " + Thread.currentThread().getName());
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
        }
        Util.printfLog("主线程结束");
        Util.mySleep(10000);
        Util.printfLog("list result=" + list.get(0));
    }

这里暂时没考虑线程安全行,所以list result可能会出现负值。

2.1 方式2 线程池(Executor)

       2.1.1 源码分析

 Executor一个接口,它通过一系列抽象类,接口等最终生成了线程池ThreadPoolExecutor。

它的包在java.util.concurrent。线程池的底层实现也是通过一系列的操作,通过Thread创建单独的线程。

ExecutorService.submit->AbstractExecutorService.submit->ThreadPoolExecutor.execute->
ThreadPoolExecutor.addWorker->Worker构造函数->DefaultThreadFactory.newThread

也可以使用工具类Executors创建线程池,底层也是通过创建ThreadPoolExecutor创建线程池。

ThreadPoolExecutor的主要方法:

方法名描述
submit

Callable<T>的实现类,创建带返回值的线程

Runnable的实现类,创建不带返回值的线程

shutdown优雅的终止线程。线程池中的线程不会立即结束,等线程池中没有在运行的线程才终止后台的线程池。
shutdownNow直接终止线程。不管线程池中有没有在运行的线程,直接将后台的线程池终止。

2.1.2  线程池创建(Executors)

线程池Executors工具类的主要方法:

方法名描述
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。

ScheduledExecutorService的方法:

        schedule:定时到达时间间隔执行。
        scheduleAtFixedRate:周期,从上一个任务开始运行就开始计时
        scheduleWithFixedDelay:周期,从上一次任务运行结束开始计时

    public static void main(String[] args) throws IOException {
        System.out.println("主线程【开始】");
        executorsNoWait();
        System.out.println("主线程【结束】");
        System.in.read();
}    

/**
     * 线程直接提交,提交完成之后直接直接回到主进程
     */
    private static void executorsNoWait() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.submit(() -> {
            threadMethod(5, "线程--1");
            return "第一线程";
        });
        executorService.submit(() -> {
            threadMethod(2, "线程--2");
            return "第二线程";
        });
        // 线程池没shutdwon,后台运行
        executorService.shutdown();
    }


private static void threadMethod(int num, String name) {
        for (int i = 0; i < num; i++) {
            Util.mySleep(1000);
            Util.printfLog(String.format("%s", name));
        }
    }

这种方式创建只提交线程到线程池,不阻塞主线程,不获取线程的返回值。通过代码运行结果,主线程已经执行完成,但是线程池中的子线程还没结束所以没有获取结果就不会阻塞主线程的运行。 只有当线程池中的没有runing的线程,线程池才会shutdown。

 2.1.3 阻塞主线程获取子线程返回值

        这种方式就是主线程提交子线程后,子线程异步运行。然后通过阻塞主线程,等待子线程都运行结束,再获取子线程的结果

1.线程池awaitTermination轮询

这种方式只能通过future.get方式获取返回值,只能等全部子线程执行完成才能获取,通过future.get返回值。

  /**
     * 线程池等待,阻塞回到主进程--awaitTermination轮训
     */
    private static void executorsAwaitTermination() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future<String> future = executorService.submit(() -> {
            threadMethod(5, "线程--1");
            return "第一线程";
        });
        Future<String> future2 = executorService.submit(() -> {
            threadMethod(2, "线程--2");
            return "第二线程";
        });
        executorService.shutdown();
        try {
            while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
                Util.printfLog("等待中");
            }
        } catch (Exception e) {
        }
    }

2.线程池future.get

直接使用future.get获取子线程的返回值,通过遍历集合中的future,以固定的顺序获取子线程返回值,只有当获取的子线程有返回值之后才继续循环获取下一个子线程的返回值。

 /**
     * 线程池等待,阻塞回到主进程--Future.get()
     */
    private static void executorsFutureGet() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Future<String>> listTemp = new ArrayList<>();
        Future<String> future = executorService.submit(() -> {
            threadMethod(5, "线程--1");
            return "第一线程";
        });
        listTemp.add(future);
        Future<String> future2 = executorService.submit(() -> {
            threadMethod(2, "线程--2");
            return "第二线程";
        });
        listTemp.add(future2);
        for (Future<String> item : listTemp) {
            try {
                System.out.println(item.get());
            } catch (Exception e) {
            }
        }
        executorService.shutdown();
    }

3. CountDownLatch类的await(推荐)

通过await方法阻塞主线程等待全部子线程执行完成后,通过future.get获取子线程的返回值,这种方式比方式一更加优雅。

    /**
     * 线程池等待,阻塞回到主进程--CountDownLatch
     */
    private static void executorsCountDownLatch() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CountDownLatch countDownLatch = new CountDownLatch(2);
        Future<String> future = executorService.submit(() -> {
            threadMethod(5, "线程--1");
            countDownLatch.countDown();
            return "第一线程";
        });
        Future<String> future2 = executorService.submit(() -> {
            threadMethod(2, "线程--2");
            countDownLatch.countDown();
            return "第二线程";
        });
        executorService.shutdown();
        try {
            countDownLatch.await();
            Util.printfLog(future2.get());
            Util.printfLog(future.get());
        } catch (Exception e) {

        }
    }

4.线程池invokeAll 

这种方式和方式二类似,也是通过future.get获取子线程返回值,只能通过遍历集合中的future,以固定的顺序获取子线程返回值。

/**
     * 线程池等待,阻塞回到主进程---使用invokeAll
     */
    private static void executorsInvokeAll() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Callable<String> callable = () -> {
            threadMethod(5, "线程--1");
            return "第一线程";
        };
        Callable<String> callable2 = () -> {
            threadMethod(2, "线程--2");
            return "第二线程";
        };
        List<Callable<String>> list = Arrays.asList(callable, callable2);
        try {
            List<Future<String>> futureList = executorService.invokeAll(list);
            executorService.shutdown();
            Util.printfLog("获取结果中---");
            for (Future<String> item : futureList) {
                Util.printfLog(item.get());
            }
        } catch (Exception e) {
        }
    }

5. ExecutorCompletionService(强烈推荐)
  • 优雅的获取子线程返回值,只要任何子线程结束就有返回值。
  • ExecutorCompletionService内部管理者一个已完成任务的阻塞队列
  • ExecutorCompletionService引用了一个Executor, 用来执行任务
  • submit()方法最终会委托给内部的executor去执行任务
  • take/poll方法的工作都委托给内部的已完成任务阻塞队列
  • 如果阻塞队列中有已完成的任务, take方法就返回任务的结果, 否则阻塞等待任务完成。
/**
     * 线程池等待,阻塞回到主进程---使用ExecutorCompletionService
     */
    private static void executorsExecutorCompletionService() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        ExecutorCompletionService<String> executorCompletionService = new ExecutorCompletionService(executorService);

        executorCompletionService.submit(() -> {
            threadMethod(5, "线程--1");
            return "第一线程返回";
        });
        executorCompletionService.submit(() -> {
            threadMethod(2, "线程--2");
            return "第二线程返回";
        });
        executorService.shutdown();
        try {
            Util.printfLog(executorCompletionService.take().get());
            Util.printfLog(executorCompletionService.take().get());
        } catch (Exception e) {
        }

    }

2.1.4 非阻塞主线程获取子线程返回值

1. Future接口:

        JDK 5引入了Future模式。Future接口是Java多线程Future模式的实现,在java.util.concurrent包中,可以来进行异步计算。Future模式是多线程设计常用的一种设计模式。
        Future虽然可以实现获取异步执行结果的需求,但它没有提供通知的机制,我们无法得知Future什么时候完成,不是真正意义上的异步。
        使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方式都会使主线程也会被迫等待,耗费CPU的资源。

private static void futureGet() {
        try {
            ExecutorService executor = Executors.newFixedThreadPool(1);
            Future<Integer> future = executor.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {

                    Util.printfLog("===task start===");
                    Util.mySleep(5000);
                    Util.printfLog("===task finish===");
                    return 3;
                }
            });
            executor.shutdown();
            //这里需要返回值时会阻塞主线程
            Integer result = future.get();
            Util.printfLog("线程返回值:" + result);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

 

2. CompletableFuture类:

        CompletableFuture实现异步操作,加上对lambda的支持,可以说实现异步任务已经发挥到了极致。CompletableFuture弥补了Future模式的缺点。在异步的任务完成后,需要用其结果继续操作时,无需等待。可以直接通过thenAccept、thenApply、thenCompose等方式将前面异步处理的结果交给另外一个异步事件处理线程来处理。

CompletableFuture的静态工厂方法:

方法名描述
runAsync(Runnable runnable)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作无返回值
runAsync(Runnable runnable, Executor executor)使用指定的thread pool执行异步代码,异步操作无返回值
supplyAsync(Supplier<U> supplier)使用ForkJoinPool.commonPool()作为它的线程池执行异步代码,异步操作有返回值
supplyAsync(Supplier<U> supplier, Executor executor)使用指定的thread pool执行异步代码,异步操作有返回值
 /**
     * 线程提交后,不阻塞主进程,
     * completableFuture子线程执行完后回调--thread pool执行异步代码
     */
    private static void completableFutureThreadPool() {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            threadMethod(5, "线程--1");
            return "第一线程";
        }, executorService);
        CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            threadMethod(2, "线程--2");
            return "第二线程";
        }, executorService);
        executorService.shutdown();
        completableFuture.thenAccept((r) -> {
            Util.printfLog(r);
        });
        completableFuture2.thenAccept((r) -> {
            Util.printfLog(r);
        });
    }

2.3 方式3 ForkJoinPool

        Java 8中加入了并行流,从此我们有了一个并行处理集合的简单方法。它和lambda一起,构成了并发计算的一个强大工具。默认情况下是通过ForkJoinPool.commonPool()实现并行的。这个通用池由JVM来管理,并且被JVM进程内的所有线程共享。

    /**
     * 使用ForkJoinPool,异步发处理
     */
    private static void forkJoinPool() {
        List<Supplier<String>> actionList = Arrays.asList(() -> {
                    threadMethod(5, "线程--1");
                    return "第一线程";
                },
                () -> {
                    threadMethod(2, "线程--2");
                    return "线程2返回";
                });
        List<String> threadResult = actionList.parallelStream().map(row -> row.get()).collect(Collectors.toList());
        Util.printfLog(threadResult.stream().collect(Collectors.joining(",")));
    }

2.4 方式4 Spring的Async注解

spring实现异步需要开启注解@EnableAsync,可以使用xml方式或者java code config的方式。
 (1)@Async 异步的方法

4. 总结:

        虽然Thread可以创建线程,但是线程的创建销毁不能很好的控制,就会导致资源耗尽的风险,所以线程资源尽量通过线程池提供,不在应用中自行显示的创建线程,一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。

        线程池的创建尽量不使用Executors,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险。

参考:

Java 并发的四种风味:Thread、Executor、ForkJoin 和 Actor

java多线程并发之旅-28-Executor CompletionService ExecutorCompletionService 详解

Java8新的异步编程方式 CompletableFuture

java线程池ThreadPoolExecutor类使用详解

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新的“SCALE”软件允许为 AMD GPU 原生编译 CUDA 应用程序

虽然已经有各种努力&#xff0c;如HIPIFY来帮助将CUDA源代码转换为AMD GPU的可移植C代码&#xff0c;然后是之前AMD资助的ZLUDA&#xff0c;允许CUDA二进制文件通过CUDA库的直接替代品在AMD GPU上运行&#xff0c;但有一个新的竞争者&#xff1a;SCALE。SCALE现在作为GPGPU工具…

超算网络体系架构-资源层-平台层-服务层-应用层

目录 超算网络体系架构 我国超算基础设施 超算互联网相关标准研制方面 技术架构 资源层 基础资源 芯片多样 体系异构 高效存储 高速互连 资源池化 可隔离 可计量 互联网络 高带宽 低时延 高安全 平台层 算力接入 资源管理 算力调度 用户管理 交易管理 模…

基于springboot和mybatis的RealWorld后端项目实战二之实现tag接口

修改pom.xml 新增tag数据表 SET FOREIGN_KEY_CHECKS0;-- ---------------------------- -- Table structure for tags -- ---------------------------- DROP TABLE IF EXISTS tags; CREATE TABLE tags (id bigint(20) NOT NULL AUTO_INCREMENT,name varchar(255) NOT NULL,PR…

VBA学习(21):遍历文件夹(和子文件夹)中的文件

很多时候&#xff0c;我们都想要遍历文件夹中的每个文件&#xff0c;例如在工作表中列出所有文件名、对每个文件进行修改。VBA给我们提供了一些方式&#xff1a;&#xff08;1&#xff09;Dir函数&#xff1b;&#xff08;2&#xff09;File System Object。 使用Dir函数 Dir…

2024年大数据高频面试题(中篇)

文章目录 Kafka为什么要用消息队列为什么选择了kafkakafka的组件与作用(架构)kafka为什么要分区Kafka生产者分区策略kafka的数据可靠性怎么保证ack应答机制(可问:造成数据重复和丢失的相关问题)副本数据同步策略ISRkafka的副本机制kafka的消费分区分配策略Range分区分配策略…

三级域名能申请SSL证书吗?

在当今互联网时代&#xff0c;SSL证书已经成为了保障网站安全的重要工具&#xff0c;企业会为网站部署SSL证书来实现HTTPS加密以保护传输数据安全。然而随着业务的增长以及交易规模的扩大&#xff0c;为了更好的管理业务和内容&#xff0c;企业会在主域名的基础上划分二级域名&…

GitHub 令牌泄漏, Python 核心资源库面临潜在攻击

TheHackerNews网站消息&#xff0c;软件供应链安全公司 JFrog 的网络安全研究人员称&#xff0c;他们发现了一个意外泄露的 GitHub 令牌&#xff0c;可授予 Python 语言 GitHub 存储库、Python 软件包索引&#xff08;PyPI&#xff09;和 Python 软件基金会&#xff08;PSF&…

【RabbitMQ】一文详解消息可靠性

目录&#xff1a; 1.前言 2.生产者 3.数据持久化 4.消费者 5.死信队列 1.前言 RabbitMQ 是一款高性能、高可靠性的消息中间件&#xff0c;广泛应用于分布式系统中。它允许系统中的各个模块进行异步通信&#xff0c;提供了高度的灵活性和可伸缩性。然而&#xff0c;这种通…

网络准入控制设备是什么?有哪些?网络准入设备臻品优选

小李&#xff1a;“小张&#xff0c;最近公司网络频繁遭遇外部攻击&#xff0c;我们得加强一下网络安全了。” 小张&#xff1a;“是啊&#xff0c;我听说实施网络准入控制是个不错的选择。但具体什么是网络准入控制设备&#xff1f;我们有哪些选择呢&#xff1f;” 小李微笑…

2024Datawhale AI夏令营---Inclusion・The Global Multimedia Deepfake Detection--学习笔记

赛题背景&#xff1a; 其实总结起来就是一句话&#xff0c;这个项目是基于目前的深度伪装技术&#xff0c;就是通过大量人脸的原数据集进行模型训练之后&#xff0c;能够生成伪造的人脸视频。这项目就是教我们如何去实现这个DeepFake技术。 Task1:了解Deepfake和跑通baseline …

Python项目部署到Linux生产环境(uwsgi+python+flask+nginx服务器)

1.安装python 我这里是3.9.5版本 安装依赖&#xff1a; yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make -y 根据自己的需要下载对应的python版本&#xff1a; cd local wget https://www.python.org/ftp…

开发实战经验分享:互联网医院系统源码与在线问诊APP搭建

作为一名软件开发者&#xff0c;笔者有幸参与了多个互联网医院系统的开发项目&#xff0c;并在此过程中积累了丰富的实战经验。本文将结合我的开发经验&#xff0c;分享互联网医院系统源码的设计与在线问诊APP的搭建过程。 一、需求分析 在开发任何系统之前&#xff0c;首先要…

成像光谱遥感技术中的AI革命:ChatGPT

遥感技术主要通过卫星和飞机从远处观察和测量我们的环境&#xff0c;是理解和监测地球物理、化学和生物系统的基石。ChatGPT是由OpenAI开发的最先进的语言模型&#xff0c;在理解和生成人类语言方面表现出了非凡的能力&#xff0c;ChatGPT在遥感中的应用&#xff0c;人工智能在…

【STM32】RTT-Studio中HAL库开发教程三:IIC通信--AHT20

文章目录 一、I2C总线通信协议二、AHT20传感器介绍三、STM32CubeMX配置硬件IIC四、RTT中初始化配置五、具体实现代码六、实验现象 一、I2C总线通信协议 使用奥松的AHT20温湿度传感器&#xff0c;对环境温湿度进行采集。AHT20采用的是IIC进行通信&#xff0c;可以使用硬件IIC或…

2. KNN分类算法与鸢尾花分类任务

鸢尾花分类任务 1. 鸢尾花分类步骤1.1 分析问题&#xff0c;搞定输入和输出1.2 每个类别各采集50朵花1.3 选择一种算法&#xff0c;完成输入到输出的映射1.4 第四步&#xff1a;部署&#xff0c;集成 2. KNN算法原理2.1 基本概念2.2 核心理念2.3 训练2.4 推理流程 3. 使用 skle…

Word参考文献交叉引用

前言 Word自带交叉引用功能&#xff0c;可在正文位置引用文档内自动编号的段落&#xff0c;同时创建超链接&#xff0c;适用于参考文献的引用。使用此方法对参考文献进行引用后&#xff0c;当参考文献的编号发生变化时&#xff0c;只需要更新域即可与正文中的引用相对应。下文…

vue3+TS从0到1手撸后台管理系统

1.路由配置 1.1路由组件的雏形 src\views\home\index.vue&#xff08;以home组件为例&#xff09; 1.2路由配置 1.2.1路由index文件 src\router\index.ts //通过vue-router插件实现模板路由配置 import { createRouter, createWebHashHistory } from vue-router import …

【15】Android基础知识之Window(一)

概述 这篇文章纠结了很久&#xff0c;在想需要怎么写&#xff1f;因为window有关的篇幅&#xff0c;如果需要讲起来那可太多了。从层级&#xff0c;或是从关联&#xff0c;总之不是很好开口。这次也下定决心&#xff0c;决定从浅入深的讲讲window这个东西。 Window Window是…

鸿蒙特色物联网实训室

一、 引言 在当今这个万物皆可连网的时代&#xff0c;物联网&#xff08;IoT&#xff09;正以前所未有的速度改变着我们的生活和工作方式。它如同一座桥梁&#xff0c;将实体世界与虚拟空间紧密相连&#xff0c;让数据成为驱动决策和创新的关键力量。随着物联网技术的不断成熟…

Qt Creator的好用的功能

&#xff08;1&#xff09;ctrlf&#xff1a; 在当前文档进行查询操作 &#xff08;2&#xff09;f3: 找到后&#xff0c;按f3&#xff0c;查找下一个 &#xff08;3&#xff09;shiftf3: 查找上一个 右键菜单&#xff1a; (4)f4&#xff1a;在…