java多线程之FutureTask、Future、CompletableFuture

news2024/10/6 23:38:37

前面已经在多线程创建的时候有提到Future和FutureTask的简单用法,这里详细介绍下FutureTask以及CompletableFuture

一、FutureTask

1、FutureTask简介

FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给 Executor执行,也可以由调用线程直接执行(FutureTask.run())。根据FutureTask.run()方法被执行 的时机,FutureTask可以处于下面3种状态。

1)未启动。FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态。当创建一 个FutureTask,且没有执行FutureTask.run()方法之前,这个FutureTask处于未启动状态。

2)已启动。FutureTask.run()方法被执行的过程中,FutureTask处于已启动状态。

3)已完成。FutureTask.run()方法执行完后正常结束,或被取消(FutureTask.cancel(…)),或 执行FutureTask.run()方法时抛出异常而异常结束,FutureTask处于已完成状态。

FutureTask的状态迁移的示意图。

当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞; 当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛 出异常。

当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执 行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以中断执行此任务线程 的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将 不会对正在执行此任务的线程产生影响(让正在执行的任务运行完成);当FutureTask处于已完 成状态时,执行FutureTask.cancel(…)方法将返回false。

图get方法和cancel方法的执行示意图。 

2、FutureTask的使用

可以把FutureTask交给Executor执行;也可以通过ExecutorService.submit(…)方法返回一个 FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel(…)方法。除此以外,还可以单独 使用FutureTask。

当一个线程需要等待另一个线程把某个任务执行完后它才能继续执行,此时可以使用 FutureTask。假设有多个线程执行若干任务,每个任务最多只能被执行一次。当多个线程试图 同时执行同一个任务时,只允许一个线程执行任务,其他线程需要等待这个任务执行完后才 能继续执行。下面是对应的示例代码。

public class Test {
    private final ConcurrentMap<Object, Future<String>> taskCache =
            new ConcurrentHashMap<Object, Future<String>>();

    private String executionTask(final String taskName)
            throws ExecutionException, InterruptedException {
        while (true) {
            Future<String> future = taskCache.get(taskName);
            if (future == null) {
                Callable<String> task = new Callable<String>() {
                    public String call() throws InterruptedException {
                        return taskName;
                    }
                }; //创建任务
                FutureTask<String> futureTask = new FutureTask<String>(task);
                future = taskCache.putIfAbsent(taskName, futureTask);
                if (future == null) {
                    future = futureTask;
                    futureTask.run(); // 执行任务
                }
            }
            try {
                return future.get(); // 线程在此等待任务执行完成
            } catch (CancellationException e) {
                taskCache.remove(taskName, future);
            }
        }
    }
}

 当两个线程试图同时执行同一个任务时,如果Thread 1执行1.3后Thread 2执行2.1,那么接 下来Thread 2将在2.2等待,直到Thread 1执行完1.4后Thread 2才能从2.2(FutureTask.get())返回。

3、FutureTask的实现

FutureTask的实现基于AbstractQueuedSynchronizer(以下简称为AQS)。java.util.concurrent中 的很多可阻塞类(比如ReentrantLock)都是基于AQS来实现的。AQS是一个同步框架,它提供通 用机制来原子性管理同步状态、阻塞和唤醒线程,以及维护被阻塞线程的队列。JDK 6中AQS 被广泛使用,基于AQS实现的同步器包括:ReentrantLock、Semaphore、ReentrantReadWriteLock、 CountDownLatch和FutureTask。

每一个基于AQS实现的同步器都会包含两种类型的操作,如下。 

至少一个acquire操作。这个操作阻塞调用线程,除非/直到AQS的状态允许这个线程继续 执行。FutureTask的acquire操作为get()/get(long timeout,TimeUnit unit)方法调用。

至少一个release操作。这个操作改变AQS的状态,改变后的状态可允许一个或多个阻塞 线程被解除阻塞。FutureTask的release操作包括run()方法和cancel(…)方法。

基于“复合优先于继承”的原则,FutureTask声明了一个内部私有的继承于AQS的子类 Sync,对FutureTask所有公有方法的调用都会委托给这个内部子类。

AQS被作为“模板方法模式”的基础类提供给FutureTask的内部子类Sync,这个内部子类只 需要实现状态检查和状态更新的方法即可,这些方法将控制FutureTask的获取和释放操作。具 体来说,Sync实现了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法,Sync通过这 两个方法来检查和更新同步状态。

FutureTask的设计示意图如图

 如图所示,Sync是FutureTask的内部私有类,它继承自AQS。

创建FutureTask时会创建内部 私有的成员对象Sync,FutureTask所有的的公有方法都直接委托给了内部私有的Sync。 FutureTask.get()方法会调用AQS.acquireSharedInterruptibly(int arg)方法,这个方法的执行 过程如下。

1)调用AQS.acquireSharedInterruptibly(int arg)方法,这个方法首先会回调在子类Sync中实 现的tryAcquireShared()方法来判断acquire操作是否可以成功。acquire操作可以成功的条件为: state为执行完成状态RAN或已取消状态CANCELLED,且runner不为null。

2)如果成功则get()方法立即返回。如果失败则到线程等待队列中去等待其他线程执行 release操作。

3)当其他线程执行release操作(比如FutureTask.run()或FutureTask.cancel(…))唤醒当前线 程后,当前线程再次执行tryAcquireShared()将返回正值1,当前线程将离开线程等待队列并唤 醒它的后继线程(这里会产生级联唤醒的效果,后面会介绍)。

4)最后返回计算的结果或抛出异常。

FutureTask.run()的执行过程如下。

1)执行在构造函数中指定的任务(Callable.call())。

2)以原子方式来更新同步状态(调用AQS.compareAndSetState(int expect,int update),设置 state为执行完成状态RAN)。如果这个原子操作成功,就设置代表计算结果的变量result的值为 Callable.call()的返回值,然后调用AQS.releaseShared(int arg)。

3)AQS.releaseShared(int arg)首先会回调在子类Sync中实现的tryReleaseShared(arg)来执 行release操作(设置运行任务的线程runner为null,然会返回true);AQS.releaseShared(int arg), 然后唤醒线程等待队列中的第一个线程。

4)调用FutureTask.done()。

当执行FutureTask.get()方法时,如果FutureTask不是处于执行完成状态RAN或已取消状态 CANCELLED,当前执行线程将到AQS的线程等待队列中等待(见下图的线程A、B、C和D)。当 某个线程执行FutureTask.run()方法或FutureTask.cancel(...)方法时,会唤醒线程等待队列的第一 个线程(见图10-16所示的线程E唤醒线程A)。

 假设开始时FutureTask处于未启动状态或已启动状态,等待队列中已经有3个线程(A、B和 C)在等待。此时,线程D执行get()方法将导致线程D也到等待队列中去等待。

当线程E执行run()方法时,会唤醒队列中的第一个线程A。线程A被唤醒后,首先把自己从 队列中删除,然后唤醒它的后继线程B,最后线程A从get()方法返回。线程B、C和D重复A线程 的处理流程。最终,在队列中等待的所有线程都被级联唤醒并从get()方法返回。

二、CompletableFuture

1、简介

CompletableFuture 在 Java 里面被用于异步编程,异步通常意味着非阻塞, 可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可 以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。

CompletableFuture 实现了 Future, CompletionStage 接口,实现了 Future 接口就可以兼容现在有线程池框架,而 CompletionStage 接口才是异步编程 的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的 CompletableFuture 类。

Futrue 在 Java 里面,通常用来表示一个异步任务的引用,比如我们将任务提 交到线程池里面,然后我们会得到一个 Futrue,在 Future 里面有 isDone 方 法来 判断任务是否处理结束,还有 get 方法可以一直阻塞直到任务结束然后获 取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或 者不断轮询才能知道任务是否完成。

Future 的主要缺点如下:

(1) 不支持手动完成 我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果, 现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直 等待它执行完成

(2) 不支持进一步的非阻塞调用 通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行 额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能

(3) 不支持链式调用 对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成 一个链式的 pipline 调用,这在 Future 中是没法实现的。

(4) 不支持多个 Future 合并比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后, 执行某些函数,是没法通过 Future 实现的。

(5) 不支持异常处理 Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题 是不好定位的。

2、使用 CompletableFuture

2.1、入门使用

场景:主线程里面创建一个 CompletableFuture,然后主线程调用 get 方法会 阻塞,最后我们在一个子线程中使其终止。

public class Test {
    public static void main(String[] args) throws
            Exception{ CompletableFuture<String> future = new
            CompletableFuture<>();new Thread(() -> {
        try{
            System.out.println(Thread.currentThread().getName() + "子线程开始干活");
//子线程睡 5 秒
            Thread.sleep(5000);
//在子线程中完成主线程
            future.complete("success");
        }catch (Exception
                e){ e.printStackTrace();
        }
    }, "A").start();
//主线程调用 get 方法阻塞
        System.out.println("主线程调用 get 方法获取结果为: " + future.get());
        System.out.println("主线程完成,阻塞结束!!!!!!");
    }
}

结果

A子线程开始干活
主线程调用 get 方法获取结果为: success
主线程完成,阻塞结束!!!!!!

2.2、没有返回值的异步任务

public class Test {
    public static void main(String[] args) throws
            Exception {
        System.out.println("主线程开始");
        //运行一个没有返回值的异步任务
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                System.out.println("子线程启动干活");
                Thread.sleep(5000);
                System.out.println("子线程完成");
            } catch (Exception e) {
                e.printStackTrace()
                ;
            }
        });
        //主线程阻塞
        future.get();
        System.out.println("主线程结束");
    }
}

2.3、有返回值的异步任务

public class Test {
    public static void main(String[] args) throws
            Exception{System.out.println("主线程开始");//运行一个有返回值的异步任务
        CompletableFuture<String> future =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        System.out.println("子线程开始任务");
                        Thread.sleep(5000);
                    } catch (Exception e)
                    {e.printStackTrace()
                    ;
                    }
                    return "子线程完成了!";
                });//主线程阻塞
        String s = future.get();
        System.out.println("主线程结束, 子线程的结果为:" + s);
    }

}

2.4、线程依赖

当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行 化

public class Test {
    private static Integer num = 10;
    /**
     * 先对一个数加 10,然后取平方
     * @param args
     */
    public static void main(String[] args) throws Exception {
        System.out.println("主线程开始");
        CompletableFuture<Integer> future =
                CompletableFuture.supplyAsync(() -> {
                    try {
                        System.out.println("加 10 任务开始");
                        num += 10;
                    } catch (Exception e) {
                        e.printStackTrace()
                        ;
                    }
                    return num;
                }).thenApply(integer ->
                {
                    return num * num;
                });
        Integer integer = future.get();
        System.out.println("主线程结束, 子线程的结果为:" + integer);
    }
}

2.5、消费处理结果

thenAccept 消费处理结果, 接收任务的处理结果,并消费处理,无返回结果


public class Test {
    private static Integer num = 10;

    public static void main(String[] args) throws Exception {
        System.out.println("主线程开始");
        CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("加 10 任务开始");
                num += 10;
            } catch (Exception e) {
                e.printStackTrace()
                ;
            }
            return num;
        }).thenApply(integer ->
        {
            return num * num;
        }).thenAccept(new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("子线程全部处理完成,最后调用了 accept,结果为:" +
                        integer);
            }
        });
    }

}

2.6、异常处理

exceptionally 异常处理,出现异常时触发

public class Test {
    private static Integer num = 10;

    public static void main(String[] args) throws
            Exception{System.out.println("主线程开始");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() ->
        {int i= 1/0;
            System.out.println("加 10 任务开始");
            num += 10;
            return num;
        }).exceptionally(ex ->
        { System.out.println(ex.getMessage());
            return -1;
        });
        System.out.println(future.get());
    }

}

handle 类似于 thenAccept/thenRun 方法,是最后一步的处理调用,但是同时可以处理异常

public class Test {
    private static Integer num = 10;

    public static void main(String[] args) throws
            Exception {
        System.out.println("主线程开始");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("加 10 任务开始");
            num += 10;
            return num;
        }).handle((i, ex) -> {
            System.out.println("进入 handle 方法");
            if (ex != null) {
                System.out.println("发生了异常,内容为:" + ex.getMessage());
                return -1;
            } else {
                System.out.println("正常完成,内容为: " + i);
                return i;
            }
        });
        System.out.println(future.get());
    }

}

2.7、结果合并

thenCompose 合并两个有依赖关系的 CompletableFutures 的执行结果

public class Test {
    private static Integer num = 10;

    public static void main(String[] args) throws
            Exception{System.out.println("主线程开始");
//第一步加 10
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() ->
        {System.out.println("加 10 任务开始");
            num += 10;
            return num;
        });
//合并
        CompletableFuture<Integer> future1 = future.thenCompose(i ->
//再来一个CompletableFuture
                CompletableFuture.supplyAsync(() -> {
                    return i + 1;
                }));
        System.out.println(future.get());
        System.out.println(future1.get());
    }

}

thenCombine 合并两个没有依赖关系的 CompletableFutures 任务


public class Test {
    private static Integer num = 10;

    public static void main(String[] args) throws
            Exception {
        System.out.println("主线程开始");
        CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("加 10 任务开始");
            num += 10;
            return num;
        });
        CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("乘以 10 任务开始");
            num = num * 10;
            return num;
        });
//合并两个结果
        CompletableFuture<Object> future = job1.thenCombine(job2, new
                BiFunction<Integer, Integer, List<Integer>>() {
                    @Override
                    public List<Integer> apply(Integer a, Integer b) {
                        List<Integer> list = new ArrayList<>();
                        list.add(a);
                        list.add(b);
                        return list;
                    }
                });
        System.out.println("合并结果为:" + future.get());
    }
}

合并多个任务的结果allOf 与anyOf

allOf: 一系列独立的future 任务,等其所有的任务执行完后做一些事情

public class Test {
    private static Integer num = 10;

    /**
     * 先对一个数加 10,然后取平方
     *
     * @param args
     */
    public static void main(String[] args) throws
            Exception {
        System.out.println("主线程开始");
        List<CompletableFuture> list = new ArrayList<>();
        CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("加 10 任务开始");
            num += 10;
            return num;
        });
        list.add(job1);
        CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("乘以 10 任务开始");
            num = num * 10;
            return num;
        });
        list.add(job2);
        CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("减以 10 任务开始");
            num = num * 10;
            return num;
        });
        list.add(job3);
        CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() ->
        {
            System.out.println("除以 10 任务开始");
            num = num * 10;
            return num;
        });
        list.add(job4);
//多任务合并
        List<Integer> collect =
                list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
        System.out.println(collect);
    }

}

anyOf: 只要在多个future 里面有一个返回,整个任务就可以结束,而不需要等到每一个 future 结束

public class Test {
    private static Integer num = 10;

    /**
     * 先对一个数加 10,然后取平方
     *
     * @param args
     */
    public static void main(String[] args) throws
            Exception {
        System.out.println("主线程开始");
        CompletableFuture<Integer>[] futures = new CompletableFuture[4];
        CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(5000);
                System.out.println("加 10 任务开始");
                num += 10;
                return num;
            } catch (Exception
                    e) {
                return 0;
            }
        });
        futures[0] = job1;
        CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() ->
        {
            try {
                Thread.sleep(2000);
                System.out.println("乘以 10 任务开始");
                num = num * 10;
                return num;
            } catch (Exception
                    e) {
                return 1;
            }
        });
        futures[1] = job2;
        CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() ->
        {
            try {
                Thread.sleep(3000);
                System.out.println("减以 10 任务开始");
                num = num * 10;
                return num;
            } catch (Exception
                    e) {
                return 2;
            }
        });
        futures[2] = job3;
        CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() ->
        {
            try {
                Thread.sleep(4000);
                System.out.println("除以 10 任务开始");
                num = num * 10;
                return num;
            } catch (Exception
                    e) {
                return 3;
            }
        });
        futures[3] = job4;
        CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
        System.out.println(future.get());
    }
}

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

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

相关文章

回溯法实现N皇后问题

题1&#xff1a;&#xff08;需打印矩阵&#xff09; 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#…

命名空间,缺省参数与函数重载

目录 一&#xff0c;命名空间 1.何为命名空间 2.命名空间的使用 ​编辑 4.::作用域限定符 3.命名空间的展开 全局展开&#xff1a; 局部展开&#xff1a; 4.嵌套命名空间 二&#xff0c;缺省参数与函数重载 1.什么是缺省参数 2.什么是函数重载 3.两者的冲突 一&…

内部数据泄露:保护数据安全的挑战与解决方案

导语&#xff1a; 在当今数字化时代&#xff0c;数据是企业的核心资产之一。然而&#xff0c;随着科技的快速发展和信息的日益增长&#xff0c;数据安全问题也日益突出。其中&#xff0c;内部数据泄露成为企业所面临的重大挑战之一。本文将探讨内部数据泄露的危害&#xff0c;…

简直太高效了!一篇文章帮你快速了解企业如何实现无纸化办公

随着科技的发展和信息技术的普及&#xff0c;无纸化办公已经成为了现代企业的一个趋势。无纸化办公即指在企业日常工作中&#xff0c;尽量减少或不使用纸张作为工作载体&#xff0c;通过电子邮件、电子文档、电子表格等工具实现信息的传递和共享。无纸化办公不仅有利于环保&…

路由器隔绝广播,为什么还要VLAN?

路由器能隔绝广播&#xff0c;那要VLAN有什么用&#xff0c;既配置了VLAN又划分在不同的网段是不是有些多余了&#xff1f; 题主的意思是不要VLAN&#xff0c;可以吗&#xff1f; 当然可以。可以是可以&#xff0c;但是每台主机都能接收到路由器同一个接口&#xff08;三层/路…

如何正确有效的学习java前端(合集)

大量阅读 我是一个劲头十足的读者。所以&#xff0c;我的第一个关于学习JavaScript的技巧就是关于阅读&#xff0c;这绝不是巧合。书籍和其他的资源(如文章)可以在很大程度上帮助你学习JavaScript。通过实践学习&#xff0c;书籍是我学习新学科最喜欢的方式。在学习JavaScript的…

如何快速新建Linux虚拟机,安装linux系统步骤

1、安装好VMware workstation 软件&#xff1a;vmware workstation centos 7 2、点击创建新的虚拟机 3、选择安装操作系统&#xff0c;&#xff08;我这里使用centos-7 最简安装包&#xff0c;这种系统开销比较小&#xff0c;对机器性能要求不高&#xff09; 硬盘根据实际…

【C++修炼之路】内存管理

&#x1f451;作者主页&#xff1a;安 度 因 &#x1f3e0;学习社区&#xff1a;StackFrame &#x1f4d6;专栏链接&#xff1a;C修炼之路 文章目录 一、C/C 内存分布二、考题三、C语言动态内存管理方式四、C内存管理方式1、对内置类型2、对自定义类型 五、C对动态管理的升级六…

vue使用echarts根据页面大小 echarts窗口自适应

1. 使用window.onresize var myChart echarts.init(document.getElementById(myChart)); window.onresize () > {myChart.resize() }优点&#xff1a; 可以根据窗口大小实现自适应 缺点&#xff1a; window.onresize是绑定到window上的&#xff0c;切换vue页面时监听依…

WEB APIs day4 (1)

一、日期对象 1.实例化 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

做私域选个微还是企微,哪个有优势?

做私域&#xff0c;你必须要有一个&#xff0c;引流新客户及留存老客户的地方。 于是&#xff0c;就有很多人讨论或者纠结&#xff1a;做私域&#xff0c;选择个人微信&#xff1f;还是企业微信&#xff1f; 让我们一起来看看个人微信和企业微信在功能和使用上有哪些区别&…

什么是 DevOps?看这一篇就够了!

目录 一、前因 二、记忆 三、他们说…… 3.1、Atlassian 回答“什么是 DevOps&#xff1f;” 3.2、微软回答“什么是 DevOps&#xff1f;” 3.3、AWS 回答“什么是 DevOps&#xff1f;” 四、DevOps 文化 4.1、什么是文化&#xff1f; 4.2、什么是 DevOps 文化&#xf…

谈人工智能和数据治理

一、说明 生成式人工智能已经开始撼动数据治理的世界&#xff0c;并且将继续这样做。 自 ChatGPT 发布以来才 6 个月&#xff0c;但感觉我们已经需要回顾了。在这篇文章中&#xff0c;我将探讨生成式人工智能如何影响数据治理&#xff0c;以及它在不久的将来可能会把我们带到哪…

APP打包教程(使用HBuilder X工具打包uni-app)

App打包&#xff08;使用Hbuilder进行App打包&#xff09; 一、修改接口地址 1.打开uni-app下config/app.js修改接口地址&#xff0c;将下图红框中的地址修改成您的域名 二、配置参数 1.打开 uni-app 根目录下的 manifest.json 文件&#xff0c; 点击《基础配置》&#xff0…

户外运动耳机怎么选?这几款耳机最适合在运动时佩戴!

随着人们开始追求运动和健身带来的乐趣&#xff0c;以及在运动过程中享受音乐的过程&#xff0c;耳机逐渐成为当下的刚需&#xff0c;其中骨传导耳机凭借防水防汗、佩戴稳固不掉落加上开放式聆听受到当下消费者的热烈欢迎&#xff0c;有优点就有缺点&#xff0c;由于骨传导耳机…

opengauss 在一个机器上搭建主备集群

项目上需要高斯主备集群&#xff0c;试了好几个版本。最后搭建出一个在一个机器上的主备。用做测试&#xff0c;记录一下。 下载安装包 从openGauss开源社区下载对应平台的安装包。 a. 通过软件包 | openGauss登录openGauss开源社区&#xff0c;选择3.1.0版本对应平台极简版安…

【网络可用性】

网络可用性 Availability defined in a service-level agreement (SLA) between a network operator (carrier) and a customer. 关于SLA&#xff0c;可参考 思科Service Level Management: Best Practices White Paper 可用性对应的停机时间 转载于 https://blog.csdn.net/a…

Spark(27):Spark任务调度机制

目录 0. 相关文章链接 1. Spark任务调度概述 2. Spark Stage级调度 3. Spark Task级调度 3.1. 调度策略 3.1.1. FIFO调度策略 3.1.2. FAIR调度策略 3.2. 本地化调度 3.3. 失败重试与黑名单机制 0. 相关文章链接 Spark文章汇总 1. Spark任务调度概述 在生产环境下&am…

C#为什么不能成为大学编程入门的首选?

大学编程入门不以C#作为首选的原因有多个因素。虽然C#是一种功能强大的编程语言&#xff0c;但在大学编程入门阶段&#xff0c;通常会选择其他语言作为首选&#xff0c;以下是一些可能的原因&#xff1a; 我这里刚好有嵌入式、单片机、plc的资料需要可以私我或在评论区扣个6 …

投个 3D 冰壶,上班玩一玩 | 物理引擎

本篇文章将介绍如何使用物理引擎和图扑 3D 可视化技术来呈现冰壶运动的模拟。 Oimo.js 物理引擎 Oimo.js 是一个轻量级的物理引擎&#xff0c;它使用 JavaScript 语言编写&#xff0c;并且基于 OimoPhysics 引擎进行了改进和优化。Oimo.js 核心库只有 150K &#xff0c;专门用…