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
的意义和用途
-
链式异步操作:
flatMap
允许你将多个异步操作串联在一起。你可以在一个Single
完成后,使用其结果启动另一个异步操作,并将其结果作为新的Single
发射。
-
动态转换:
- 使用
flatMap
,你可以根据第一个Single
的结果动态地决定接下来的操作。这在需要根据前一个操作的结果来决定后续操作的场景中非常有用。
- 使用
-
避免嵌套回调:
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)
:single1
和single2
是两个Single
,分别发射"Hello"
和42
。(s, i) -> s + " " + i
是一个组合函数,将字符串和整数组合成一个新的字符串。
-
执行顺序:
-Single.zip
会等待single1
和single2
都完成。
- 一旦两个Single
都发射了结果,组合函数会被调用,生成"Hello 42"
。
- 最终的Single
发射组合后的结果。
因此,Single.zip
确保所有参与的 Single
都完成后才会执行组合逻辑,这使得它非常适合需要同时处理多个异步结果的场景。
zipwith#
在RxJava中,Single
的zipWith
操作符用于将两个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. 使用 retryWhen
和 timeout
#
如果你希望在超时后重试请求,可以结合 timeout
和 retryWhen
操作符。
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)是一个重要的概念,用于处理生产者(数据源)和消费者(数据处理逻辑)之间的速度不匹配问题。具体来说,背压机制允许消费者以其能够处理的速度来请求数据,从而避免因数据过载而导致的性能问题或系统崩溃。
背压的必要性#
-
速度不匹配:
- 在许多应用场景中,数据生产者可能会以比消费者更快的速度生成数据。如果没有控制机制,消费者可能会被淹没在数据流中,导致内存溢出或其他性能问题。
-
资源限制:
- 消费者可能受到资源限制(如 CPU、内存、I/O 带宽等)的影响,无法及时处理所有接收到的数据。
背压的实现#
在响应式编程框架中(如 RxJava、Project Reactor),背压可以通过多种方式实现:
-
请求-响应模式:
- 消费者可以请求特定数量的数据项,生产者在接收到请求后才发送数据。这种模式允许消费者根据自身的处理能力来控制数据流的速度。
-
缓冲:
- 在生产者和消费者之间引入缓冲区,可以暂时存储多余的数据项,缓解瞬时的速度不匹配问题。不过,缓冲区的大小是有限的,仍需谨慎管理。
-
丢弃或跳过:
- 当消费者无法及时处理数据时,可以选择丢弃或跳过某些数据项,以防止系统过载。
-
批处理:
- 将多个数据项组合成一个批次进行处理,可以减少处理开销,提高效率。
RxJava 中的背压#
在 RxJava 2 中,引入了 Flowable
来支持背压。Flowable
是一种支持背压的 Observable
,它提供了多种策略来处理背压问题,如 BUFFER
、DROP
、LATEST
、MISSING
等。
例如,使用 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的简单示例,展示了subscribeOn
和observeOn
的区别:
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!
这个例子清晰地展示了如何使用subscribeOn
和observeOn
来控制不同阶段的线程执行。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?