Правильное поведение при одновременном доступе - сложная тема, и она не так проста, как простое наложение synchronized
на все, так как теперь вам нужно подумать о том, как операции могут чередоваться.
Например, представьте, что выиметь класс как список, и вы хотите сделать его потокобезопасным.Таким образом, вы делаете все методы синхронизированными и продолжаете.Скорее всего, клиенты могут использовать ваш список следующим образом:
int index = ...; // this gets set somewhere, maybe passed in as an argument
// Check that the list has enough elements for this call to make sense
if (list.size() > index)
{
return list.get(index);
}
else
{
return DEFAULT_VALUE;
}
В однопоточной среде этот код совершенно безопасен.Однако, если к списку обращаются (и, возможно, изменяют) одновременно, размер списка может измениться после вызова size()
, но до вызова get()
.Таким образом, список может «невозможно» вызвать исключение IndexOutOfBoundsException (или подобное) в этом случае, даже если размер был проверен заранее.
Нет быстрого способа исправить это - вам просто нужно тщательно подумать об использовании- случаи для вашего класса / интерфейса и убедитесь, что вы действительно можете гарантировать их при чередовании с любыми другими действительными операциями.Часто для этого может потребоваться дополнительная сложность или просто больше деталей в документации.Если класс гипотетического списка указывает, что он всегда синхронизируется на своем собственном мониторе, тогда эту специфическую ситуацию можно исправить как
synchronized(list)
{
if (list.size() > index)
{
return list.get(index);
}
}
, но при других схемах синхронизации это не будет работать.Или это может быть слишком узким местом.Или принуждение клиентов делать несколько вызовов в одной и той же лексической области может быть недопустимым ограничением.Все зависит от того, чего вы пытаетесь достичь, от того, как вы можете сделать свой интерфейс безопасным, производительным и элегантным.