jetcache
jetcache copied to clipboard
CacheResult 线程安全问题
背景:我们自己实现了 CacheMonitor 对缓存读写事件进行监听统计。压测时发现有空指针问题。 排查问题: CacheResult 的属性读写都没有做线程安全防护,正常情况下只有缓存读写的主线程会访问这个对象实例。但是有个CacheMonitor接口,CacheEvent的实现类中会透传CacheResult(例如 CacheGetEvent)。此时若CacheMonitor实现类内部另起监听线程,那么就会存在多个线程同时读写CacheResult的场景,引发线程安全问题。
我们的解决方式是对 CacheResult#waitForResult 方法加synchronized..
public void waitForResult(Duration timeout) { synchronized (this) { if (resultCode != null) { return; } try { ResultData resultData = future.toCompletableFuture().get( timeout.toMillis(), TimeUnit.MILLISECONDS); fetchResultSuccess(resultData); } catch (TimeoutException | InterruptedException | ExecutionException e) { fetchResultFail(e); } } }
感觉最好的解决方式应该是 CacheEvent 这个事件中传递的是一个final的只读结果,而不是一个有可能为中间态且与主线程共享的对象
具体错误是什么?可能是有的地方没有加volatile
具体错误是什么?可能是有的地方没有加volatile
-
第一个报错的空指针在 MultiLevelCache#do_GET -> #unwrapHolder -> Objects.requireNonNull(h);
-
另外一个是缓存返回null(期望值肯定是非空) 具体原因就是CacheMonitor 消费event的时间比主线程早,导致isSuccess() == true,但是value还没set完成。
根本原因是 CacheGetResult 的 resultCode, value, holder 属性读写不是原子的,并发就有问题,类似读写锁解决的问题场景。除了synchronized,我也对这几个参数加了volatile。
另外 CacheResult#isSuccess,#getResultCode 等方法不仅仅是一个get方法,里面有#waitForResult这种写操作,这就比较坑了,实现CacheMonitor时一旦调用了这些方法又不知道内部实现的话肯定踩坑。
我大概知道怎么回事了,过两天修一下。 这个地方可以不加synchronized,就是稍微麻烦点。
已经修好了,你用最新的master试下?