### Description

`SearchResponse.Clusters` currently uses a `HashMap<String, AtomicReference>` for storing Cluster objects. The AtomicReference CAS swaps are done in a do/while loop in most cases (e.g., in `CCSSingleCoordinatorSearchProgressListener`).

This can be replaced with a `ConcurrentHashMap` in conjunction with either `compute()` method or the `replace(key, assumed_previous_value, value)` method for atomically swapping in a new Cluster object with a CAS operation, assuring that the updates don't lose data from competing threads doing separate Cluster object updates.

See: https://github.com/elastic/elasticsearch/pull/98457/files#r1310467586 
x-pack/plugin/async-search/src/test/java/org/elasticsearch/xpack/search/AsyncSearchResponseTests.java
}
        int numClusters = successful + skipped + partial;
        for (int i = 0; i < numClusters; i++) {
            String clusterAlias = "cluster_" + i;
            AtomicReference<SearchResponse.Cluster> clusterRef = clusters.getCluster(clusterAlias);
            SearchResponse.Cluster orig = clusterRef.get();
            SearchResponse.Cluster updated;
            if (successful > 0) {
                updated = new SearchResponse.Cluster(
                    clusterAlias,
                    clusterRef.get().getIndexExpression(),
                    false,
                    SearchResponse.Cluster.Status.SUCCESSFUL,
                    5,
                    5,
                    0,
                    0,
                    Collections.emptyList(),
                    new TimeValue(1000),
                    false
                );
                successful--;
            } else if (skipped > 0) {
                updated = new SearchResponse.Cluster(
                    clusterAlias,
                    clusterRef.get().getIndexExpression(),
                    false,
                    SearchResponse.Cluster.Status.SKIPPED,
                    5,
                    0,