diff --git a/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java b/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java index 555cb75..4315ea9 100644 --- a/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java +++ b/src/main/java/net/lamgc/cgj/bot/cache/CacheStoreCentral.java @@ -12,6 +12,8 @@ import net.lamgc.cgj.exception.HttpRequestException; import net.lamgc.cgj.pixiv.PixivDownload; import net.lamgc.cgj.pixiv.PixivSearchLinkBuilder; import net.lamgc.cgj.pixiv.PixivURL; +import net.lamgc.cgj.util.Locker; +import net.lamgc.cgj.util.LockerMap; import net.lamgc.cgj.util.URLs; import net.lamgc.utils.encrypt.MessageDigestUtils; import net.lz1998.cq.utils.CQCode; @@ -51,6 +53,8 @@ public final class CacheStoreCentral { central = new CacheStoreCentral(); } + private final LockerMap lockerMap = new LockerMap<>(); + private CacheStoreCentral() {} private final Hashtable imageCache = new Hashtable<>(); @@ -228,14 +232,20 @@ public final class CacheStoreCentral { */ public JsonObject getIllustInfo(int illustId, boolean flushCache) throws IOException, NoSuchElementException { - String illustIdStr = buildSyncKey(Integer.toString(illustId)); + Locker locker = buildSyncKey(Integer.toString(illustId)); + String illustIdStr = locker.getKey(); JsonObject illustInfoObj = null; if (!illustInfoCache.exists(illustIdStr) || flushCache) { - synchronized (illustIdStr) { - if (!illustInfoCache.exists(illustIdStr) || flushCache) { - illustInfoObj = BotGlobal.getGlobal().getPixivDownload().getIllustInfoByIllustId(illustId); - illustInfoCache.update(illustIdStr, illustInfoObj, null); + try { + locker.lock(); + synchronized (locker) { + if (!illustInfoCache.exists(illustIdStr) || flushCache) { + illustInfoObj = BotGlobal.getGlobal().getPixivDownload().getIllustInfoByIllustId(illustId); + illustInfoCache.update(illustIdStr, illustInfoObj, null); + } } + } finally { + locker.unlock(); } } @@ -255,31 +265,37 @@ public final class CacheStoreCentral { * @throws IOException 当Http请求处理发生异常时抛出 */ public JsonObject getIllustPreLoadData(int illustId, boolean flushCache) throws IOException { - String illustIdStr = buildSyncKey(Integer.toString(illustId)); + Locker locker = buildSyncKey(Integer.toString(illustId)); + String illustIdStr = locker.getKey(); JsonObject result = null; if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) { - synchronized (illustIdStr) { - if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) { - log.trace("IllustId {} 缓存失效, 正在更新...", illustId); - JsonObject preLoadDataObj = BotGlobal.getGlobal().getPixivDownload() - .getIllustPreLoadDataById(illustId) - .getAsJsonObject("illust") - .getAsJsonObject(Integer.toString(illustId)); + try { + locker.lock(); + synchronized (locker) { + if (!illustPreLoadDataCache.exists(illustIdStr) || flushCache) { + log.trace("IllustId {} 缓存失效, 正在更新...", illustId); + JsonObject preLoadDataObj = BotGlobal.getGlobal().getPixivDownload() + .getIllustPreLoadDataById(illustId) + .getAsJsonObject("illust") + .getAsJsonObject(Integer.toString(illustId)); - long expire = 7200 * 1000; - String propValue = SettingProperties. - getProperty(SettingProperties.GLOBAL, "cache.illustPreLoadData.expire", "7200000"); - log.debug("PreLoadData有效时间设定: {}", propValue); - try { - expire = Long.parseLong(propValue); - } catch (Exception e) { - log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire); + long expire = 7200 * 1000; + String propValue = SettingProperties. + getProperty(SettingProperties.GLOBAL, "cache.illustPreLoadData.expire", "7200000"); + log.debug("PreLoadData有效时间设定: {}", propValue); + try { + expire = Long.parseLong(propValue); + } catch (Exception e) { + log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire); + } + + result = preLoadDataObj; + illustPreLoadDataCache.update(illustIdStr, preLoadDataObj, expire); + log.trace("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire); } - - result = preLoadDataObj; - illustPreLoadDataCache.update(illustIdStr, preLoadDataObj, expire); - log.trace("作品Id {} preLoadData缓存已更新(有效时间: {})", illustId, expire); } + } finally { + locker.unlock(); } } @@ -292,17 +308,24 @@ public final class CacheStoreCentral { public List getIllustPages(int illustId, PixivDownload.PageQuality quality, boolean flushCache) throws IOException { - String pagesSign = buildSyncKey(Integer.toString(illustId), ".", quality.name()); + Locker locker + = buildSyncKey(Integer.toString(illustId), ".", quality.name()); + String pagesSign = locker.getKey(); List result = null; if (!pagesCache.exists(pagesSign) || flushCache) { - synchronized (pagesSign) { - if (!pagesCache.exists(pagesSign) || flushCache) { - List linkList = PixivDownload - .getIllustAllPageDownload(BotGlobal.getGlobal().getPixivDownload().getHttpClient(), - BotGlobal.getGlobal().getPixivDownload().getCookieStore(), illustId, quality); - result = linkList; - pagesCache.update(pagesSign, linkList, null); + try { + locker.lock(); + synchronized (locker) { + if (!pagesCache.exists(pagesSign) || flushCache) { + List linkList = PixivDownload + .getIllustAllPageDownload(BotGlobal.getGlobal().getPixivDownload().getHttpClient(), + BotGlobal.getGlobal().getPixivDownload().getCookieStore(), illustId, quality); + result = linkList; + pagesCache.update(pagesSign, linkList, null); + } } + } finally { + locker.unlock(); } } @@ -342,23 +365,30 @@ public final class CacheStoreCentral { } String date = new SimpleDateFormat("yyyyMMdd").format(queryDate); - String requestSign = buildSyncKey(contentType.name(), ".", mode.name(), ".", date); + Locker locker + = buildSyncKey(contentType.name(), ".", mode.name(), ".", date); + String requestSign = locker.getKey(); List result = null; if(!rankingCache.exists(requestSign) || flushCache) { - synchronized(requestSign) { - if(!rankingCache.exists(requestSign) || flushCache) { - log.trace("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign); - List rankingResult = BotGlobal.getGlobal().getPixivDownload() - .getRanking(contentType, mode, queryDate, 1, 500); - long expireTime = 0; - if(rankingResult.size() == 0) { - expireTime = 5400000 + expireTimeFloatRandom.nextInt(1800000); - log.warn("数据获取失败, 将设置浮动有效时间以准备下次更新. (ExpireTime: {}ms)", expireTime); + try { + locker.lock(); + synchronized (locker) { + if (!rankingCache.exists(requestSign) || flushCache) { + log.trace("Ranking缓存失效, 正在更新...(RequestSign: {})", requestSign); + List rankingResult = BotGlobal.getGlobal().getPixivDownload() + .getRanking(contentType, mode, queryDate, 1, 500); + long expireTime = 0; + if (rankingResult.size() == 0) { + expireTime = 5400000 + expireTimeFloatRandom.nextInt(1800000); + log.warn("数据获取失败, 将设置浮动有效时间以准备下次更新. (ExpireTime: {}ms)", expireTime); + } + result = new ArrayList<>(rankingResult).subList(start - 1, start + range - 1); + rankingCache.update(requestSign, rankingResult, expireTime); + log.trace("Ranking缓存更新完成.(RequestSign: {})", requestSign); } - result = new ArrayList<>(rankingResult).subList(start - 1, start + range - 1); - rankingCache.update(requestSign, rankingResult, expireTime); - log.trace("Ranking缓存更新完成.(RequestSign: {})", requestSign); } + } finally { + locker.unlock(); } } @@ -428,42 +458,49 @@ public final class CacheStoreCentral { log.debug("正在搜索作品, 条件: {}", searchBuilder.getSearchCondition()); - String requestUrl = searchBuilder.buildURL().intern(); + Locker locker + = buildSyncKey(searchBuilder.buildURL()); + String requestUrl = locker.getKey(); log.debug("RequestUrl: {}", requestUrl); JsonObject resultBody = null; if(!searchBodyCache.exists(requestUrl)) { - synchronized (requestUrl) { - if (!searchBodyCache.exists(requestUrl)) { - log.trace("searchBody缓存失效, 正在更新..."); - JsonObject jsonObject; - HttpGet httpGetRequest = BotGlobal.getGlobal().getPixivDownload(). - createHttpGetRequest(requestUrl); - HttpResponse response = BotGlobal.getGlobal().getPixivDownload(). - getHttpClient().execute(httpGetRequest); + try { + locker.lock(); + synchronized (locker) { + if (!searchBodyCache.exists(requestUrl)) { + log.trace("searchBody缓存失效, 正在更新..."); + JsonObject jsonObject; + HttpGet httpGetRequest = BotGlobal.getGlobal().getPixivDownload(). + createHttpGetRequest(requestUrl); + HttpResponse response = BotGlobal.getGlobal().getPixivDownload(). + getHttpClient().execute(httpGetRequest); - String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); - log.trace("ResponseBody: {}", responseBody); - jsonObject = BotGlobal.getGlobal().getGson().fromJson(responseBody, JsonObject.class); + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.trace("ResponseBody: {}", responseBody); + jsonObject = BotGlobal.getGlobal().getGson().fromJson(responseBody, JsonObject.class); - if (jsonObject.get("error").getAsBoolean()) { - log.error("接口请求错误, 错误信息: {}", jsonObject.get("message").getAsString()); - throw new HttpRequestException(response.getStatusLine(), responseBody); + if (jsonObject.get("error").getAsBoolean()) { + log.error("接口请求错误, 错误信息: {}", jsonObject.get("message").getAsString()); + throw new HttpRequestException(response.getStatusLine(), responseBody); + } + + long expire = 7200 * 1000; + String propValue = SettingProperties + .getProperty(SettingProperties.GLOBAL, "cache.searchBody.expire", "7200000"); + try { + expire = Long.parseLong(propValue); + } catch (Exception e) { + log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire); + } + resultBody = jsonObject.getAsJsonObject().getAsJsonObject("body"); + searchBodyCache.update(requestUrl, jsonObject, expire); + log.trace("searchBody缓存已更新(有效时间: {})", expire); + } else { + log.trace("搜索缓存命中."); } - - long expire = 7200 * 1000; - String propValue = SettingProperties - .getProperty(SettingProperties.GLOBAL, "cache.searchBody.expire", "7200000"); - try { - expire = Long.parseLong(propValue); - } catch (Exception e) { - log.warn("全局配置项 \"{}\" 值非法, 已使用默认值: {}", propValue, expire); - } - resultBody = jsonObject.getAsJsonObject().getAsJsonObject("body"); - searchBodyCache.update(requestUrl, jsonObject, expire); - log.trace("searchBody缓存已更新(有效时间: {})", expire); - } else { - log.trace("搜索缓存命中."); } + } finally { + locker.unlock(); } } else { log.trace("搜索缓存命中."); @@ -494,12 +531,12 @@ public final class CacheStoreCentral { * @param keys String对象 * @return 合并后, 如果常量池存在合并后的结果, 则返回常量池中的对象, 否则存入常量池后返回. */ - private static String buildSyncKey(String... keys) { + private Locker buildSyncKey(String... keys) { StringBuilder sb = new StringBuilder(); for (String string : keys) { sb.append(string); } - return sb.toString().intern(); + return lockerMap.createLocker(sb.toString(), true); } /** diff --git a/src/main/java/net/lamgc/cgj/util/Locker.java b/src/main/java/net/lamgc/cgj/util/Locker.java new file mode 100644 index 0000000..4111025 --- /dev/null +++ b/src/main/java/net/lamgc/cgj/util/Locker.java @@ -0,0 +1,64 @@ +package net.lamgc.cgj.util; + +import java.util.concurrent.atomic.AtomicInteger; + +public final class Locker { + + private final LockerMap fromMap; + + private final K key; + + private final boolean autoDestroy; + + private final AtomicInteger lockCount = new AtomicInteger(0); + + /** + * 构造一个锁对象 + * @param map 所属LockerMap + * @param key 所属Key + */ + Locker(LockerMap map, K key, boolean autoDestroy) { + this.fromMap = map; + this.key = key; + this.autoDestroy = autoDestroy; + } + + /** + * 上锁 + */ + public void lock() { + lockCount.incrementAndGet(); + } + + /** + * 解锁 + */ + public void unlock() { + int newValue = lockCount.decrementAndGet(); + if(newValue <= 0 && autoDestroy) { + destroy(); + } + } + + /** + * 获取锁对象所属Key + */ + public K getKey() { + return key; + } + + /** + * 销毁锁对象 + */ + public void destroy() { + fromMap.destroyLocker(this); + } + + @Override + public String toString() { + return "Locker{" + + "fromMap=" + fromMap + + ", key=" + key + + '}'; + } +} diff --git a/src/main/java/net/lamgc/cgj/util/LockerMap.java b/src/main/java/net/lamgc/cgj/util/LockerMap.java new file mode 100644 index 0000000..0953cdd --- /dev/null +++ b/src/main/java/net/lamgc/cgj/util/LockerMap.java @@ -0,0 +1,31 @@ +package net.lamgc.cgj.util; + +import java.util.HashMap; + +public class LockerMap { + + private final HashMap> lockerHashMap = new HashMap<>(); + + /** + * 创建锁 + * @param key Key + * @return 如果Key所属锁存在, 则返回对应锁, 否则返回新锁 + */ + public Locker createLocker(K key, boolean autoDestroy) { + if(lockerHashMap.containsKey(key)) { + return lockerHashMap.get(key); + } + Locker newLocker = new Locker<>(this, key, autoDestroy); + lockerHashMap.put(key, newLocker); + return newLocker; + } + + /** + * 销毁锁 + * @param locker 锁对象 + */ + public void destroyLocker(Locker locker) { + lockerHashMap.remove(locker.getKey()); + } + +}