Как наиболее эффективно синхронизировать коллекцию?
Пишу RESTful сервис на Spring. Имеется класс CarProviderService, его зона ответственности - обновление ArrayList'а cars.
Раз в минуту с предоставлением актуального состояние этого листа другим частям приложения. Это реализовано вызовом TimerTask'a в @PostConstruct методе.
Вопрос заключается в том:
как обеспечить предоставление актуального состояния ArrayList'а
carsс максимальной производительностью?
В предоставленном коде, используется CopyOnWriteArrayList, но частые итерации по листу не будут синхронизированы, а обновление элементов будет осуществляться лишь раз в минуту.
И остался не решён вопрос с атомарностью методов cars.clear() и cars.adAll(newCars). При отсутствии явной синхронизации возможна ситуация когда другие потоки увидят пустой лист.
Получается, всё же необходимо явно синхронизировать блок очистки + заполнения данными листа и блок итерации по этому листу?
Буду благодарен предложенным решениям и советами, как это подобные ситуации обычно решаются на практике.
@Service
public class CarProviderService {
private static final Logger LOGGER = LoggerFactory.getLogger(CarProviderService.class);
private List<Car> cars = new CopyOnWriteArrayList<>();
/**
*
* @return instance of {@link CopyOnWriteArrayList<Car>} of cars
* @param company - required company
*/
public List<Car> getCarsByCompany(@NotNull Company company) {
Predicate<Car> isBelongsToCompany = car -> car.getCompany().equals(company);
return cars.stream().filter(isBelongsToCompany)
.collect(Collectors.collectingAndThen(Collectors.toList(), CopyOnWriteArrayList::new));
}
/**
* Updating cars every minute method.
* {@link #cars} list contains all available cars.
* Note that it's implementation is {@link CopyOnWriteArrayList<Car>}.
* It provides an ability to iterate cars in thread-safe way.
*/
@PostConstruct
public void consumeActualCars(){
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
LOGGER.debug("New cars fetching has been started");
List<Car> newCars = new ArrayList<>();
/* Заполняем лист newCars новыми данными, после чего обновляем cars*/
cars.clear();
cars.addAll(newCars);
}
};
// Starting receiving new cars every minute
Timer timer = new Timer();
timer.schedule(timerTask,0,60000);
}
}
Ответы (1 шт):
Самый простой и быстрый способ это изменить ссылку:
...
List<Car> newCars = new ArrayList<>();
/* Заполняем лист newCars новыми данными, после чего обновляем cars*/
cars = newCars;
Изменение ссылки атомарно и плюс нет накладных расходов на очистку/модификацию коллекции.
Важно только добавить модификатор volatile в определение поля cars чтобы изменение было видно в других потоках:
private volatile List<Car> cars = new CopyOnWriteArrayList<>();