V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Acceml
V2EX  ›  Java

[ Java ] CountDownLatch 运用场景(1)

  •  
  •   Acceml ·
    Acceml · 2018-08-28 21:54:41 +08:00 · 2075 次点击
    这是一个创建于 2311 天前的主题,其中的信息可能已经有所发展或是发生改变。

    基本功能

    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. 如果你是工作的小伙伴,你们的服务缓存有很好的管理机制吗? 可以参考下我们的实现方法。

    热门阅读


    最近在刷题,有一起的可以加手撕代码群:805423079 Leetcode 名企之路

    3 条回复    2018-08-29 09:42:12 +08:00
    skypyb
        1
    skypyb  
       2018-08-29 09:13:18 +08:00
    闭锁啊..用过几次,一般都是必须等待某一些线程 /任务执行完毕然后进行一些操作才用得上把 = =
    Acceml
        2
    Acceml  
    OP
       2018-08-29 09:15:02 +08:00
    @skypyb 缓存加载就是这么一个典型场景啊。网上举的例子都是什么开会要等待会议人员到场啥的,我觉得还是用一个生产环境的例子比较好。
    skypyb
        3
    skypyb  
       2018-08-29 09:42:12 +08:00
    @Acceml 闭锁的使用还是比较简单的。说的简单点,在线程 /任务类中接收一个闭锁,在线程执行完毕或者 finally 块里面调用 countDown()。
    需要等待其余线程执行完毕的主线程使用 await()等待闭锁打开就行了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5519 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 07:37 · PVG 15:37 · LAX 23:37 · JFK 02:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.