CountDownLatch 也叫闭锁,使得一(多)个主线程必须等待其他线程完成操作后再执行。 使用的方式是:CountDownLatch 内部维护一个计数器,主线程先执行 await 方法,如果此时计数器大于 0,则阻塞等待。当一个线程完成任务后,计数器值减 1。直到计数器为 0 时,表示所有的线程已经完成任务,等待的主线程被唤醒继续执行。 应用场景:应用程序的主线程希望在负责启动框架服务的线程已经完成之后再执行。
在广告的核心引擎中,我们的服务需要加载很多缓存数据,加载完成之后,主线程才能启动对外提供服务。这个时候我们就用到了 CountDownLatch 来定时加载缓存。缓存加载的东西我们之后再单独开帖子讲,这里先看 CountDownLatch 的使用。
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();
}
//加载 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
最近在刷题,有一起的可以加手撕代码群:805423079
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.