You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin + Spring Boot 如何为Map缓存添加TTL自动过期机制

Kotlin 实现带TTL自动过期的缓存Map方案

根据你的需求,分为两种常见场景提供对应实现:全局定时清空缓存、单条记录按写入时间自动过期。

场景1:全局每小时统一清空所有缓存

如果你只需要整份缓存每小时重置一次,实现最简单,不需要额外封装:

import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

private var labelsForGroupId: Map<GroupId, Label> = emptyMap()

init {
    // 初始化定时任务,首次延迟1小时执行,之后每隔1小时执行一次清空
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
        { labelsForGroupId = emptyMap() },
        1,
        1,
        TimeUnit.HOURS
    )
}

场景2:单条记录写入后1小时单独过期

如果需要每条缓存按自己的写入时间计算过期时间,推荐以下三种实现:

方案A:无第三方依赖的轻量实现

不需要引入额外库,兼顾惰性清理(访问时判断过期)和定期清理(避免冷数据占内存):

import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

class TTLCache<K, V>(private val ttlMillis: Long) {
    // 存储结构:键 -> (值, 过期时间戳)
    private val cache = ConcurrentHashMap<K, Pair<V, Long>>()

    init {
        // 启动定时清理任务,间隔为TTL时长
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
            ::clearExpired,
            ttlMillis,
            ttlMillis,
            TimeUnit.MILLISECONDS
        )
    }

    // 写入缓存
    fun put(key: K, value: V) {
        cache[key] = value to (System.currentTimeMillis() + ttlMillis)
    }

    // 读取缓存,过期返回null
    fun get(key: K): V? {
        val (value, expireAt) = cache[key] ?: return null
        return if (System.currentTimeMillis() > expireAt) {
            cache.remove(key)
            null
        } else {
            value
        }
    }

    // 清理所有过期缓存
    private fun clearExpired() {
        val now = System.currentTimeMillis()
        cache.entries.removeIf { now > it.value.second }
    }

    // 手动清空所有缓存
    fun clearAll() {
        cache.clear()
    }
}

使用方式:

// 初始化1小时TTL的缓存
private val labelsForGroupId = TTLCache<GroupId, Label>(3600 * 1000)

// 写入缓存
labelsForGroupId.put(groupId, label)

// 读取缓存
val label = labelsForGroupId.get(groupId)

方案B:JVM环境用Guava Cache(生产级实现)

如果允许引入第三方依赖,Guava Cache是经过工业验证的成熟缓存实现,自带TTL、容量限制、LRU淘汰等能力,不需要自己造轮子:

import com.google.common.cache.CacheBuilder
import java.util.concurrent.TimeUnit

private val labelsForGroupId = CacheBuilder.newBuilder()
    .expireAfterWrite(1, TimeUnit.HOURS) // 写入后1小时过期
    .maximumSize(1000) // 可选:配置最大缓存容量,避免内存溢出
    .build<GroupId, Label>()

使用方式:

// 写入
labelsForGroupId.put(groupId, label)
// 读取,过期返回null
val label = labelsForGroupId.getIfPresent(groupId)

方案C:Kotlin协程栈适配实现

如果你的项目用了Kotlin协程,可以用协程实现定时任务,更符合协程的生命周期管理:

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.ConcurrentHashMap

class CoroutineTTLCache<K, V>(
    private val ttlMillis: Long,
    private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) {
    private val cache = ConcurrentHashMap<K, Pair<V, Long>>()

    init {
        scope.launch {
            while (true) {
                delay(ttlMillis)
                clearExpired()
            }
        }
    }

    fun put(key: K, value: V) {
        cache[key] = value to (System.currentTimeMillis() + ttlMillis)
    }

    fun get(key: K): V? {
        val (value, expireAt) = cache[key] ?: return null
        return if (System.currentTimeMillis() > expireAt) {
            cache.remove(key)
            null
        } else {
            value
        }
    }

    private fun clearExpired() {
        val now = System.currentTimeMillis()
        cache.entries.removeIf { now > it.value.second }
    }
}

注意事项

  • Android端开发建议关闭全局定时清理逻辑,仅保留访问时的惰性过期判断,避免后台线程长期运行导致耗电
  • 如果对数据一致性要求高,可以在每次读取接口前判断缓存是否过期,过期后主动拉取新数据
  • 高并发场景下推荐使用Guava Cache等成熟实现,避免自写缓存的并发安全问题

内容的提问来源于stack exchange,提问作者vukojevicf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 12:09:03