JDK8函数编程及JDK9中响应式流

一、前言

Spring5是基于Reactor框架实现响应式流,其中Spring Boot WebFlux是完全依赖reactor-core来实现;直接上手Spring WebFlux,虽然能简单使用,但对其原理未做深入了解,难免会有不少迷惑,故建议从基础出发,按照如下步骤进行学习:
1、掌握JDK8的函数式编程及stream流;

2、学习JDK9的flux的响应式流设计原理及实现机制

3、上手spring Boot webflux(JDK8函数式编程及stream流 + JDK9响应式流 的结合)

二、JDK8中的函数式编程及stream流

jdk8中最大的特点之一就是支持函数式编程及stream流,下面就其中重点做下基本的简介和示例:

(一)lamda表达式

Lambda 表达式,也可称为闭包,本质上来说其允许把函数作为一个方法的参数。

public class LambdaDemo1 {
    public static void main(String[] args) {
    	// 非lambda表达式代码示例
        Runnable thread1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("普通线程");
            }
        };
        new Thread(target).start();

        // jdk8 lambda表达式代码示例
        //返回一个接口,可以使用强转的方式(不过我觉得应该没人这么用)
        Runnable thread2 = (Runnable)() -> System.out.println("ok");
        new Thread(thread2).start();
    }
}

通过上面示例,可看出lambda表单式的很大的一个特点就是把复杂的代码逻辑,变得更为简洁和紧凑。

另外,lambda表达式还有其他的如下写法:

interface InterfaceDemo1 {
    int doubleNum(int i);
    int addNum(int i1, int i2);
}

public class LambdaDemo1 {
    public static void main(String[] args) {
    	// 函数逻辑多行时,需要加大括号
        InterfaceDemo1 i1 = (int i) -> {
            System.out.println("----------");
            return i * 2;
        };
        
    	// 函数逻辑仅为一行时,可省去大括号和return
        InterfaceDemo1 i2 = (int i) -> i * 2;
        
        // 参数可省去类型
        InterfaceDemo1 i3 = (i) -> i * 2;
        
    	// 单个参数时,可以省去括号
    	InterfaceDemo1 i4 = i -> i * 2;
    	
        // 多个参数时,需要加括号
        InterfaceDemo1 i5 = (i1, i2) -> i1 + i2;
    }
}

(二)函数式接口

函数式接口:只有一个方法的interface接口,一般通过添加注解 @FunctionalInterface来标注(不添加注解也可以,但建议加上注解以供编译时检验,加上注解后如果有多个方法会报错)

默认接口: interface接口中使用default关键字标注的实现方法;该方法可以不被实现,也可以被重写,和类中的方法一样,可以在方法使用this调用接口中的方法。

@FunctionalInterface
interface InterfaceDemo2 {
	// jdk8之后建议接口设计尽量的小,一个接口只做一件事,也就是更加便于函数式接口的使用
    int doubleNum(int i);
    // 默认方法
    default int add(int x, int y) {
        return x + y;
    }
}

public class LambdaDemo2 {
    public static void main(String[] args) {
        InterfaceDemo2 i1 = i -> i * 2;
        // 调用接口默认方法
        System.out.println(i1.add(3, 7));
        System.out.println(i1.doubleNum(20));
    }
}

(三)内置函数接口

1、四大核心函数式接口

函数式接口参数类型返回类型方法说明
ConsumerTvoidvoid accept(T t)无返回值,对指定T类型参数进行处理
SupplierTT get()无入参,通过处理返回指定T类型对象
Function<T,R>TRR apply(T)对指定T类型参数进行处理,通过处理返回指定R类型对象
PredicateTbooleanboolean test(T)对指定T类型参数进行处理,判断是否满足某种条件
public class FunctionDemo1 {
    public static void main(String[] args) {
     	// Consumer<T>
        Consumer<String> consumer = s -> System.out.println(s);
        consumer.accept("1");
        
        // Supplier<T>
        Supplier<String> supplier = () -> "2";
        System.out.println(supplier.get());
        
        // Function<T,R>
        Function<Integer, Integer> function = i -> i * 2;
        System.out.println(function.apply(3));
        
        // Predicate<T>
        Predicate<Integer> predicate = i -> i > 0;
        System.out.println(predicate.test(-4));
    }
}
2、 其他演变函数式接口
函数式接口参数类型返回类型方法说明
BiFunction<T,U,R>T,URR apply(T t, U u)对指定T类型和U类型参数进行处理,通过处理返回指定R类型对象
UnaryOperatorTTT apply(T t)对指定T类型参数进行处理,通过处理返回相同T类型对象,
其是Function<T,R>子接口
BinaryOperatorT,TTT apply(T t, T t)对两个指定的T类型参数进行处理,通过处理返回相同T类型对象,
其是BiFunction<T,U,R>子接口
BiConsumer<T,U>T,Uvoidvoid accept(T t,U u)无返回值,对指定T类型和U类型参数进行处理

(四)stream流

Stream流是一个来自数据源的元素队列并支持聚合操作,其有如下三个要素:

  1. 元素:特定类型的对象,基于该类型对象形成一个队列,且Stream流并不存储元素,而是仅按需进行操作;
  2. 数据源: Stream流的数据来源,可以是集合,数组,I/O channel, 产生器generator 等;
  3. 聚合操作:对元素做的简便操作,比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作还有两个基础的特征:

  1. Pipelining:Stream流并不存储元素,而是仅按需进行操作,且中间操作都会返回流对象本身;基于此多个操作可串联起来,形成一个类似管道一样的操作序列;
  2. 内部迭代: Stream流通过访问者模式(Visitor)实现了内部迭代 。

1、 流的创建

来源方法
集合Collection.stream/parallelStream
数组Arrays.stream
数字StreamIntStream/LongStream.range/rangeClosed
Random.ins/longs/doubles
直接创建Stream.generate/interate
public class StreamDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // 集合创建
        list.stream();
        list.parallelStream();

        // 数组创建
        Arrays.stream(new int[]{1, 2, 3});

        // 数字Stream创建
        IntStream.of(1, 2, 3);
        IntStream.rangeClosed(1, 5);

        // 创建一个无限流
        new Random().ints().limit(5);

        // 创建
        Random rd= new Random();
        Stream.generate(rd::nextInt).limit(20);
    }
}

2、流的中间操作

操作分类相关方法
无状态
(有数据存储功能,线程不安全)
map/mapToXxx
flatMap/flatMapToXxx
filter
peek
unordered
有状态
(一次操作,不能保存数据,线程安全)
distinct
sorted
limit/skip

3、流的终止操作

操作分类相关方法
非短路操作
(有数据存储功能,线程不安全)
forEach/forEachOrdered
collect/toArray
reduce
min/max/count
短路操作
(不需要等待所有结果都计算完就可以结束流的操作)
findFirst/findAny
allMatch/anyMatch/noneMatch
public class StreamDemo2 {
    public static void main(String[] args) {
        String str = "Hello world";
        // 迭代器forEach
        // 使用并行流,乱序(效率更高)
        str.chars().parallel().forEach(i -> System.out.print((char)i));
        System.out.println("+++++++++++++++++++");
        // 使用 forEachOrdered保证顺序
        str.chars().parallel().forEachOrdered(i -> System.out.print((char)i));
        System.out.println("+++++++++++++++++++");

        // 收集器collect
        // 收集到集合
        List<String> list = Stream.of(str.split(" ")).collect(Collectors.toList());
        System.out.println(list);
        // 收集到Set
        Set<String> set = Stream.of(str.split(" ")).collect(Collectors.toSet());
        System.out.println(set);

        // 归约reduce,将流中的元素结合起来得到一个值
        // 拼接字符串
        Optional<String> letters = Stream.of(str.split(" ")).reduce((s1, s2) -> s1 + "," + s2);
        // 使用optional进行空判断
        System.out.println(letters.orElse(null));

        // 带初始化值的reduce,默认值也会被算入元素中
        String reduce = Stream.of(str.split(" ")).reduce("java语言:", (s1, s2) -> s1 + "," + s2);
        System.out.println(reduce);

        // 计算所有单词总长度
        Integer length = Stream.of(str.split(" ")).map(String::length).reduce(0, (s1, s2) -> s1 + s2);
        System.out.println(length);

        // max的使用,找出最长的单词
        Optional<String> maxLengthWorld = Stream.of(str.split(" ")).max(Comparator.comparingInt(String::length));
        System.out.println(maxLengthWorld.orElse(null));
        // min的使用,找出最短的单词
        Optional<String> minLengthWorld = Stream.of(str.split(" ")).min(Comparator.comparingInt(String::length));
        System.out.println(minLengthWorld.orElse(null));
        // count的使用,统计有多少个单词
        long count = Stream.of(str.split(" ")).count();
        System.out.println(count);

        // 使用 allMatch判断是否所有单词长度都大于1,返回boolean
        // anyMatch和noneMatch用法差不多,都是返回boolean
        boolean all = Stream.of(str.split(" ")).anyMatch(s -> s.length() > 1);
        System.out.println(all);

        // 使用 findFirst 短路操作
        OptionalInt findFirst = new Random().ints().findFirst();
        System.out.println(findFirst.orElse(0));
        // 使用 findAny 短路操作
        OptionalInt findAny = new Random().ints().findAny();
        System.out.println(findAny.orElse(0));
    }
}

三、JDK9中的响应式流

JDK9中的响应式流与JDK8中的函数式编程及stream流其实没有什么关系,其是依据Reactive Streams—响应式流规范而实现的,本质是一个发布-订阅模式的实现,其中核心类为java.util.concurrent.Flow,其中定义了Publisher、Subscriber、Subscription、Proccessor等核心响应式编程接口。

(一)简单demo

相关依赖:

JDK:1.9 以上
maven依赖:lombok
1、定义消费者Subscriber对象
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Flow;

// 实现java.util.concurrent.Flow.Subscriber
@Slf4j
public class BizSubscriber implements Flow.Subscriber<Integer> {

    private Flow.Subscription subscription;

    @Override
    public void onSubscribe(Flow.Subscription subscription) {
    	// 绑定Subscription
        this.subscription = subscription;
        // 通过Subscription向Publisher请求数据
        this.subscription.request(1);
    }

    @Override
    public void onNext(Integer item) {
        log.info("subscriber 接收数据:" + item);
        // TODO 
        // do some biz things ……
        
        // 处理完成后,重新通过Subscription向Publisher请求数据
        this.subscription.request(1);
    }

    @Override
    public void onError(Throwable throwable) {
        log.error("subscriber 处理错误", throwable);
        // 处理发生异常时的错误处理
        // TODO
    	// do some things when error
        
        this.subscription.cancel();
    }

    @Override
    public void onComplete() {
    	log.info("subscriber 完成数据处理!!");
    	// 处理完成后的业务逻辑处理
    	// TODO
    	// do some things when complete
    }
}
2、demo代码
import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;

public class FlowTest {
    public static void main(String[] args) throws Exception {
		// 1、定义发布者实例,这里直接使用jdk自带的SubmissionPublisher(实现了Publisher接口),同时定义发布的数据类型式Integer
        SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
        
        // 2、定义消费者实例
        Flow.Subscriber subscriber = new BizSubscriber();
        
        // 3、建立发布-订阅消费绑定关系
        publisher.subscribe(subscriber);

		// 4、发布者开始发布数据
        publisher.submit(1);
        publisher.submit(2);
        publisher.submit(3);
        publisher.submit(4);

		// 5、发布者发布完成后关闭
        publisher.close();

		// 这里为了演示需要,设置主线程延迟停止(否则发布者发布的数据还没有消费完,就可能会退出)
        Thread.currentThread().join(1000);
    }
}

最后执行接口如下:

16:10:15.589 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:1
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:2
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:3
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 接收数据:4
16:10:15.593 [ForkJoinPool.commonPool-worker-3] INFO com.abinge.boot.staging.flow.BizSubscriber - subscriber 完成数据处理!!

(二)源码解析

1、时序图示

下面我们结合上面的例子,深入到JDK的源码中查看整个链路的执行过程,我这里大致梳理了整体执行时序如下图所示:

jdk9____________

通过上面图示我们可以总结出如下内容:

1、整个响应式流的逻辑核心处理逻辑是在Subscription中的;

2、Subscription的初始化工作是由Publisher来控制的,具体是在与subscriber创建绑定关系时做的;

3、Publisher真正提交数据时,也就是submit数据时,Subsciber才会真正的通过request方法请求到数据,并真实的消费处理数据。

2、关键步骤源码跟踪
(1)发布者定义
// 1、定义发布者实例,这里直接使用jdk自带的SubmissionPublisher(实现了Publisher接口),同时定义发布的数据类型式Integer
SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();
// 这里做了两件事情:
// 1、设置Publisher异步执行线程池executor=ASYNC_POOL,最终默认线程池是ForkJoinPool.commonPool()
// 2、设置最大的缓存空间(也就是背压容量)为Flow.defaultBufferSize(),默认为256

// java.util.concurrent.SubmissionPublisher#SubmissionPublisher()
public SubmissionPublisher() {
    this(ASYNC_POOL, Flow.defaultBufferSize(), null);
}

// 最终执行的SubmissionPublisher的构造方法
// java.util.concurrent.SubmissionPublisher#SubmissionPublisher(java.util.concurrent.Executor, int, java.util.function.BiConsumer<? super java.util.concurrent.Flow.Subscriber<? super T>,? super java.lang.Throwable>)
public SubmissionPublisher(Executor executor, int maxBufferCapacity,
                               BiConsumer<? super Subscriber<? super T>, ? super Throwable> handler) {
        if (executor == null)
            throw new NullPointerException();
        if (maxBufferCapacity <= 0)
            throw new IllegalArgumentException("capacity must be positive");
        this.executor = executor;
        this.onNextHandler = handler;
        this.maxBufferCapacity = roundCapacity(maxBufferCapacity);
}

// ASYNC_POOL的初始逻辑
private static final Executor ASYNC_POOL =
        (ForkJoinPool.getCommonPoolParallelism() > 1) ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
(2)发布者与消费者绑定订阅关系
// 3、建立发布-订阅消费绑定关系
publisher.subscribe(subscriber);
// java.util.concurrent.SubmissionPublisher#subscribe
public void subscribe(Subscriber<? super T> subscriber) {
        if (subscriber == null) throw new NullPointerException();
        // 1、初始化背压容器,设置背压容器的最大容量
        int max = maxBufferCapacity; // allocate initial array
        Object[] array = new Object[max < INITIAL_CAPACITY ?
                                    max : INITIAL_CAPACITY];
        
        // 2、初始化绑定关系Subscription实例,也就是BufferedSubscription实例         
        BufferedSubscription<T> subscription =
            new BufferedSubscription<T>(subscriber, executor, onNextHandler,
                                        array, max);
                                        
        // 3、同步操作,这里就标明Publisher的subscribe方法是阻塞执行                      
        synchronized (this) {
            if (!subscribed) {
                subscribed = true;
                owner = Thread.currentThread();
            }
            // 4、这里是死循环会一直尝试执行,直到发生错误或执行完成
            for (BufferedSubscription<T> b = clients, pred = null;;) {
                if (b == null) {
                	// 5、首次进来,b = clients 为null,开始执行8、subscription.onSubscribe()方法
                    Throwable ex;
                    subscription.onSubscribe();
                    if ((ex = closedException) != null)
                        subscription.onError(ex);
                    else if (closed)
                        subscription.onComplete();
                    else if (pred == null)
                        clients = subscription;
                    else
                        pred.next = subscription;
                    break;
                }
                BufferedSubscription<T> next = b.next;
                if (b.isClosed()) {   // remove
                    b.next = null;    // detach
                    if (pred == null)
                        clients = next;
                    else
                        pred.next = next;
                }
                else if (subscriber.equals(b.subscriber)) {
                	// 6、发生错误后,停止死循环
                    b.onError(new IllegalStateException("Duplicate subscribe"));
                    break;
                }
                else
                    pred = b;
                    
                // 7、执行下一个存在绑定关系(也就是下一个消费者实例)
                b = next;
            }
        }
    }
    
// 8、java.util.concurrent.SubmissionPublisher.BufferedSubscription#onSubscribe
final void onSubscribe() {
    startOnSignal(RUN | ACTIVE);
}

// 9、java.util.concurrent.SubmissionPublisher.BufferedSubscription#startOnSignal
final void startOnSignal(int bits) {
      if ((ctl & bits) != bits &&
          (getAndBitwiseOrCtl(bits) & (RUN | CLOSED)) == 0)
          tryStart();
}

// 10、java.util.concurrent.SubmissionPublisher.BufferedSubscription#tryStart
// 这里开始异步执行具体的任务
final void tryStart() {
      try {
          Executor e;
          // ConsumerTask的初始化逻辑详见下面11
          // 这里的this就是上面2、中创建的BufferedSubscription实例 
          ConsumerTask<T> task = new ConsumerTask<T>(this);
          if ((e = executor) != null)   // skip if disabled on error
              e.execute(task);
       } catch (RuntimeException | Error ex) {
          getAndBitwiseOrCtl(ERROR | CLOSED);
           throw ex;
       }
}

// 11、java.util.concurrent.SubmissionPublisher.ConsumerTask
// 这里是ConsumerTask的初始化方法,其中consumer就是上面10中的this,也就是2、中创建的BufferedSubscription实例
// 通过该类的run方法,我们可以看到其最终执行的是BufferedSubscription实例的consume方法
static final class ConsumerTask<T> extends ForkJoinTask<Void>
        implements Runnable, CompletableFuture.AsynchronousCompletionTask {
        final BufferedSubscription<T> consumer;
        ConsumerTask(BufferedSubscription<T> consumer) {
            this.consumer = consumer;
        }
        public final Void getRawResult() { return null; }
        public final void setRawResult(Void v) {}
        public final boolean exec() { consumer.consume(); return false; }
        public final void run() { consumer.consume(); }
}

// 12、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consume
// 这里是整个响应式流背压执行的核心逻辑,其中head、tail等都是对array背压容量的相关位置记录
final void consume() {
			// 13、注意这里的s,此处设值为subscriber,也就是2、中初始化时我们创建绑定关系的那个消费者BizSubscriber实例
            Subscriber<? super T> s;
            if ((s = subscriber) != null) {          // hoist checks
            	// 14、这里开始传入消费者实例,确定与消费者之间的绑定关系,并会执行subscriber的onSubscribe方法
                subscribeOnOpen(s);
                long d = demand;
                
                // 15、这里又是一个死循环,不断是对背压容器array的遍历操作执行
                for (int h = head, t = tail;;) {
                    int c, taken; boolean empty;
                    if (((c = ctl) & ERROR) != 0) {
                        closeOnError(s, null);
                        break;
                    }
                    // 15-1、首次进来,会执行takeItems方法,其中把subscriber消费者实例传递了进去,具体详见15-2
                    // 在Publisher尚未submit提交数据时,taker永远是0,不会真实的执行subscriber中的逻辑
                    else if ((taken = takeItems(s, d, h)) > 0) {
                        head = h += taken;
                        d = subtractDemand(taken);
                    }
                    else if ((d = demand) == 0L && (c & REQS) != 0)
                        weakCasCtl(c, c & ~REQS);    // exhausted demand
                    else if (d != 0L && (c & REQS) == 0)
                        weakCasCtl(c, c | REQS);     // new demand
                    else if (t == (t = tail)) {      // stability check
                        if ((empty = (t == h)) && (c & COMPLETE) != 0) {
                            closeOnComplete(s);      // end of stream
                            break;
                        }
                        else if (empty || d == 0L) {
                            int bit = ((c & ACTIVE) != 0) ? ACTIVE : RUN;
                            if (weakCasCtl(c, c & ~bit) && bit == RUN)
                                break;               // un-keep-alive or exit
                        }
                    }
                }
            }
        }
        
// 14-1、java.util.concurrent.SubmissionPublisher.BufferedSubscription#subscribeOnOpen
// 此处的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final void subscribeOnOpen(Subscriber<? super T> s) {
    if ((ctl & OPEN) == 0 && (getAndBitwiseOrCtl(OPEN) & OPEN) == 0)
        consumeSubscribe(s);
}

// 14-2、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consumeSubscribe
// 此处的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final void consumeSubscribe(Subscriber<? super T> s) {
    try {
        if (s != null) // ignore if disabled
        	// 此处开始执行subscriber的onSubscribe方法
            s.onSubscribe(this);
    } catch (Throwable ex) {
        closeOnError(s, ex);
    }
}

// 14-3、com.abinge.boot.staging.flow.BizSubscriber#onSubscribe
public void onSubscribe(Flow.Subscription subscription) {
    // 绑定Subscription
    this.subscription = subscription;
    // 通过Subscription向Publisher请求数据
    this.subscription.request(1);
}

// 14-4、java.util.concurrent.SubmissionPublisher.BufferedSubscription#request
public final void request(long n) {
    if (n > 0L) {
        for (;;) {
            long p = demand, d = p + n;  // saturate
            if (casDemand(p, d < p ? Long.MAX_VALUE : d))
                break;
        }
        // 最终仍会执行到最初的startOnSignal方法
        startOnSignal(RUN | ACTIVE | REQS);
    }
    else
        onError(new IllegalArgumentException(
                    "non-positive subscription request"));
}


// 15-2、java.util.concurrent.SubmissionPublisher.BufferedSubscription#takeItems
// 这里的入参s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final int takeItems(Subscriber<? super T> s, long d, int h) {
    Object[] a;
    int k = 0, cap;
    // 在Publisher尚未submit提交数据时,k永远是0,不会继续执行到15-4中去真实的执行subscriber中的逻辑
    if ((a = array) != null && (cap = a.length) > 0) {
        int m = cap - 1, b = (m >>> 3) + 1; // min(1, cap/8)
        int n = (d < (long)b) ? (int)d : b;
        // 15-3、这里又是一个死循环,仍然是对背压容器array的相关操作
        for (; k < n; ++h, ++k) {
            Object x = QA.getAndSet(a, h & m, null);
            if (waiting != 0)
                signalWaiter();
            if (x == null)
                break;
            //  15-4、首次执行,会进入到consumeNext方法, 其中s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
            else if (!consumeNext(s, x))
                break;
        }
    }
    return k;
}

// 15-5、java.util.concurrent.SubmissionPublisher.BufferedSubscription#consumeNext
// 这里s就是2、中初始化时创建绑定关系的那个消费者BizSubscriber实例
final boolean consumeNext(Subscriber<? super T> s, Object x) {
    try {
        @SuppressWarnings("unchecked") T y = (T) x;
        if (s != null)
        // 15-6、此处会去真正的执行BizSubscriber实例的onNext方法
            s.onNext(y);
        return true;
    } catch (Throwable ex) {
        handleOnNext(s, ex);
        return false;
    }
}

// 15-6、com.abinge.boot.staging.flow.BizSubscriber#onNext
// 这里就会去执行我们自定义的BizSubscriber实例的onNext方法,其中可包含具体的业务逻辑,执行完成后再次向Publisher请求数据
public void onNext(Integer item) {
    log.info("subscriber 接收数据:" + item);
    // TODO 
    // do some biz things ……
    
    // 处理完成后,重新通过Subscription向Publisher请求数据,具体可参见14-4
    this.subscription.request(1);
}
(3)Publisher真实的提交数据
// 4、发布者开始发布数据
publisher.submit(1);
publisher.submit(2);
publisher.submit(3);
publisher.submit(4);

此处源码跟踪可自行查询,其大致逻辑是把item放入背压容器array中,然后执行10、java.util.concurrent.SubmissionPublisher.BufferedSubscription#tryStart方法,真实的启动subscriber的相应业务逻辑。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值