### 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
localRef.get().getIndexExpression(),
                    false,
                    SearchResponse.Cluster.Status.SKIPPED,
                    5,
                    0,
                    0,
                    5,
                    Collections.emptyList(),
                    new TimeValue(1000),
                    false
                );
                skipped--;
            } else {
                updated = new SearchResponse.Cluster(
                    localAlias,
                    localRef.get().getIndexExpression(),
                    false,
                    SearchResponse.Cluster.Status.PARTIAL,
                    5,
                    2,
                    1,
                    3,
                    Collections.emptyList(),
                    new TimeValue(1000),
                    false
                );
                partial--;
            }
            boolean swapped = localRef.compareAndSet(orig, updated);
            assertTrue("CAS swap failed for cluster " + updated, swapped);