【java】CountDownLatch运用场景(1)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/creazierHIT/article/details/82155081

作者: 码蹄疾
毕业于哈尔滨工业大学。 小米广告第三代广告引擎的设计者、开发者;
负责小米应用商店、日历、开屏广告业务线研发;
主导小米广告引擎多个模块重构;
关注推荐、搜索、广告领域相关知识;

基本功能

CountDownLatch也叫闭锁,使得一(多)个主线程必须等待其他线程完成操作后再执行。
使用的方式是:CountDownLatch内部维护一个计数器,主线程先执行await方法,如果此时计数器大于0,则阻塞等待。当一个线程完成任务后,计数器值减1。直到计数器为0时,表示所有的线程已经完成任务,等待的主线程被唤醒继续执行。
应用场景:应用程序的主线程希望在负责启动框架服务的线程已经完成之后再执行。

应用:缓存加载

在广告的核心引擎中,我们的服务需要加载很多缓存数据,加载完成之后,主线程才能启动对外提供服务。这个时候我们就用到了CountDownLatch来定时加载缓存。缓存加载的东西我们之后再单独开帖子讲,这里先看CountDownLatch的使用。

  • 定义加载缓存的job抽象类
public abstract class BaseCacheUpdateJob {
    //job的名字
    public String name() {
        return this.getClass().getSimpleName();
    }
    //job的执行周期
    public long getPeriodInSecond() {
        return PERIOD_ONE_HOUR;
    }
    //job的重要性
    public boolean isEssential() {
        return false;
    }
    //job的具体内容
    public abstract boolean update();
}
  • 实现需要的job
//加载App数据的cache.
@Component
public class AppCache extends BaseCacheUpdateJob {

    private Map<String, String> map = new HashMap<>();

    @Autowired
    public AppCache() {
    }

    @Override
    public long getPeriodInSecond() {
        return PERIOD_ONE_MINUTE;
    }

    public String getValueByKey(String appId) {
        return map.getOrDefault(appId, "not find in appCache");
    }

    @Override
    public boolean update() {
        map.put("add", "0");
        return true;
    }
}

//加载广告数据的cache.
@Component
public class AdCache extends BaseCacheUpdateJob {

    private Map<String, String> map = new HashMap<>();

    @Autowired
    public AdCache() {
    }

    @Override
    public long getPeriodInSecond() {
        return PERIOD_ONE_MINUTE;
    }

    public String getValueByKey(String appId) {
        return map.getOrDefault(appId, "not find in AdCache");
    }

    @Override
    public boolean update() {
        map.put("add", "0");
        return true;
    }
}

// 加载用户画像的cache
// 加载Ctr预估模型的cache
// 加载黑白名单的cache
// 加载配置项的cache
// ...
  • 开始加载缓存

上面两步我们定义好了我们服务启动的时候需要干什么事情,那么具体怎么干,就交给了CountDownLatch

@Service
@Slf4j
public final class InterCacheService { 
    //这里spring的自动注入会把定义好的Bean全部注入进来内存
    @Autowired
    private List<BaseCacheUpdateJob> cacheUpdateJobs;

    @PostConstruct
    private void start() {
        //定义线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(cacheUpdateJobs.size());
        CountDownLatch completeTaskLatch = new CountDownLatch(cacheUpdateJobs.size());

        for(BaseCacheUpdateJob job : cacheUpdateJobs) {
            boolean loadStatus = job.update();
            if (loadStatus) {
                countDownLatch.countDown();
            }
        }
        //阻塞住,等待上面的加载完,才会执行主线程.
        completeTaskLatch.await();
        //缓存加载到内存中了,主线程可以继续加载其他bean,完成之后提供服务.
    }
}

这里只是举个例子,简化了很多代码,这种代码肯定不能在生产环境跑的,这么跑肯定会出问题的。

比如:

1.考虑这么一个场景,如果缓存是一个不那么重要的,你的服务其实是可以起来的。那么如何管理这种状态呢?

2.还有可能出现缓存依赖的问题,加载AdCache需要依赖于AppCache,加载AppCache需要依赖BlackListCache,怎么管理这种状态呢?

3.缓存没加载成功,我什么时候去尝试呢?隔多久?

4.缓存都在同一个时间点去加载,导致我线上的GC压力比较大怎么办?

5.缓存一般是多线程访问的公共资源,那么怎么在线程安全和性能之间做取舍呢?

我后面单独开几篇帖子讲缓存,有兴趣的小伙伴可以先看下这个小框架的源码。

源码

https://github.com/Acceml/local_cache_manager

  1. 如果你校招/1-3年的社招,简历上没有项目的话,把这个项目吃透,可以拿出去和面试官吹牛逼的。前提是你理解我们为什么这么写。这个可比你写个什么爬虫有技术含量得多。
  2. 如果你是工作的小伙伴,你们的服务缓存有很好的管理机制吗? 可以参考下我们的实现方法。

热门阅读

扫码关注.jpg

猜你喜欢

转载自blog.csdn.net/creazierHIT/article/details/82155081