同步方法与基于LOCK的同步块失效问题求助
嘿,我一眼就看出你的同步机制为啥没起作用了——核心问题出在OkHttp的enqueue()是异步执行的!
你现在的写法是把发起请求的代码包在synchronized块里,但enqueue()只是把请求扔进OkHttp的线程池就立刻返回了,同步锁马上就被释放,导致requestCryptoSimple()和requestCryptoDetailed()几乎同时发起请求,完全没达到串行执行的目的。更糟的是,requestCryptoDetailed()还依赖cryptoSimpleList的结果,可异步回调还没执行的时候,这个列表根本没被赋值,大概率会触发空指针或者请求参数错误。
解决方案:让请求串行化,依赖回调完成再执行下一步
我们需要把第二个请求放在第一个请求的成功回调里,同时修正requestingInProgress的更新逻辑(异步操作里不能刚发起请求就把它设为false,得等所有请求和处理都完成后再改)。
修改后的完整代码:
public class PagingBoundaryCallback extends PagedList.BoundaryCallback<Crypto> { private static final String LOG_TAG = PagingBoundaryCallback.class.getSimpleName(); private static final String TAG_COIN_MARKET_CAP_API = LOG_TAG + CoinMarketCapApi.class.getSimpleName(); private static final String TAG_CRYPTO_COMPARE_API = LOG_TAG + CryptoCompareApi.class.getSimpleName(); private static final int RESULTS_SIZE = 50; private static final String TO_SYMBOL = "USD"; private static final int FAVOURITE_FALSE = 0; private static final Object LOCK = new Object(); private CoinMarketCap coinMarketCapApi; private CryptoCompare cryptoCompareApi; private CryptoLocalCache cache; private int resultsFromRank = 1; private boolean requestingInProgress = false; private List<CryptoSimple> cryptoSimpleList; private List<CryptoDetailed> cryptoDetailedList; private List<Crypto> cryptoList = new ArrayList<>(); public PagingBoundaryCallback( CoinMarketCap coinMarketCapApi, CryptoCompare cryptoCompareApi, CryptoLocalCache cache) { this.coinMarketCapApi = coinMarketCapApi; this.cryptoCompareApi = cryptoCompareApi; this.cache = cache; } @Override public void onZeroItemsLoaded() { super.onZeroItemsLoaded(); requestAndSaveData(); } @Override public void onItemAtEndLoaded(Crypto itemAtEnd) { super.onItemAtEndLoaded(itemAtEnd); requestAndSaveData(); } private void requestAndSaveData() { synchronized (LOCK) { if (requestingInProgress) return; requestingInProgress = true; } // 先请求Simple数据,成功后再触发Detailed请求 requestCryptoSimple(); } private void requestCryptoSimple() { coinMarketCapApi.requestCoins(resultsFromRank, RESULTS_SIZE).enqueue( new Callback<CryptoSimpleResponse>() { @Override public void onFailure(Call<CryptoSimpleResponse> call, Throwable t) { Log.d(TAG_COIN_MARKET_CAP_API, "failed to get data"); Log.d(TAG_COIN_MARKET_CAP_API, "Unknown error " + t.getMessage()); // 请求失败时重置状态,允许后续请求 synchronized (LOCK) { requestingInProgress = false; } } @Override public void onResponse(Call<CryptoSimpleResponse> call, Response<CryptoSimpleResponse> response) { Log.d(TAG_COIN_MARKET_CAP_API, "got response: " + response.toString()); if (response.isSuccessful()) { cryptoSimpleList = new ArrayList<>(); HashMap<String, CryptoSimple> hashMap = response.body().getItems(); for (Map.Entry<String, CryptoSimple> entry : hashMap.entrySet()) { CryptoSimple cryptoSimple = entry.getValue(); cryptoSimpleList.add(cryptoSimple); Log.v("CryptoSimple HashMap", cryptoSimple.toString()); } Log.v("cryptoSimpleListSize", String.valueOf(cryptoSimpleList.size())); resultsFromRank += RESULTS_SIZE; // Simple数据拿到手后,再发起Detailed请求 requestCryptoDetailed(); } else { Log.d(TAG_COIN_MARKET_CAP_API, "Unknown error " + response.errorBody().toString()); synchronized (LOCK) { requestingInProgress = false; } } } } ); } private void requestCryptoDetailed() { StringBuilder cryptoSymbol = new StringBuilder(RESULTS_SIZE); Log.v("cryptoSimpleListSize", String.valueOf(cryptoSimpleList.size())); for (CryptoSimple item : cryptoSimpleList) { cryptoSymbol .append(item.getSymbol()) .append(","); } int cryptoSymbolLength = cryptoSymbol.length(); Log.v("cryptoSymbolLength", String.valueOf(cryptoSymbolLength)); String fromSymbol = cryptoSymbol.delete(cryptoSymbolLength - 1, cryptoSymbolLength).toString(); cryptoCompareApi.requestCoins(fromSymbol, TO_SYMBOL).enqueue(new Callback<CryptoDetailedResponse>() { @Override public void onResponse(Call<CryptoDetailedResponse> call, Response<CryptoDetailedResponse> response) { Log.d(TAG_CRYPTO_COMPARE_API, "got response: " + response.toString()); if (response.isSuccessful()) { cryptoDetailedList = response.body().getList(); // 两个请求都成功后,合并数据并保存到本地 createCryptoFromResponses(cryptoSimpleList, cryptoDetailedList); } else { Log.d(TAG_COIN_MARKET_CAP_API, "Unknown error " + response.errorBody().toString()); } // 不管成功失败,都重置请求状态 synchronized (LOCK) { requestingInProgress = false; } } @Override public void onFailure(Call<CryptoDetailedResponse> call, Throwable t) { Log.d(TAG_CRYPTO_COMPARE_API, "failed to get data"); Log.d(TAG_CRYPTO_COMPARE_API, "Unknown error " + t.getMessage()); synchronized (LOCK) { requestingInProgress = false; } } }); } private void createCryptoFromResponses(List<CryptoSimple> cryptoSimpleList, List<CryptoDetailed> cryptoDetailedList) { if (cryptoList.size() != 0) cryptoList.clear(); for (int i = 0; i < cryptoSimpleList.size(); i++) { Crypto cryptoItem = new Crypto( cryptoSimpleList.get(i).getId(), cryptoSimpleList.get(i).getName(), cryptoSimpleList.get(i).getSymbol(), cryptoSimpleList.get(i).getRank(), cryptoDetailedList.get(i).getPrice(), cryptoDetailedList.get(i).getTime(), cryptoDetailedList.get(i).getVolume(), cryptoDetailedList.get(i).getChangePercentage(), cryptoDetailedList.get(i).getMarketCap(), FAVOURITE_FALSE ); cryptoList.add(cryptoItem); } if (cryptoList.size() > 0) cache.insertCoins(cryptoList); Log.v(LOG_TAG, "Inserted " + String.valueOf(cryptoList.size()) + " to database"); } }
关键修改点说明
- 串行化请求流程:把
requestCryptoDetailed()的调用移到requestCryptoSimple()的成功回调里,确保只有拿到Simple数据后,才会发起Detailed请求,彻底解决依赖问题。 - 修正请求状态的更新时机:不再在发起请求后立刻把
requestingInProgress设为false,而是在两个请求的回调完成后(无论成功失败)才更新,防止重复发起请求。 - 简化锁的使用:只在检查和更新
requestingInProgress时加锁,因为异步请求已经是串行执行的,不需要给整个请求方法套锁。
另外提两个优化建议:
- 可以用
LiveData或者RxJava来管理异步流,代码会更简洁,也更容易处理线程切换和错误情况。 cryptoSimpleList和cryptoDetailedList可以不用作为成员变量,直接在回调中传递,避免潜在的线程安全问题。
内容的提问来源于stack exchange,提问作者Kuba
相关产品推荐
相关产品推荐

