CompletableFuture 线程编排

news2024/11/16 7:31:15

一、前言

Java8 新特性之一,其实现了Future<T>, CompletionStage<T>两接口,后者是对前者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使 Java 在处理多任务的协同工作时更加顺畅便利。

二、CompletableFuture的使用方法

2.1创建异步任务

2.1.1 runAsync:创建没有返回值的异步任务

// 不带返回值的异步请求,默认线程池:ForkJoinPool
public static CompletableFuture<Void> runAsync(Runnable runnable)
 
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

示例1:

public class CompletableFutureTest {

    public static void main(String[] args) {
        System.out.println("主线程-------------");
        CompletableFuture.runAsync(() -> {
            System.out.println("异步执行,当前线程名称:" + Thread.currentThread().getName());
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:

主线程-------------
主线程执行完毕,当前线程名称:main
异步执行,当前线程名称:ForkJoinPool.commonPool-worker-9

示例2:

public class CompletableFutureTest {

    public static void main(String[] args) {
        System.out.println("主线程-------------");
        CompletableFuture.runAsync(() -> {
            System.out.println("异步执行,使用自定义线程池,当前线程名称:" + Thread.currentThread().getName());
        }, ThreadPoolFactory.getExecutor());
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:

主线程-------------
主线程执行完毕,当前线程名称:main
异步执行,使用自定义线程池,当前线程名称:自定义ThreadPool_-1-thread-1

2.1.2 supplyAsync:创建有返回值的异步任务

// 带返回值异步请求,默认线程池:ForkJoinPool
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

示例1:

public class CompletableFutureTest {
    public static void main(String[] args) {
        System.out.println("主线程-------------");

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return "OK";
        });
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());

【返回结果】:
主线程-------------
异步执行,有返回值,使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
OK
主线程执行完毕,当前线程名称:main

示例2:

public class CompletableFutureTest {
    public static void main(String[] args) {
        System.out.println("主线程-------------");

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return "OK";
        }, ThreadPoolFactory.getExecutor());
        try {
            System.out.println(future.get());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());

【执行结果】:
主线程-------------
异步执行,有返回值,使用线程池,当前线程名称:自定义ThreadPool_-1-thread-1
OK
主线程执行完毕,当前线程名称:main

【注】:在获取返回结果时,可以使用 get() 或者 fork()。区别在于:get() 方法抛出的是经过检查的异常,ExecutionException, InterruptedException, 需要用户手动处理;join() 方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。

2.2任务异步回调

2.2.1 thenRun / thenRunAsync

第一个任务执行完,执行回调,做第二个任务,不关心第一个任务的返回值,无入参,无返回值

public CompletableFuture<Void> thenRun(Runnable action);
public CompletableFuture<Void> thenRunAsync(Runnable action);
public class CompletableFutureTest {

    public static void main(String[] args) {
        System.out.println("主线程-------------");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });

        CompletableFuture future2 = future.thenRun(() -> {
            System.out.println("future2 thenRun ……,当前线程名称:"+ Thread.currentThread().getName());
        });

        CompletableFuture future3 = future.thenRunAsync(()->{
            System.out.println("future3 thenRun ……,当前线程名称:"+ Thread.currentThread().getName());
        });

        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
主线程-------------
异步执行,有返回值,使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future2 thenRun ……,当前线程名称:main
主线程执行完毕,当前线程名称:main
future3 thenRun ……,当前线程名称:ForkJoinPool.commonPool-worker-9

【注】调用thenRunAsync 方法执行任务时,子任务与父任务使用同一个线程,而thenRun不是。

2.2.2 thenAccept / thenAcceptAsync

第一个任务执行完成后,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

public class CompletableFutureTest {

    public static void main(String[] args) {
        System.out.println("主线程-------------");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });

        CompletableFuture future4 = future.thenAccept((result)->{
            result += 1;
            System.out.println("future4 result最终的值为:" + result + "当前线程名称:"+ Thread.currentThread().getName());
        });
        CompletableFuture future5 = future.thenAcceptAsync((result) -> {
            result += 2;
            System.out.println("future5 result最终的值为:" + result + "当前线程名称:"+ Thread.currentThread().getName());
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
主线程-------------
异步执行,有返回值,使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future4 result最终的值为:2当前线程名称:main
主线程执行完毕,当前线程名称:main
future5 result最终的值为:3当前线程名称:ForkJoinPool.commonPool-worker-9

2.2.3 thenApply / thenApplyAsync

第一个任务执行完成后,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("主线程-------------");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });

        CompletableFuture future6 = future.thenApply((result)->{
            result += 1;
            System.out.println("future4 result最终的值为:" + result + "当前线程名称:"+ Thread.currentThread().getName());
            return result;
        });
        CompletableFuture future7 = future.thenApplyAsync((result) -> {
            result += 2;
            System.out.println("future5 result最终的值为:" + result + "当前线程名称:"+ Thread.currentThread().getName());
            return result;
        });
        System.out.println("future6 返回结果:" + future6.get());
        System.out.println("future7 返回结果:" + future7.get());
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
主线程-------------
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future4 result最终的值为:2当前线程名称:main
future6 返回结果:2
future5 result最终的值为:3当前线程名称:ForkJoinPool.commonPool-worker-9
future7 返回结果:3
主线程执行完毕,当前线程名称:main

2.2.4 exceptionally

第一个任务执行异常时,执行回调方法;将抛出异常作为参数,传递到回调方法。

public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("主线程-------------");

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
           // return 1;
           throw new RuntimeException();
        });
        CompletableFuture<String> future8 = future.exceptionally((e)->{
           e.printStackTrace();
           return "程序异常";
        });
         System.out.println("future8 返回结果:" + future8.get());

        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}   

【执行结果】:
主线程-------------
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future8 返回结果:程序异常
主线程执行完毕,当前线程名称:main
java.util.concurrent.CompletionException: java.lang.RuntimeException
    at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
    at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.RuntimeException
    at com.zy.service.completable.CompletableFutureTest.lambda$main$0(CompletableFutureTest.java:16)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    ... 5 more

2.2.5 whenComplete

第一个任务执行完,执行的回调方法,whenComplete方法返回的CompletableFuture的result是上个任务的结果

public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("主线程-------------");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });
        CompletableFuture<Integer> future9 = future.whenComplete((result, e) -> {
            result += 100;
            System.out.println(result);
        });
        System.out.println("future9 结果:" + future9.get());
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
主线程-------------
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
101
future9 结果:1
主线程执行完毕,当前线程名称:main

2.2.6 handle

第一个任务执行完,执行的回调方法,有返回值,handle方法返回的CompletableFuture 的 result是回调方法执行的结果。

public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("主线程-------------");

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });
        CompletableFuture<Integer> future10 = future.handle((result, e) -> {
            result += 100;
            System.out.println(result);
            return result;
        });
        System.out.println("future10 结果:" + future10.get());
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
主线程-------------
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
101
future10 结果:101
主线程执行完毕,当前线程名称:main

2.3 多个任务组合处理

2.3.1 thenCombine / runAfterBoth / thenAcceptBoth

都表示两个 CompletableFuture 执行完,才执行的方法,区别在于:

  • thenCombine:会将两个 CompletableFuture 任务的执行结果作为方法入参,传递到指定方法中,且有返回值

  • thenAcceptBoth: 会将两个 CompletableFuture 任务的执行结果作为方法入参,传递到指定方法中,且无返回值

  • runAfterBoth 不会把执行结果当做方法入参,且没有返回值

另外:thenCombineAsync / runAfterBothAsync / thenAcceptBothAsync 相较于 thenCombine / runAfterBoth / thenAcceptBoth 的区别就是异步执行第三个 CompletableFuture。

public class CompletableFutureTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 2;
        });

        //有入参,有返回值
        CompletableFuture<String> future3 = future1.thenCombine(future2, (s1, s2) -> {
            System.out.println("thenCombine,s1=" + s1 + ",s2=" + s2 + ",当前线程名称:" + Thread.currentThread().getName());
            return "thenCombine";
        });

        //无入参,无返回值
        CompletableFuture future4 = future1.runAfterBoth(future2, () -> {
            System.out.println("runAfterBoth,当前线程名称:" + Thread.currentThread().getName());
        });

        //有入参,无返回值
        CompletableFuture future5 = future1.thenAcceptBoth(future2, (s1, s2) -> {
            System.out.println("thenAcceptBoth,s1=" + s1 + ",s2=" + s2 + ",当前线程名称:" + Thread.currentThread().getName());
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-2
thenCombine,s1=1,s2=2,当前线程名称:main
runAfterBoth,当前线程名称:main
thenAcceptBoth,s1=1,s2=2,当前线程名称:main
主线程执行完毕,当前线程名称:main

2.3.2 applyToEither / acceptEither / runAfterEither

都表示:将两个 CompletableFuture 组合起来,只要其中一个执行完了,就执行第三个CompletableFuture。

区别在于:

  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值

  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值

  • runAfterEither:不会把执行结果当做方法入参,且没有返回值

另外:applyToEitherAsync / acceptEitherAsync / runAfterEitherAsync 相较于 applyToEither / acceptEither / runAfterEither 的区别就是异步执行第三个 CompletableFuture。

public class CompletableFutureTest {

    public static void main(String[] args)  {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 1;
        });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 2;
        });
        //有入参,有返回值
        CompletableFuture<String> future3 = future1.applyToEither(future2, (s1) -> {
            System.out.println("applyToEither,s1=" + s1 + ",当前线程名称:" + Thread.currentThread().getName());
            return "thenCombine";
        });

        //无入参,无返回值
        CompletableFuture future4 = future1.runAfterEither(future2, () -> {
            System.out.println("runAfterEither,当前线程名称:" + Thread.currentThread().getName());
        });

        //有入参,无返回值
        CompletableFuture future5 = future1.acceptEither(future2, (s1) -> {
            System.out.println("acceptEither,s1=" + s1 + ",当前线程名称:" + Thread.currentThread().getName());
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

2.3.3 allOf

所有任务都执行完成后,才执行 allOf 返回的 CompletableFuture。如果任意一个任务异常,allOf 的CompletableFuture,执行get方法,会抛出异常。

public class CompletableFutureTest {

    public static void main(String[] args)  {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future1 -- 异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 1;
        });
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future2 -- 异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 2;
        });
        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(future1, future2).whenComplete((s1, s2) -> {
            System.out.println("future1, future2 执行完,才执行该处。");
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
future1 -- 异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future2 -- 异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-2
future1, future2 执行完,才执行该处。
主线程执行完毕,当前线程名称:main

2.3.4 anyOf

任意一个任务执行完,就执行 anyOf 返回的 CompletableFuture。如果最快完成的任务出现了异常,也会先返回异常,如果害怕出错可以加个 exceptionally() 去处理一下可能发生的异常并设定默认返回值。

public class CompletableFutureTest {

    public static void main(String[] args) {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
             System.out.println("future1 -- 异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            // return 1;

            try {
                throw new Exception();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future2 -- 异步执行,有返回值,未使用线程池,当前线程名称:" + Thread.currentThread().getName());
            return 2;
        });

        CompletableFuture<Object> voidCompletableFuture = CompletableFuture.anyOf(future1, future2).whenComplete((s1, s2) -> {
            System.out.println("future1, future2 执行完,才执行该处。");
        }).exceptionally(e -> {
            System.out.println("异常:" + e);
            return -1;
        });
        System.out.println("主线程执行完毕,当前线程名称:" + Thread.currentThread().getName());
    }
}

【执行结果】:
future1 -- 异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-9
future2 -- 异步执行,有返回值,未使用线程池,当前线程名称:ForkJoinPool.commonPool-worker-2
future1, future2 执行完,才执行该处
异常:java.util.concurrent.CompletionException: java.lang.RuntimeException: java.lang.Exception
主线程执行完毕,当前线程名称:main

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

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

相关文章

POST请求与GET请求的区别

POST请求 &#xff08;提交数据&#xff0c;一般用于将数据发给服务器&#xff0c;用于修改和写入数据&#xff09; 1、传参方式&#xff1a;相对安全&#xff0c;入参在request body中&#xff0c;可通过各种抓包工具获取 2、缓存&#xff1a;不会被缓存&#xff0c;保存在服…

基于Qt的嵌入式GUI开发指南(一)

Qt 是一个跨平台的应用程序开发框架&#xff0c;用于创建高性能、可扩展和用户友好的图形用户界面&#xff08;GUI&#xff09;应用程序。它提供了丰富的工具、库和功能&#xff0c;使开发者能够轻松地构建各种类型的应用程序&#xff0c;包括桌面应用程序、移动应用程序和嵌入…

Bellhop 从入门到上手

文章目录 前言一、Bellhop 简介二、Bellhop 结构1、输入文件2、输出文件 三、Bellhop 环境文件1、OPTIONS12、OPTIONS23、OPTIONS34、OPTIONS45、其他参数 四、BELLHOP&#xff08;Matlab_GUI&#xff09;实例1、bellhop 工具箱自取2、解压缩工具箱3、设置路径4、添加并包含子文…

高频面试八股文原理篇(六) mysql数据库的左连接,右连接,内链接有何区别

目录 内连接与外连接的区别 在sql中l外连接包括左连接&#xff08;left join &#xff09;和右连接&#xff08;right join&#xff09;&#xff0c;全外连接&#xff08;full join&#xff09;&#xff0c;内连接(inner join) 内连接与外连接的区别 自连接 一个表与它自身进…

Redis(一)常见命令使用

常见文件名Redis-cli使用命令1、启动Redis2、连接Redis3、停止Redis4、发送命令1、redis-cli带参数运行&#xff0c;如&#xff1a;2、redis-cli不带参数运行&#xff0c;如&#xff1a; 5、测试连通性 key操作命令获取所有键查询键是否存在删除键查询键类型移动键查询key的生命…

第二十章行为性模式—迭代器模式

文章目录 迭代器模式解决的问题结构实例存在的问题适用场景 JDK 源码 - Iterator 行为型模式用于描述程序在运行时复杂的流程控制&#xff0c;即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务&#xff0c;它涉及算法与对象间职责的分配。行为型模式分为…

“30分钟”带你走进线性回归的世界,轻松学习互联网营销推荐算法!

前言 本章主要介绍用于推荐系统的算法-线性回归算法的推导介绍&#xff0c;文章思路如下&#xff1a;由机器学习介绍&#xff0c;到监督学习&#xff0c;并重点介绍监督学习中回归问题里面的线性回归问题及推导。 可能需要大家具备一定的统计学、高数相关知识。 一、由机器学…

k8s入门(一)之pod创建、label使用、污点、亲和性、RS

一、创建一个pod [rootmaster01 ~]# kubectl create ns prod [rootmaster01 ~]# cat pod.yaml apiVersion: v1 kind: Pod metadata:name: pod-demonamespace: prodlabels:app: myapp spec:containers:- name: test1image: busybox:latestcommand:- "/bin/sh"- "…

问题记录 bug1-系统上电挂载异常分区,df与du命令查看文件使用大小显示不一样

linux磁盘分区 在Linux中&#xff0c;一切皆目录&#xff0c;每一块硬盘分区对应Linux的一个目录&#xff0c;所以我们可以通过管理目录来管理硬盘分区&#xff0c;而将硬盘分区与文件目录关联的操作就称为“挂载”【mount】&#xff0c;反之为“卸载”【unmount】 emmc&…

C高级 text

1.从命令行传参传入两个整数&#xff0c;整数1代表从整数几开始求和&#xff0c;整数2代表求和到整数几为止 2.打印99乘法表 3.输入年月日&#xff0c;计算是该年的第几天 1. 2. 3.

Linux进程基础

进程指正在运行的程序&#xff0c;如下图示&#xff0c;是资源分配的最小单位&#xff0c;可以通过“ps ”或“top”等命令查看正 在运行的进程&#xff0c;线程是系统的最小调度单位&#xff0c;一个进程可以拥有多个线程&#xff0c;同一进程里的线程可以共享此 进程的同一资…

Server版支持即将到期,Jira和Confluence如何迁移?(2)

到2024年2月&#xff0c;Atlassian将终止对Server产品及插件的所有支持。是时候制定您的迁移计划了——Atlassian为您提供两种迁移选择&#xff0c;一是本地部署的数据中心版本&#xff0c;中国用户25人以上即可使用&#xff0c;二是云版。作为Atlassian全球白金合作伙伴&#…

Markdown可以在线编辑吗?这个办法很好用

Markdown是一种轻量级标记语言&#xff0c;它使用简单的语法来创建文本&#xff0c;非常易于学习。它最初被设计为一种用于写作的格式&#xff0c;但现在已经成为了一种非常受欢迎的文本编辑工具。 作为一个较为方便的在线文本编辑器&#xff0c;它可以用代码代替文字&#xf…

一篇完整的测试方案怎么写

看上面的目录&#xff0c;详细 文档说明 文档名称 创建人/修改人 版本 时间 备注 v1.0 2022-11-17 新建 v1.1 2022-11-25 v1.2 2022-12-05 v2.0 2022-12-13 v2.1 2022-12-14 一、文档目的 为软件开发项目管理者、软件工程师、系统维护工程师、测试…

如何开发合成物品功能?

UE5 插件开发指南 前言0 数据结构1 合成面板UI组件2 小结前言 现在策划有一个合成物品的需求:可以将多个低级物品合成高级物品,如果背包中已有低级物品了,合成时需要减掉物品的费用,只需要支付合成费;提供玩家一个合成物品的层级视图,以便于玩家有节奏的购买物品,如下图…

电影《刀剑神域进击篇:暮色黄昏》观后感

上周看了电影《刀剑神域进击篇&#xff1a;暮色黄昏》&#xff0c;刀剑神域系列质量还是非常不错的&#xff0c; 本部电影讲述主角团队攻克boss&#xff0c;阻止公会团体互相打架的故事。 刀剑系列&#xff0c;记得当初是以一部连载动漫为开端&#xff0c;如果不是特别喜欢看动…

计算机网络—HTTP基本概念、HTTPS、HTTP状态码、HTTP缓存、HTTP请求

参考小林coding HTTP基本概念 HTTP是超文本传输协议。所谓的超文本&#xff0c;就是超越了普通文本的文本&#xff0c;最关键的是有超链接&#xff0c;能从一个超文本跳转到另一个超文本。 HTML是最常见的超文本&#xff0c;本身是纯文字文件&#xff0c;但是内部使用很多标签…

Scrum敏捷项目管理实例

这是一个Scrum敏捷单团队敏捷开发示例。 1、建立产品路线图 首先我们需要为这个项目创建一个产品路线图&#xff0c;产品路线图是一个高层次的战略计划&#xff0c;它描述了产品在未来一段时间可能会如何发展和壮大&#xff0c;产品路线图确保整个产品团队持续关注产品的目标…

GEE:对Landsat遥感影像进行处理,水体提取与可视化

作者:CSDN @ _养乐多_ 本文介绍了通过Google Earth Engine平台,并使用Landsat卫星遥感数据提取水体掩膜的方法和代码。通过裁剪和去除云等处理步骤,最终得到具有水体掩膜的影像,并进行可视化和导出。这种方法基于归一化水体指数(MNDWI)和OTSU阈值计算技术,使用了一个自…

MyBatis(MyBatis环境搭建,单表操作)

目录 MyBatis 环境搭建 1.添加 Mybatis 框架支持 2.设置 MyBatis 配置信息 2.1.设置数据库连接的相关信息 2.2 Mybatis xml 保存路径和 xml命名格式 ​编辑 MyBatis 模式开发 Mybatis xml 模板 查询表内容 单元测试 以根据id,查询用户对象这个方法为例 获取动态参数的…