理解kotlin limitedParallelism(1)与Actor模型

Dispatcher.kt

复制代码
package com.example.demo.process

import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.incrementAndFetch

object Dispatcher {
    private val logger = KotlinLogging.logger {}

    //获取当前机器的 CPU 核心数,后续所有线程池大小的计算都基于此
    private val cpuCore = Runtime.getRuntime().availableProcessors()

    //使用 AtomicInt 作为线程编号的原子计数器,保证线程命名的唯一性和线程安全。
    @OptIn(ExperimentalAtomicApi::class)
    private val newThreadId: AtomicInt = AtomicInt(0)

    @OptIn(ExperimentalAtomicApi::class)
    private fun createDispatcherThreadPool(name: String, minSize: Int, maxSize: Int): CoroutineDispatcher {
        //核心线程数(下限 ≥1)
        val corePoolSize = minSize.coerceAtLeast(1)

        //最大线程数(范围:corePoolSize ~ 128)
        val maximumPoolSize = maxSize.coerceIn(corePoolSize, 128)

        logger.info { "create dispatcher $name, corePoolSize=$corePoolSize, maximumPoolSize=$maximumPoolSize" }

        return ThreadPoolExecutor(
            corePoolSize,                       //核心线程数,源码:workerCountOf(c) < corePoolSize就会创建线程,所以就算是是:任务一个执行完了来,
            maximumPoolSize,                    // TODO 此参数其实压根不会生效,因为无界队列
            30,                   //回收非核心线程。 源码中: workQueue.poll(30, SECONDS)  ← 限时等待 30 秒, 如果线程没有任务,就会被回收(非核心线程),核心线程不会回收!!!
            TimeUnit.SECONDS,
            LinkedBlockingQueue(),  // 无界队列
        ) { runnable ->
            /*
            线程命名 :格式为 {name}-{自增ID} ,例如 Scheduler-1 、 Actor-3 ,便于 jstack 排查。
            且是守护线程
             */
            Thread(runnable, "$name-${newThreadId.incrementAndFetch()}").apply { isDaemon = true }
        }.asCoroutineDispatcher() //是 Kotlin 协程库( kotlinx.coroutines )提供的一个扩展函数,定义在 java.util.concurrent.Executor 上,用于把 Java 线程池(Executor) 桥接到 Kotlin 协程的调度器体系 中
    }

    // 轻量级调度器,固定 2 个核心线程,适合处理定时任务、延迟任务等轻量操作。
    val Scheduler = createDispatcherThreadPool("Scheduler", 2, cpuCore / 2)

    // 主力执行调度器,至少有 10 个核心线程,承载进程内大部分的业务逻辑执行
    //Dispatcher.Actor.limitedParallelism(1)的意思是:复用这个Actor调度器的底层线程池,然后再创建一个子调度器,并发度是1,这样子
    val Actor = createDispatcherThreadPool("Actor", cpuCore.coerceAtLeast(10), cpuCore * 2)
}

main.kt

复制代码
package com.example.demo

import com.example.demo.process.Dispatcher
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.springframework.boot.autoconfigure.SpringBootApplication
import java.util.concurrent.TimeUnit

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
//    runApplication<DemoApplication>(*args)

    val actorScope = CoroutineScope(CoroutineName("DemoApplication") + Dispatcher.Actor)
    val schedulerScope = CoroutineScope(Dispatcher.Scheduler)


    //------------------------------------test1-------------------------------------
    var count = 0

    actorScope.launch(CoroutineName("TIMER")) {
        for (i in 1..10000) {
            //上下文调度器本来就是多个协程可以启动
            launch {
                count++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println(">>>>>count:${count}")

    //------------------------------------test2-------------------------------------

    var limitedParallelismCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 默认使用父协程上下文(也就是并发度也是1的调度器,起多个协程业没用)
            launch {
                limitedParallelismCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("-----limitedParallelismCounter:${limitedParallelismCounter}")

    //------------------------------------test3-------------------------------------

    var limitedParallelismAnotherCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 换了上下文(也就是又变成多线程了)
            schedulerScope.launch {
                limitedParallelismAnotherCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("=====limitedParallelismAnotherCounter:${limitedParallelismAnotherCounter}")


    //------------------------------------test4-------------------------------------

    var limitedParallelismSameCounter = 0
    actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) {
        for (i in 1..10000) {
            // 其实协程作用阈还是变了
            actorScope.launch {
                limitedParallelismSameCounter++
            }
        }
    }

    TimeUnit.SECONDS.sleep(5)

    println("*****limitedParallelismAnotherCounter:${limitedParallelismSameCounter}")
}

/*
15:22:27.868 [main] INFO com.example.demo.process.Dispatcher -- create dispatcher Scheduler, corePoolSize=2, maximumPoolSize=8
15:22:27.873 [main] INFO com.example.demo.process.Dispatcher -- create dispatcher Actor, corePoolSize=16, maximumPoolSize=32
>>>>>count:9974
-----limitedParallelismCounter:10000
=====limitedParallelismAnotherCounter:9969
*****limitedParallelismAnotherCounter:9962
 */

总结:

复制代码
actorScope.launch(Dispatcher.Actor.limitedParallelism(1) + CoroutineName("limitedParallelism")) 

通过传入协程上下文,限制该协程下最多只有一个协程在运行,这样子实现Actor模型

相关推荐
.千余1 小时前
【C++】C++类与对象3:const成员函数与取地址运算符重载,权限管理的艺术
开发语言·c++
影寂ldy1 小时前
C# 类和对象
开发语言·c#
丷丩1 小时前
MapLibre GL JS第25课:添加栅格瓦片源
开发语言·javascript·gis·mapbox·maplibre gl js
朔北之忘 Clancy2 小时前
2026 年 3 月青少年软编等考 C 语言二级真题解析
c语言·开发语言·c++·学习·青少年编程·题解·考级
plainGeekDev2 小时前
RecyclerView.Adapter → ListAdapter
java·kotlin·gradle
Old Uncle Tom2 小时前
Harness Engineering 综述
java·开发语言·数据库
星原望野2 小时前
JAVA:策略模式的实战使用
java·开发语言·策略模式
码界筑梦坊2 小时前
282-基于Python的豆瓣音乐可视化分析推荐系统
开发语言·python·信息可视化·数据分析·flask·vue
LJianK12 小时前
java多态
java·开发语言·python