You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同步方法与基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:25:54