每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)

简介: 每日一博 - Java 异步编程的 Promise 模式 CompletableFuture的前世今生 (上)

概述

在 Java 中,在 JDK 1.8 里也引入了类似 JavaScript 的玩法 —— CompletableFuture。这个类提供了大量的异步编程中 Promise 的各种方式。

CompletableFuture.supplyAsync(this::findReceiver)
                 .thenApply(this::sendMsg)
                 .thenAccept(this::notify);
  • supplyAsync() 表示执行一个异步方法
  • thenApply() 表示执行成功后再串联另外一个异步方法
  • 最后是 thenAccept() 来处理最终结果。

接下来,我们再来看一下,Java 这个类相关的异常处理:

CompletableFuture.supplyAsync(Integer::parseInt) //输入: "ILLEGAL"
           .thenApply(r -> r * 2 * Math.PI)
           .thenApply(s -> "apply>> " + s)
           .exceptionally(ex -> "Error: " + ex.getMessage());

注意到上面代码里的 exceptionally() 方法 , 运行上面的代码,会出现如下输出:

Error: java.lang.NumberFormatException: For input string: "ILLEGAL"

也可以这样:

CompletableFuture.supplyAsync(Integer::parseInt) // 输入: "ILLEGAL"
         .thenApply(r -> r * 2 * Math.PI)
         .thenApply(s -> "apply>> " + s)
         .handle((result, ex) -> {
           if (result != null) {
             return result;
           } else {
             return "Error handling: " + ex.getMessage();
           }
         });

上面代码中,使用了 handle() 方法来处理最终的结果,其中包含了异步函数中的错误处理。


Executor与线程池

创建对象,仅仅是在 JVM 的堆里分配一块内存而已;而创建一个线程,却需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源,这个成本就很高了,所以线程是一个重量级的对象,应该避免频繁创建和销毁


为什么线程池没有采用一般意义上池化资源的设计方法呢?如果线程池采用一般意义上池化资源的设计方法,应该是下面示例代码这样。

//采用一般意义上池化资源的设计方法
class ThreadPool{
  // 获取空闲线程
  Thread acquire() {
  }
  // 释放线程
  void release(Thread t){
  }
} 
//期望的使用
ThreadPool pool;
Thread T1=pool.acquire();
//传入Runnable对象
T1.execute(()->{
  //具体业务逻辑
  ......
});

可以来思考一下,假设我们获取到一个空闲线程 T1,然后该如何使用 T1 呢?

我们期望的可能是这样:通过调用 T1 的 execute() 方法,传入一个 Runnable 对象来执行具体业务逻辑,就像通过构造函数 Thread(Runnable target) 创建线程一样。可惜的是,翻遍 Thread 对象的所有方法,都不存在类似 execute(Runnable target) 这样的公共方法。

线程池是一种生产者 - 消费者模式。 线程池的使用方是生产者,线程池本身是消费者。在下面的示例代码中,创建了一个非常简单的线程池 MyThreadPool,可以通过它来理解线程池的工作原理。

//简化的线程池,仅用来说明工作原理
class MyThreadPool{
  //利用阻塞队列实现生产者-消费者模式
  BlockingQueue<Runnable> workQueue;
  //保存内部工作线程
  List<WorkerThread> threads   = new ArrayList<>();
  // 构造方法
  MyThreadPool(int poolSize,  BlockingQueue<Runnable> workQueue){
    this.workQueue = workQueue;
    // 创建工作线程
    for(int idx=0; idx<poolSize; idx++){
      WorkerThread work = new WorkerThread();
      work.start();
      threads.add(work);
    }
  }
  // 提交任务
  void execute(Runnable command){
    workQueue.put(command);
  }
  // 工作线程负责消费任务,并执行任务
  class WorkerThread extends Thread{
    public void run() {
      //循环取任务并执行
      while(true){ // ①
        Runnable task = workQueue.take();
        task.run();
      } 
    }
  }  
}
/** 下面是使用示例 **/
// 创建有界阻塞队列
BlockingQueue<Runnable> workQueue =   new LinkedBlockingQueue<>(2);
// 创建线程池  
MyThreadPool pool = new MyThreadPool(  10, workQueue);
// 提交任务  
pool.execute(()->{
    System.out.println("hello");
});

在 MyThreadPool 的内部,我们维护了一个阻塞队列 workQueue 和一组工作线程,工作线程的个数由构造函数中的 poolSize 来指定。用户通过调用 execute() 方法来提交 Runnable 任务,execute() 方法的内部实现仅仅是将任务加入到 workQueue 中。MyThreadPool 内部维护的工作线程会消费 workQueue 中的任务并执行任务,相关的代码就是代码①处的 while 循环。线程池主要的工作原理就这些.


Java 中的线程池

Java 提供的线程池相关的工具类中,最核心的是 ThreadPoolExecutor,通过名字你也能看出来,它强调的是 Executor,而不是一般意义上的池化资源。

最完备的构造函数有 7 个参数

ThreadPoolExecutor(
  int corePoolSize,
  int maximumPoolSize,
  long keepAliveTime,
  TimeUnit unit,
  BlockingQueue<Runnable> workQueue,
  ThreadFactory threadFactory,
  RejectedExecutionHandler handler) 

可以把线程池类比为一个项目组,而线程就是项目组的成员

  • corePoolSize:表示线程池保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留 corePoolSize 个人坚守阵地。
  • maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。
  • keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了
  • workQueue:工作队列,和上面示例代码的工作队列同义。
  • threadFactory:通过这个参数可以自定义如何创建线程,
  • handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,可以通过 handler 这个参数来指定。

ThreadPoolExecutor 已经提供了以下 4 种策略。

CallerRunsPolicy:提交任务的线程自己去执行该任务。

AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。

DiscardPolicy:直接丢弃任务,没有任何异常抛出。

DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

Java 在 1.6 版本还增加了 allowCoreThreadTimeOut(boolean value) 方法,它可以让所有线程都支持超时,这意味着如果项目很闲,就会将项目组的成员都撤走。


使用线程池的注意事项

强烈建议使用有界队列

考虑到 ThreadPoolExecutor 的构造函数实在是有些复杂,所以 Java 并发包里提供了一个线程池的静态工厂类 Executors,利用 Executors 你可以快速创建线程池。不过目前大厂的编码规范中基本上都不建议使用 Executors 了。

不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列


默认拒绝策略要慎重使用

使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。


注意异常处理的问题

使用线程池,还要注意异常处理的问题,例如通过 ThreadPoolExecutor 对象的 execute() 方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止;不过,最致命的是任务虽然异常了,但是你却获取不到任何通知,这会让你误以为任务都执行得很正常。虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理,你可以参考下面的示例代码。

try {
  //业务逻辑
} catch (RuntimeException x) {
  //按需处理
} catch (Throwable x) {
  //按需处理
} 



如何获取任务执行结果

ThreadPoolExecutorvoid execute(Runnable command) 方法,利用这个方法虽然可以提交任务,但是却没有办法获取任务的执行结果(execute() 方法没有返回值)。而很多场景下,我们又都是需要获取任务的执行结果的。

Java 通过 ThreadPoolExecutor 提供的 3 个 submit() 方法和 1 个 FutureTask 工具类来支持获得任务执行结果的需求。

// 提交Runnable任务
Future<?> submit(Runnable task);
// 提交Callable任务
<T> Future<T> submit(Callable<T> task);
// 提交Runnable任务及结果引用  
<T> Future<T> submit(Runnable task, T result);

它们的返回值都是 Future 接口.

Future接口的5个方法

Future 接口有 5 个方法

// 取消任务
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否已取消  
boolean isCancelled();
// 判断任务是否已结束
boolean isDone();
// 获得任务执行结果
get();
// 获得任务执行结果,支持超时
get(long timeout, TimeUnit unit);
  • 取消任务的方法 cancel()
  • 判断任务是否已取消的方法 isCancelled()
  • 判断任务是否已结束的方法 isDone()
  • 2 个获得任务执行结果的 get() 和 get(timeout, unit),其中最后一个 get(timeout, unit) 支持超时机制。

ThreadPoolExecutor的3种submit方式

这 3 个 submit() 方法之间的区别在于方法参数不同,

  • 提交 Runnable 任务 submit(Runnable task) :这个方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。
  • 提交 Callable 任务 submit(Callable task):这个方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果
  • 提交 Runnable 任务及结果引用 submit(Runnable task, T result):这个方法很有意思,假设这个方法返回的 Future 对象是 f,f.get() 的返回值就是传给 submit() 方法的参数 result。

这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要注意的是 Runnable 接口的实现类 Task 声明了一个有参构造函数 Task(Result r) ,创建 Task 对象的时候传入了 result 对象,这样就能在类 Task 的 run() 方法中对 result 进行各种操作了。result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据

ExecutorService executor  = Executors.newFixedThreadPool(1);
// 创建Result对象r
Result r = new Result();
r.setAAA(a);
// 提交任务
Future<Result> future = 
  executor.submit(new Task(r), r);  
Result fr = future.get();
// 下面等式成立
fr === r;
fr.getAAA() === a;
fr.getXXX() === x
class Task implements Runnable{
  Result r;
  //通过构造函数传入result
  Task(Result r){
    this.r = r;
  }
  void run() {
    //可以操作result
    a = r.getAAA();
    r.setXXX(x);
  }
}

FutureTask 工具类

Future是一个接口,FutureTask是一个工具类。

FutureTask的构造函数

FutureTask(Callable<V> callable);
FutureTask(Runnable runnable, V result);

FutureTask实现了Runnable可以将FutureTask对象作为任务提交给ThreadPoolExecutor执行 & 直接被Thread执行)和Future接口(获得任务的执行结果)。 利用FutureTask对象可以很容易获取子线程的执行结果。


使用 FutureTask

其实很简单,FutureTask 实现了 RunnableFuture 接口

  • 由于实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;
  • 又因为实现了 Future 接口,所以也能用来获得任务的执行结果
FutureTask 对象提交给 ThreadPoolExecutor 去执行

public static void submitFutureTask() throws ExecutionException, InterruptedException{

// 创建FutureTask

FutureTask futureTask = new FutureTask<>(()->6*6);

// 创建线程池
    ExecutorService es = Executors.newFixedThreadPool(1);
    // 提交FutureTask
    es.submit(futureTask);
    // 获取计算结果
    Integer integer = futureTask.get();
    System.out.println(integer);
    // 关闭线程池
    es.shutdown();
}
FutureTask 对象直接被 Thread 执行
public static void executeFutureTaskByThread() throws ExecutionException, InterruptedException {
        // 创建FutureTask
        FutureTask<Integer> fs = new FutureTask<>(()->6*8);
        // 创建线程
        Thread thread = new Thread(fs,"TestThread");
        // 提交
        thread.start();
        // 获取返回结果
        Integer integer = fs.get();
        System.out.println(integer);
    }

利用 FutureTask 对象可以很容易获取子线程的执行结果



FutureTask实战—烧水泡茶

并发编程可以总结为三个核心问题:分工、同步和互斥。 编写并发程序,首先要做的就是分工,所谓分工指的是如何高效地拆解任务并分配给线程。

对于烧水泡茶这个程序,一种最优的分工方案可以是下图所示的这样:用两个线程 T1 和 T2 来完成烧水泡茶程序,

  • T1 负责洗水壶、烧开水、泡茶这三道工序,
  • T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,
  • 其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序。

对于 T1 的这个等待动作,你应该可以想出很多种办法,例如 Thread.join()、CountDownLatch,甚至阻塞队列都可以解决,不过今天我们用 Future 特性来实现。

下面的示例代码就是用Future 特性来实现的。

  • 首先,我们创建了两个 FutureTask——ft1 和 ft2,ft1 完成洗水壶、烧开水、泡茶的任务,ft2 完成洗茶壶、洗茶杯、拿茶叶的任务;
  • 这里需要注意的是 ft1 这个任务在执行泡茶任务前,需要等待 ft2 把茶叶拿来,所以 ft1 内部需要引用 ft2,并在执行泡茶之前,调用 ft2 的 get() 方法实现等待
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.*;
/**
 * @author 小工匠
 * @version 1.0
 * @description:
 * @date 2023/3/20 13:34
 * @mark: show me the code , change the world
 */
public class BurnWater {
    //对于烧水泡茶这个程序,一种最优的分工方案可以是下图所示的这样:
    // 用两个线程 T1 和 T2 来完成烧水泡茶程序,
    // T1 负责洗水壶、烧开水、泡茶这三道工序,
    // T2 负责洗茶壶、洗茶杯、拿茶叶三道工序,
    // 其中 T1 在执行泡茶这道工序时需要等待 T2 完成拿茶叶的工序。
    // 对于 T1 的这个等待动作,你应该可以想出很多种办法,例如 Thread.join()、CountDownLatch,甚至阻塞队列都可以解决,
    // 不过今天我们用 Future 特性来实现。
    public static void main(String[] args) {
        // T2 的任务 FutureTask
        FutureTask<String> futureTaskT2 = new FutureTask<>(new T2Work());
        // T1 的任务 FutureTask
        FutureTask<String> futureTaskT1 = new FutureTask<>(new T1Work(futureTaskT2));
        // 任务T1执行线程
        ExecutorService es1 = Executors.newFixedThreadPool(1);
        // 任务T2执行线程
        ExecutorService es2 = Executors.newFixedThreadPool(1);
        // 执行T1
        es1.submit(futureTaskT1);
        // 执行T2
        es2.submit(futureTaskT2);
        // 关闭线程池
        es1.shutdown();
        es2.shutdown();
    }
    /**
     * T1Task需要执行的任务: 洗水壶、烧开水、泡茶(泡茶之前要拿到茶叶)
     */
    static class T1Work implements Callable<String > {
        private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        private FutureTask<String> futureTask ;
        public T1Work( FutureTask<String> futureTask){
            this.futureTask = futureTask ;
        }
        @Override
        public String call() throws Exception {
            // 洗水壶
            System.out.printf("T1:洗水壶%s%n", LocalDateTime.now().format(dtf));
            TimeUnit.SECONDS.sleep(1);
            // 烧开水
            System.out.printf("T1:烧开水%s%n", LocalDateTime.now().format(dtf));
            TimeUnit.SECONDS.sleep(15);
            // 拿茶叶 (从T2线程的返回结果中获取)
            System.out.println("T1:开始拿茶叶:" + LocalDateTime.now().format(dtf));
            String tea = futureTask.get();
            System.out.println("T1:拿到T2给的茶叶:"+tea  + " "  + LocalDateTime.now().format(dtf));
            System.out.println("T1泡茶" + LocalDateTime.now().format(dtf));
            return "上茶 " + tea;
        }
    }
    /**
     * T2Task需要执行的任务: 洗茶壶、洗茶杯、拿茶叶
     */
    static class T2Work implements Callable<String > {
        private DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        @Override
        public String call() throws Exception {
            // 洗茶壶
            System.out.println("T2:洗茶壶" + LocalDateTime.now().format(dtf));
            TimeUnit.SECONDS.sleep(1);
            // 洗茶杯
            System.out.println("T2:洗茶杯" + LocalDateTime.now().format(dtf));
            TimeUnit.SECONDS.sleep(2);
            System.out.println("T2:拿茶叶..." + LocalDateTime.now().format(dtf));
            TimeUnit.SECONDS.sleep(100);
            return "茶叶-绿茶" ;
        }
    }
}

利用 Java 并发包提供的 Future 可以很容易获得异步任务的执行结果,无论异步任务是通过线程池 ThreadPoolExecutor 执行的,还是通过手工创建子线程来执行的。

利用多线程可以快速将一些串行的任务并行化,从而提高性能;如果任务之间有依赖关系,比如当前任务依赖前一个任务的执行结果,这种问题基本上都可以用 Future 来解决。在分析这种问题的过程中,建议用有向图描述一下任务之间的依赖关系,同时将线程的分工也做好,类似于烧水泡茶最优分工方案那幅图。对照图来写代码,好处是更形象,且不易出错。



CompletableFuture:异步编程

用多线程优化性能,其实不过就是将串行操作变成并行操作。 在串行转换成并行的过程中,一定会涉及到异步化,

例如下面的示例代码,现在是串行的,为了提升性能,我们得把它们并行化,那具体实施起来该怎么做呢?

//以下两个方法都是耗时操作
doBizA();
doBizB();

创建两个子线程去执行就可以了。下面的并行方案,主线程无需等待 doBizA() 和 doBizB() 的执行结果,也就是说 doBizA() 和 doBizB() 两个操作已经被异步化了。

new Thread(()->doBizA())
  .start();
new Thread(()->doBizB())
  .start();  

利用多线程优化性能这个核心方案得以实施的基础。 Java 在 1.8 版本提供了 CompletableFuture 来支持异步编程,CompletableFuture 有可能是我们见过的最复杂的工具类了,不过功能也着实让人感到震撼。


CompletableFuture 实现烧水泡茶

首先还是需要先完成分工方案。 分了 3 个任务:

  • 任务 1 负责洗水壶、烧开水
  • 任务 2 负责洗茶壶、洗茶杯和拿茶叶
  • 任务 3 负责泡茶。其中任务 3 要等待任务 1 和任务 2 都完成后才能开始

下面是代码实现,你先略过 runAsync()、supplyAsync()、thenCombine() 这些不太熟悉的方法,从大局上看:

  • 无需手工维护线程,没有繁琐的手工维护线程的工作,给任务分配线程的工作也不需要我们关注
  • 语义更清晰,例如 f3 = f1.thenCombine(f2, ()->{}) 能够清晰地表述“任务 3 要等待任务 1 和任务 2 都完成后才能开始”;
  • 代码更简练并且专注于业务逻辑,几乎所有代码都是业务逻辑相关的
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
/**
 * @author 小工匠
 * @version 1.0
 * @description: TODO
 * @date 2023/3/21 10:32
 * @mark: show me the code , change the world
 */
public class CompletableBurnWater {
    private static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * -  任务 1 负责洗水壶、烧开水
     * -  任务 2 负责洗茶壶、洗茶杯和拿茶叶
     * -  任务 3 负责泡茶。其中任务 3 要等待任务 1 和任务 2 都完成后才能开始
     * @param args
     */
    public static void main(String[] args)  throws Exception {
        // 任务 1 负责洗水壶、烧开水
        CompletableFuture<Void> cf1 = CompletableFuture.runAsync(() -> {
            // 洗水壶
            System.out.printf("T1:洗水壶%s%n", LocalDateTime.now().format(dtf));
            sleep(1,TimeUnit.SECONDS);
            // 烧开水
            System.out.printf("T1:烧开水%s%n", LocalDateTime.now().format(dtf));
            sleep(15,TimeUnit.SECONDS);
        });
        // 任务 2 负责洗茶壶、洗茶杯和拿茶叶
        CompletableFuture<String> cf2 = CompletableFuture.supplyAsync(() -> {
            // 洗茶壶
            System.out.println("T2:洗茶壶" + LocalDateTime.now().format(dtf));
            sleep(1,TimeUnit.SECONDS);
            // 洗茶杯
            System.out.println("T2:洗茶杯" + LocalDateTime.now().format(dtf));
            sleep(2,TimeUnit.SECONDS);
            System.out.println("T2:拿茶叶..." + LocalDateTime.now().format(dtf));
            sleep(30,TimeUnit.SECONDS);
            return "茶叶-绿茶" ;
        });
        //任务3:任务1和任务2完成后执行:泡茶
        // (__,tf)两个参数,第一个参数__表示f1的返回值,因为f1没有返回值,随便写了个代替,第二个tf表示f2的返回值,="茶叶-绿茶"
        CompletableFuture<String> cf3 = cf1.thenCombine(cf2, (__, tf) -> {
            System.out.println("T1:拿到茶叶:" + tf);
            System.out.println("T1:泡茶...");
            return tf;
        });
        //等待任务3执行结果
        System.out.println(cf3.join());
    }
    static void sleep(int unit , TimeUnit timeUnit){
        try {
            timeUnit.sleep(unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

runAsync vs supplyAsync

//使用默认线程池
static CompletableFuture<Void>  runAsync(Runnable runnable)
static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier)
//可以指定线程池  
static CompletableFuture<Void>   runAsync(Runnable runnable, Executor executor)
static <U> CompletableFuture<U>  supplyAsync(Supplier<U> supplier, Executor executor)  
  • Runnable 接口的 run() 方法没有返回值
  • Supplier 接口的 get() 方法是有返回值的
  • 后两个方法第二个参数 Executor 可以指定线程池参数

默认情况下 CompletableFuture 会使用公共的 ForkJoinPool 线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数)。

如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以强烈建议根据不同的业务类型创建不同的线程池,以避免互相干扰。


相关文章
|
3天前
|
Java 开发者 UED
Java 异步和事件驱动编程:探索响应式模式
【4月更文挑战第27天】在现代软件开发中,异步和事件驱动编程是提高应用性能和响应性的关键策略。Java 提供了多种机制来支持这些编程模式,使开发者能够构建高效、可扩展的应用程序。
14 4
|
3天前
|
设计模式 消息中间件 Java
Java 设计模式:探索发布-订阅模式的原理与应用
【4月更文挑战第27天】发布-订阅模式是一种消息传递范式,被广泛用于构建松散耦合的系统。在 Java 中,这种模式允许多个对象监听和响应感兴趣的事件。
20 2
|
4天前
|
人工智能 监控 数据可视化
Java智慧工地云平台源码带APP SaaS模式 支持私有化部署和云部署
智慧工地是指应用智能技术和互联网手段对施工现场进行管理和监控的一种工地管理模式。它利用传感器、监控摄像头、人工智能、大数据等技术,实现对施工现场的实时监测、数据分析和智能决策,以提高工地的安全性、效率和质量(技术架构:微服务+Java+Spring Cloud +UniApp +MySql)。
19 4
|
6天前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
6天前
|
人工智能 监控 安全
JAVA基于SaaS模式的智慧工地云平台源码(云智慧工地解决方案)
智慧工地支持多端展示(PC端、手机端、平板端)SaaS微服务架构,项目监管端,工地管理端源码
13 0
|
7天前
|
设计模式 存储 JavaScript
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
[设计模式Java实现附plantuml源码~创建型] 多态工厂的实现——工厂方法模式
|
7天前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
[设计模式Java实现附plantuml源码~创建型] 集中式工厂的实现~简单工厂模式
|
14天前
|
Java API 数据库
深研Java异步编程:CompletableFuture与反应式编程范式的融合实践
【4月更文挑战第17天】本文探讨了Java中的CompletableFuture和反应式编程在提升异步编程体验上的作用。CompletableFuture作为Java 8引入的Future扩展,提供了一套流畅的链式API,简化异步操作,如示例所示的非阻塞数据库查询。反应式编程则关注数据流和变化传播,通过Reactor等框架实现高度响应的异步处理。两者结合,如将CompletableFuture转换为Mono或Flux,可以兼顾灵活性和资源管理,适应现代高并发环境的需求。开发者可按需选择和整合这两种技术,优化系统性能和响应能力。
|
15天前
|
缓存 小程序
Java+saas模式 智慧校园系统源码MySQL5.7+ elmentui前后端分离架构 让校园管理更高效的数字化平台系统源码
智慧校园是在数字通增强版基础上,研发的一套面向教育行业的数字化校园软件,其显著特点是集学校网站、协同办公、即时通讯、网络空间、移动办公于一体。在满足教职工日常办公需要的同时,拥有诸多教育行业功能,并提供便捷易用的“家校通”平台以满足老师、学生、家长的日常交流。数字通智慧校园教育版中的协同办公、即时通讯、移动办公等功能模块随通用版一同改进,将网络办公最新技术应用到教育行业。
21 1
|
16天前
|
传感器 小程序 Java
Java+saas模式 智慧校园系统源码Java Android +MySQL+ IDEA 多校运营数字化校园云平台源码
Java+saas模式 智慧校园系统源码Java Android +MySQL+ IDEA 多校运营数字化校园云平台源码 智慧校园即智慧化的校园,也指按智慧化标准进行的校园建设,按标准《智慧校园总体框架》中对智慧校园的标准定义是:物理空间和信息空间的有机衔接,使任何人、任何时间、任何地点都能便捷的获取资源和服务。
16 1