### 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 
server/src/main/java/org/elasticsearch/action/search/TransportSearchAction.java
this.exceptions = exceptions;
            this.cluster = cluster;
            this.originalListener = originalListener;
            this.startTime = System.currentTimeMillis();
        }
        @Override
        public final void onResponse(Response response) {
            innerOnResponse(response);
            maybeFinish();
        }
        abstract void innerOnResponse(Response response);
        @Override
        public final void onFailure(Exception e) {
            ShardSearchFailure f = new ShardSearchFailure(e);
            logCCSError(f, clusterAlias, skipUnavailable);
            if (skipUnavailable) {
                if (cluster != null) {
                    ccsClusterInfoUpdate(f, cluster, skipUnavailable);
                }
                
            } else {
                if (cluster != null) {
                    ccsClusterInfoUpdate(f, cluster, skipUnavailable);
                }
                Exception exception = e;
                if (RemoteClusterAware.LOCAL_CLUSTER_GROUP_KEY.equals(clusterAlias) == false) {
                    exception = wrapRemoteClusterFailure(clusterAlias, e);
                }
                if (exceptions.compareAndSet(null, exception) == false) {
                    exceptions.accumulateAndGet(exception, (previous, current) -> {