RxJava入门

Single

入门示例#

public class Main {  
  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        Single<String> singleUser = getUserFirst().flatMap(Main::getUserSecond);  
        Single<String> singleHotel = getHotelInfo();  
  
        Single<String> single = Single.zip(singleUser, singleHotel, (user, hotel) -> "user:" + user + " hotel:" + hotel);  
        Future<String> future = single.toFuture();  
  
        try {  
            System.out.println(future.get());  
        } catch (Exception e) {  
            System.out.println("出错了");  
        }  
  
    }  
    private static Single<String> getUserFirst() {  
        return Single.fromCallable(() -> {  
            try {  
                if (Math.random() < 0.5) {  
                    throw new Exception("获取用户阶段1出错");  
                }  
                return "user step1";  
            } catch (Exception e) {  
                return "默认用户阶段1";  
            }  
        });  
    }  
  
    private static Single<String> getUserSecond(String user) {  
        return Single.fromCallable(() -> {  
            try {  
                if (Math.random() < 0.5) {  
                    throw new Exception("获取用户阶段2出错");  
                }  
                return user + " user step2";  
            } catch (Exception e) {  
                return user + " 默认用户阶段2";  
            }  
        });  
    }  
  
    private static Single<String> getHotelInfo() {  
        return Single.fromCallable(() -> {  
            try {  
                if (Math.random() < 0.5) {  
                    throw new Exception("获取酒店出错");  
                }  
                return "hotel1";  
            } catch (Exception e) {  
                return "默认酒店";  
            }  
  
        });  
    }  
}

Single.just(Optional.empty())#

public class Main {  
  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        Single<Optional<String>> single1 = Single.fromCallable(() -> {  
            System.out.println("single1线程" + Thread.currentThread().getName());  
            if (Math.random() < 0.8) {  
                return Optional.of("Hello");  
            } else {  
                return Optional.empty();  
            }  
        });  
        
        Single<Optional<String>> single2 = Single.fromCallable(() -> {  
            System.out.println("single2线程" + Thread.currentThread().getName());  
            if (Math.random() < 0.8) {  
                return Optional.of(" world");  
            } else {  
                return Optional.empty();  
            }  
        });  
  
        Single<Optional<String>> single3 = Single.zip(single1, single2, (res1, res2) -> {  
            System.out.println("single3线程" + Thread.currentThread().getName());  
            if (res1.isPresent() && res2.isPresent()) {  
                return Optional.of(res1.get() + res2.get());  
            } else {  
                return Optional.empty();  
            }  
        });  
  
        System.out.println(single3.toFuture().get());  
    }  
}

Single多线程#

public class Main {  
  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        Single<Optional<?>> single1 = Single.fromCallable(() -> {  
            System.out.println("single1线程" + Thread.currentThread().getName());  
            if (Math.random() < 0.8) {  
                return Optional.of("Hello");  
            } else {  
                return Optional.empty();  
            }  
        }).subscribeOn(Schedulers.io());  
  
        Single<Optional<?>> single2 = Single.fromCallable(() -> {  
            System.out.println("single2线程" + Thread.currentThread().getName());  
            if (Math.random() < 0.8) {  
                return Optional.of(" world");  
            } else {  
                return Optional.empty();  
            }  
        }).subscribeOn(Schedulers.io());  
  
        Single<Optional<String>> single3 = Single.zip(single1, single2, (res1, res2) -> {  
            System.out.println("single3线程" + Thread.currentThread().getName());  
            if (res1.isPresent() && res2.isPresent()) {  
                return Optional.of(res1.get().toString() + res2.get());  
            } else {  
                return Optional.empty();  
            }  
        });  
  
        System.out.println(single3.toFuture().get());  
    }  
}
single2线程RxCachedThreadScheduler-2
single1线程RxCachedThreadScheduler-1
single3线程RxCachedThreadScheduler-2
Optional[Hello world]

single转换成future#

Single 可以转换为 Future。在 Java 中,Single 是 RxJava 提供的一个表示单个异步计算结果的类型,而 Future 是 Java 标准库中用于表示异步计算结果的接口。要将 Single 转换为 Future,可以使用 Single 提供的 toFuture() 方法。

以下是一个简单的示例,展示如何将 Single 转换为 Future:

import io.reactivex.rxjava3.core.Single;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class SingleToFutureExample {

    public static void main(String[] args) {
        // 创建一个示例 Single
        Single<String> single = Single.fromCallable(() -> {
            // 模拟一些计算或操作
            Thread.sleep(1000);
            return "Hello from Single";
        });

        // 将 Single 转换为 Future
        Future<String> future = single.toFuture();

        try {
            // 从 Future 中获取结果
            String result = future.get(); // 这个调用会阻塞直到结果可用
            System.out.println("Result: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

关键点
阻塞行为:调用 future.get() 会阻塞当前线程,直到 Single 完成并返回结果。
异常处理:ExecutionException 用于封装在 Single 执行过程中抛出的任何异常。
这种转换方式在需要与使用 Future 的代码进行集成时非常有用,但要注意 Future 的阻塞特性与 RxJava 的非阻塞特性之间的区别。

public class Main {  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
        Single<String> single = Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + "Single  " + Thread.currentThread().getName());  
            Thread.sleep(1000);  
            return "hello world";  
        });  
  
        System.out.println(LocalDateTime.now() + "主线程 1 " + Thread.currentThread().getName());  
        Thread.sleep(2000);  
  
        System.out.println(LocalDateTime.now() + "主线程 2 " + Thread.currentThread().getName());  
        Future<String> future = single.toFuture();  
  
        System.out.println(LocalDateTime.now() + "主线程 3 " + Thread.currentThread().getName());  
        System.out.println(future.get());  
    }  
}
2025-01-17T15:45:58.699主线程 1 main
2025-01-17T15:46:00.702主线程 2 main
2025-01-17T15:46:00.712Single  main
2025-01-17T15:46:01.724主线程 3 main
hello world

请注意这里的结果,这个结果说明了single中的任务实际上是等到tofuture()执行的时候才开始执行,也就是tofuture和订阅动作是一样的效果

操作符

flatmap#

Single.flatMap 是 RxJava 中的一个操作符,用于将一个 Single 的结果转换为另一个 Single。它允许你在异步操作完成后,基于结果执行另一个异步操作,并将最终结果作为一个新的 Single 发射。

Single.flatMap 的意义和用途

  1. 链式异步操作

    • flatMap 允许你将多个异步操作串联在一起。你可以在一个 Single 完成后,使用其结果启动另一个异步操作,并将其结果作为新的 Single 发射。
  2. 动态转换

    • 使用 flatMap,你可以根据第一个 Single 的结果动态地决定接下来的操作。这在需要根据前一个操作的结果来决定后续操作的场景中非常有用。
  3. 避免嵌套回调

    • flatMap 提供了一种优雅的方式来避免嵌套回调(callback hell)。通过将异步操作链式组合,你可以保持代码的简洁和可读性。

示例

假设你有一个场景,需要先从网络获取用户信息,然后基于用户信息获取用户的详细数据:

Single<User> getUserSingle() {
    return Single.fromCallable(() -> {
        // 模拟网络请求获取用户信息
        return new User("userId123");
    });
}

Single<UserDetails> getUserDetailsSingle(String userId) {
    return Single.fromCallable(() -> {
        // 模拟网络请求获取用户详细信息
        return new UserDetails(userId, "John Doe", "johndoe@example.com");
    });
}

getUserSingle()
    .flatMap(user -> getUserDetailsSingle(user.getId()))
    .subscribe(
        userDetails -> System.out.println("User Details: " + userDetails),
        throwable -> System.err.println("Error: " + throwable)
    );

解释

  • getUserSingle():返回一个 Single<User>,模拟从网络获取用户信息。
  • getUserDetailsSingle(String userId):返回一个 Single<UserDetails>,模拟基于用户 ID 获取用户详细信息。
  • flatMap 操作符:在 getUserSingle() 完成后,使用其结果(User 对象)调用 getUserDetailsSingle(user.getId()),并返回一个新的 Single<UserDetails>
  • subscribe:最终订阅结果 Single,处理用户详细信息或错误。

通过 flatMap,你可以轻松地将多个异步操作串联在一起,保持代码的清晰和简洁。

zip#

public class Main {  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        CountDownLatch countDownLatch = new CountDownLatch(1);  
  
        Observable<Integer> observable1 = Observable.create(new ObservableOnSubscribe<Integer>() {  
            @Override  
            public void subscribe(@NonNull ObservableEmitter<Integer> emitter) throws Exception {  
                Thread.sleep(2000);  
                System.out.println("被观察者1发送了事件1");  
                emitter.onNext(1);  
                // 为了方便展示效果,所以在发送事件后加入2s的延迟  
  
                System.out.println("被观察者1发送了事件2");  
                emitter.onNext(2);  
                Thread.sleep(1000);  
  
                System.out.println("被观察者1发送了事件3");  
                emitter.onNext(3);  
                Thread.sleep(1000);  
  
                emitter.onComplete();  
            }  
        }).subscribeOn(Schedulers.io()); // 设置被观察者1在工作线程1中工作  
  
  
        Observable<String> observable2 = Observable.create(new ObservableOnSubscribe<String>() {  
            @Override  
            public void subscribe(@NonNull ObservableEmitter<String> emitter) throws Exception {  
                Thread.sleep(2000);  
                System.out.println("被观察者2发送了事件A");  
                emitter.onNext("A");  
  
                System.out.println("被观察者2发送了事件B");  
                emitter.onNext("B");  
                Thread.sleep(1000);  
  
                System.out.println("被观察者2发送了事件C");  
                emitter.onNext("C");  
                Thread.sleep(1000);  
  
                System.out.println("被观察者2发送了事件D");  
                emitter.onNext("D");  
                Thread.sleep(1000);  
  
                emitter.onComplete();  
            }  
        }).subscribeOn(Schedulers.newThread());// 设置被观察者2在工作线程2中工作  
        // 假设不作线程控制,则该两个被观察者会在同一个线程中工作,即发送事件存在先后顺序,而不是同时发送  
  
  
        // 注:创建BiFunction对象传入的第3个参数 = 合并后数据的数据类型  
        Observable.zip(observable1, observable2, new BiFunction<Integer, String, String>() {  
            @Override  
            public String apply(Integer integer, String string) throws Exception {  
                return integer + string;  
            }  
        }).subscribe(new Observer<String>() {  
            @Override  
            public void onSubscribe(@NonNull Disposable d) {  
                System.out.println("onSubscribe");  
            }  
  
            @Override  
            public void onNext(@NonNull String value) {  
                System.out.println("最终接收到的事件 =  " + value);  
            }  
  
            @Override  
            public void onError(@NonNull Throwable e) {  
                System.out.println("onError");  
            }  
  
            @Override  
            public void onComplete() {  
                System.out.println("onComplete");  
                countDownLatch.countDown();  
            }  
        });  
  
        countDownLatch.await();  
  
    }  
}```

onSubscribe
被观察者1发送了事件1
被观察者2发送了事件A
被观察者1发送了事件2
最终接收到的事件 = 1A
被观察者2发送了事件B
最终接收到的事件 = 2B
被观察者2发送了事件C
被观察者1发送了事件3
最终接收到的事件 = 3C
被观察者2发送了事件D
onComplete


是的,`Single.zip` 会等待所有参与的 `Single` 都完成后,再将它们的结果组合并发射

>工作原理

- **等待所有 `Single` 完成**:`Single.zip` 会订阅所有提供的 `Single`,并等待每一个 `Single` 发射其单个结果
  
- **组合结果**:一旦所有 `Single` 都发射了结果,`zip` 操作符会使用提供的组合函数将这些结果组合成一个新的结果

- **发射组合后的结果**:组合后的结果会被发射给最终的 `Single`,并且这个 `Single` 只会发射一次结果

> 示例

假设你有两个 `Single`,一个发射字符串,另一个发射整数,你希望将它们的结果组合成一个字符串:

```java
Single<String> single1 = Single.fromCallable(() -> {
    // 模拟一些操作
    Thread.sleep(1000);
    return "Hello";
});

Single<Integer> single2 = Single.fromCallable(() -> {
    // 模拟一些操作
    Thread.sleep(2000);
    return 42;
});

Single.zip(single1, single2, (s, i) -> s + " " + i)
    .subscribe(
        result -> System.out.println("Result: " + result),
        throwable -> System.err.println("Error: " + throwable)
    );

解释

  • Single.zip(single1, single2, (s, i) -> s + " " + i)

    • single1single2 是两个 Single,分别发射 "Hello"42
    • (s, i) -> s + " " + i 是一个组合函数,将字符串和整数组合成一个新的字符串。
  • 执行顺序
    - Single.zip 会等待 single1single2 都完成。
    - 一旦两个 Single 都发射了结果,组合函数会被调用,生成 "Hello 42"
    - 最终的 Single 发射组合后的结果。

因此,Single.zip 确保所有参与的 Single 都完成后才会执行组合逻辑,这使得它非常适合需要同时处理多个异步结果的场景。

zipwith#

在RxJava中,SinglezipWith操作符用于将两个Single源组合成一个新的Single。它会在两个Single都成功发射各自的单个项时,通过一个指定的函数将这两个项合并,并发射合并后的结果。

以下是Single.zipWith的基本用法示例:

import io.reactivex.Single;

public class SingleZipWithExample {
    public static void main(String[] args) {
        // 创建两个Single源
        Single<String> single1 = Single.just("Hello");
        Single<String> single2 = Single.just("World");

        // 使用zipWith将两个Single组合
        Single<String> resultSingle = single1.zipWith(single2, (s1, s2) -> s1 + " " + s2);

        // 订阅并打印结果
        resultSingle.subscribe(
            result -> System.out.println("Result: " + result),
            throwable -> System.err.println("Error: " + throwable)
        );
    }
}

解释:

  • Single.just("Hello")Single.just("World")

    • 创建两个Single实例,分别发射字符串"Hello""World"
  • zipWith

    • zipWith方法接受另一个Single和一个BiFunction作为参数。BiFunction用于定义如何合并这两个Single发射的项。在这个例子中,合并函数是一个简单的字符串连接操作。
  • 结果

    • 当两个Single都发射了它们的项后,zipWith会将这两个项传递给合并函数,并发射合并后的结果。在这个例子中,结果是"Hello World"
  • 订阅

    • 通过subscribe方法订阅resultSingle,以便在合并后的结果可用时处理它。这里简单地将结果打印出来。

zipWith操作符非常有用,当你需要同时处理两个异步计算的结果,并将它们组合成一个新的结果时。

异常处理

请求接口超时和出错#

在 RxJava 中处理接口调用的超时问题,可以通过几个不同的操作符来实现。以下是一些常用的方法来处理超时:

1. 使用 timeout 操作符#

timeout 操作符可以用于指定一个时间限制,如果 Observable 在这个时间内没有发射任何数据或终止,它将抛出一个 TimeoutException

Observable<String> apiCallObservable = Observable.fromCallable(() -> {
    // 模拟接口请求
    Thread.sleep(5000); // 假设请求需要5秒
    return "Response from API";
});

apiCallObservable
    .timeout(3, TimeUnit.SECONDS) // 设置超时时间为3秒
    .subscribe(
        response -> System.out.println("Response: " + response),
        throwable -> {
            if (throwable instanceof TimeoutException) {
                System.out.println("Request timed out");
            } else {
                System.out.println("Error: " + throwable.getMessage());
            }
        }
    );

在这个示例中,如果 apiCallObservable 在3秒内没有发射数据,将抛出 TimeoutException,并在 subscribe 的错误处理部分捕获。

2. 使用 retryWhentimeout#

如果你希望在超时后重试请求,可以结合 timeoutretryWhen 操作符。

apiCallObservable
    .timeout(3, TimeUnit.SECONDS)
    .retryWhen(errors -> 
        errors.takeWhile(error -> error instanceof TimeoutException)
              .delay(1, TimeUnit.SECONDS) // 延迟1秒后重试
    )
    .subscribe(
        response -> System.out.println("Response: " + response),
        throwable -> System.out.println("Final Error: " + throwable.getMessage())
    );

在这个示例中,retryWhen 会在遇到 TimeoutException 时重试请求,并在每次重试之间延迟1秒。

3. 使用 onErrorResumeNext 提供默认值#

如果你希望在超时后提供一个默认值,可以使用 onErrorResumeNext

apiCallObservable
    .timeout(3, TimeUnit.SECONDS)
    .onErrorResumeNext(throwable -> {
        if (throwable instanceof TimeoutException) {
            return Observable.just("Default Response");
        }
        return Observable.error(throwable);
    })
    .subscribe(
        response -> System.out.println("Response: " + response),
        throwable -> System.out.println("Error: " + throwable.getMessage())
    );

在这个示例中,如果发生超时,将返回一个默认的响应 "Default Response"

这些方法提供了灵活的方式来处理接口调用中的超时问题,具体选择哪种方法取决于你的应用需求和业务逻辑。

如果调用链中出现了异常怎么办#

public class Main {  
  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        getUserFromDatabase()  
                .flatMap(user -> getUserDetailsFromRemoteService(user))  
                .flatMap(userDetails -> saveUserDetailsToCache(userDetails))  
                .subscribe(  
                        successMessage -> System.out.println("Success: " + successMessage),  
                        error -> System.err.println("Error!!" + error.getMessage())  
                );  
    }  
  
    // 模拟从数据库获取用户信息  
    private static Single<String> getUserFromDatabase() {  
        return Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + " getUserFromDatabase 1:" + Thread.currentThread().getName());  
            // 假设这里可能抛出异常  
            if (Math.random() > 0.5) {  
                throw new Exception("Database error");  
            }  
            return "User123";  
  
        });  
    }  
  
    // 模拟从远程服务获取用户详细信息  
    private static Single<String> getUserDetailsFromRemoteService(String user) {  
        return Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + " getUserDetailsFromRemoteService 2:" + Thread.currentThread().getName());  
            // 模拟远程服务调用  
            System.out.println("Fetching user details for " + user + " from remote service...");  
            // 假设这里可能抛出异常  
            if (Math.random() > 0.5) {  
                throw new Exception("Remote service error");  
            }  
            return "User123 Details";  
  
        });  
    }  
  
    // 模拟将用户详细信息保存到本地缓存  
    private static Single<String> saveUserDetailsToCache(String userDetails) {  
        return Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + " saveUserDetailsToCache 3:" + Thread.currentThread().getName());  
            // 模拟缓存操作  
            System.out.println("Saving " + userDetails + " to cache...");  
            // 假设这里可能抛出异常  
            if (Math.random() > 0.5) {  
                throw new Exception("Cache error");  
            }  
            return "User details saved successfully";  
  
        });  
    }  
}

在调用链的任何一个环节出错了,后面的环节都会直接跳过,直接跳到最后的订阅者的错误处理器,所以在整个任务流中,只需要一个错误处理器就可以了。

如果zip中的某个被观察者出现了异常怎么办#

在 RxJava 中,使用 zip 操作符时,如果其中的一个 Single 抛出了异常,这个异常会导致整个 zip 操作终止,并将异常传递给下游的观察者。具体行为如下:

异常传播:任何一个参与 zip 的 Single 抛出异常时,zip 操作会捕获这个异常,并将其作为 onError 事件传递给下游。
流终止:一旦异常发生,整个 zip 操作会终止,不会继续处理其他 Single 的结果。

以下是一个示例,展示如何处理 zip 操作中的异常:

import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.SingleSource;
import io.reactivex.rxjava3.functions.BiFunction;

public class ZipErrorHandlingExample {

    public static void main(String[] args) {
        Single<String> single1 = getUserFromDatabase();
        Single<String> single2 = getUserDetailsFromRemoteService();

        Single.zip(single1, single2, new BiFunction<String, String, String>() {
            @Override
            public String apply(String user, String userDetails) throws Throwable {
                return user + " - " + userDetails;
            }
        }).subscribe(
            result -> System.out.println("Success: " + result),
            error -> System.err.println("Error: " + error.getMessage()) // 处理异常
        );
    }

    private static Single<String> getUserFromDatabase() {
        return Single.fromCallable(() -> {
            // 模拟可能抛出异常的操作
            if (Math.random() > 0.5) {
                throw new Exception("Database error");
            }
            return "User123";
        });
    }

    private static Single<String> getUserDetailsFromRemoteService() {
        return Single.fromCallable(() -> {
            // 模拟可能抛出异常的操作
            if (Math.random() > 0.5) {
                throw new Exception("Remote service error");
            }
            return "User123 Details";
        });
    }
}

关键点

异常传播:zip 操作符会将任何一个 Single 抛出的异常传递给 onError 回调。
流终止:异常发生后,zip 的执行会终止,不会继续处理其他 Single 的结果。
在实际应用中,处理 zip 中的异常通常是在 subscribe 方法的 onError 回调中进行。通过这种方式,你可以捕获并处理在 zip 操作中发生的任何异常。

背压

在响应式编程中,背压(Backpressure)是一个重要的概念,用于处理生产者(数据源)和消费者(数据处理逻辑)之间的速度不匹配问题。具体来说,背压机制允许消费者以其能够处理的速度来请求数据,从而避免因数据过载而导致的性能问题或系统崩溃。

背压的必要性#

  1. 速度不匹配

    • 在许多应用场景中,数据生产者可能会以比消费者更快的速度生成数据。如果没有控制机制,消费者可能会被淹没在数据流中,导致内存溢出或其他性能问题。
  2. 资源限制

    • 消费者可能受到资源限制(如 CPU、内存、I/O 带宽等)的影响,无法及时处理所有接收到的数据。

背压的实现#

在响应式编程框架中(如 RxJava、Project Reactor),背压可以通过多种方式实现:

  1. 请求-响应模式

    • 消费者可以请求特定数量的数据项,生产者在接收到请求后才发送数据。这种模式允许消费者根据自身的处理能力来控制数据流的速度。
  2. 缓冲

    • 在生产者和消费者之间引入缓冲区,可以暂时存储多余的数据项,缓解瞬时的速度不匹配问题。不过,缓冲区的大小是有限的,仍需谨慎管理。
  3. 丢弃或跳过

    • 当消费者无法及时处理数据时,可以选择丢弃或跳过某些数据项,以防止系统过载。
  4. 批处理

    • 将多个数据项组合成一个批次进行处理,可以减少处理开销,提高效率。

RxJava 中的背压#

在 RxJava 2 中,引入了 Flowable 来支持背压。Flowable 是一种支持背压的 Observable,它提供了多种策略来处理背压问题,如 BUFFERDROPLATESTMISSING 等。

例如,使用 Flowable 可以这样处理背压:

Flowable<Integer> flowable = Flowable.range(1, 1000)
    .onBackpressureBuffer(); // 使用缓冲策略

flowable.subscribe(
    item -> {
        // 模拟处理延迟
        Thread.sleep(10);
        System.out.println("Processed: " + item);
    },
    Throwable::printStackTrace
);

在这个例子中,onBackpressureBuffer 用于在消费者处理速度较慢时暂时缓冲多余的数据项。

背压是响应式编程中一个关键的机制,确保系统在高负载下仍能稳定运行,并有效利用资源。

线程切换

public class Main {  
  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
  
        CountDownLatch countDownLatch = new CountDownLatch(1);  
  
        Single<String> single1 = Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + " Fetching data1 on: " + Thread.currentThread().getName());  
            Thread.sleep(5000); // 模拟耗时操作  
            return "Data1";  
        }).subscribeOn(Schedulers.io());  
  
        Single<String> single2 = Single.fromCallable(() -> {  
            System.out.println(LocalDateTime.now() + " Fetching data2 on: " + Thread.currentThread().getName());  
            Thread.sleep(5000); // 模拟耗时操作  
            return "Data2";  
        }).subscribeOn(Schedulers.io());  
  
        Single.zip(single1, single2, new BiFunction<String, String, String>() {  
                    @Override  
                    public String apply(String data1, String data2) {  
                        System.out.println(LocalDateTime.now() + " Zipping on: " + Thread.currentThread().getName());  
                        return data1 + " & " + data2;  
                    }  
                })                .observeOn(Schedulers.computation())  
                .flatMap(result -> {  
                    System.out.println(LocalDateTime.now() + " Processing on: " + Thread.currentThread().getName());  
                    return Single.just(result.toUpperCase());  
                })  
                .observeOn(Schedulers.newThread())  
                .subscribe(finalResult -> {  
                    System.out.println(LocalDateTime.now() + " Final result on: " + Thread.currentThread().getName());  
                    System.out.println(LocalDateTime.now() + " Result: " + finalResult);  
                    countDownLatch.countDown();  
                });  
  
        // 为了确保程序在所有异步操作完成后再退出  
        countDownLatch.await();  
    }  
}
2025-01-17T11:53:36.417 Fetching data1 on: RxCachedThreadScheduler-1
2025-01-17T11:53:36.417 Fetching data2 on: RxCachedThreadScheduler-2
2025-01-17T11:53:41.428 Zipping on: RxCachedThreadScheduler-1
2025-01-17T11:53:41.429 Processing on: RxComputationThreadPool-1
2025-01-17T11:53:41.431 Final result on: RxNewThreadScheduler-1
2025-01-17T11:53:41.431 Result: DATA1 & DATA2

在这个示例中,single1和single2都使用了Schedulers.io(),它们共享同一个IO线程池。

详细说明:

Schedulers.io():Schedulers.io()是一个用于IO密集型任务的线程池调度器,适用于网络请求、文件读写等不需要占用CPU的操作。
这个调度器会从一个共享的线程池中获取线程,因此多个使用Schedulers.io()的任务可能会在同一个线程池中执行。

线程池行为:虽然它们共享同一个线程池,但不一定在同一个线程上执行。线程池会根据当前的负载和可用线程情况来分配线程。
如果线程池中有空闲线程,两个任务可能会在不同的线程上并发执行。
如果线程池负载较高,任务可能会在同一个线程上顺序执行,具体取决于线程池的实现和当前的系统状态。
在你的示例中,由于两个任务都被调度到IO线程池,它们可能会并发执行,这就是为什么你可能会看到两个任务在不同的线程上打印输出。要验证这一点,可以观察输出中显示的线程名称。如果两个任务在不同的线程上执行,线程名称将会不同。

subscribeOn和observeOn#

在RxJava中,subscribeOn和observeOn是用于控制Observable操作符链中代码执行的线程或调度器的两个重要方法。它们的区别在于它们影响的部分和作用范围不同。

subscribeOn
作用:指定Observable开始执行的线程或调度器。
影响范围:影响整个Observable链中从源头开始的执行线程,也就是说,它决定了数据生成和上游操作符的执行线程。
使用场景:通常用于指定数据产生的线程,比如从网络或数据库中获取数据时,可以指定在IO线程上执行。
示例:

Observable.fromCallable(() -> {
    // 这个操作将在IO线程上执行
    return fetchDataFromNetwork();
})
.subscribeOn(Schedulers.io())
.subscribe(data -> {
    // 处理数据
});

observeOn
作用:指定后续操作符的执行线程或调度器。
影响范围:只影响在它之后的操作符的执行线程。可以多次调用observeOn来切换不同的线程。
使用场景:通常用于指定数据消费的线程,比如在计算完成后切换到主线程更新UI。
示例:

Observable.fromCallable(() -> {
    // 这个操作将在IO线程上执行
    return fetchDataFromNetwork();
})
.subscribeOn(Schedulers.io())
.map(data -> {
    // 这个操作也在IO线程上执行
    return processData(data);
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
    // 这个操作将在主线程上执行
    updateUI(result);
});

总结
subscribeOn:影响数据生成和上游操作符的线程,通常只需要调用一次。
observeOn:影响从调用点开始的后续操作符的线程,可以多次调用以切换不同的线程。
通过合理使用这两个方法,可以有效地管理RxJava流的线程调度,确保在合适的线程上执行合适的任务。

下面是一个使用RxJava的简单示例,展示了subscribeOnobserveOn的区别:

Observable.fromCallable(() -> {
    // 模拟一个耗时操作,比如从网络或数据库获取数据
    System.out.println("Executing on: " + Thread.currentThread().getName());
    return "Hello, World!";
})
.subscribeOn(Schedulers.io()) // 指定订阅操作在IO线程上执行
.observeOn(Schedulers.computation()) // 切换到计算线程进行数据处理
.map(data -> {
    System.out.println("Processing on: " + Thread.currentThread().getName());
    return data.toUpperCase();
})
.observeOn(AndroidSchedulers.mainThread()) // 切换到主线程进行UI更新
.subscribe(result -> {
    // 在主线程上更新UI
    System.out.println("Result on: " + Thread.currentThread().getName());
    System.out.println("Result: " + result);
});

在这个示例中:

  • subscribeOn(Schedulers.io()):指定从网络或数据库获取数据的操作在IO线程上执行。这是数据流的起始点。
  • observeOn(Schedulers.computation()):切换到计算线程进行数据处理,比如将字符串转换为大写。
  • observeOn(AndroidSchedulers.mainThread()):最后切换到主线程,以便安全地更新UI。

输出结果可能类似于:

Executing on: RxCachedThreadScheduler-1
Processing on: RxComputationThreadPool-1
Result on: main
Result: HELLO, WORLD!

这个例子清晰地展示了如何使用subscribeOnobserveOn来控制不同阶段的线程执行。

作者:Esofar

出处:https://www.cnblogs.com/DCFV/p/18677306

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Duancf  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示