Если два потока вызывают одновременно и идентификатор существует, они оба вернут правильную информацию о пользователе.Первая блокировка - это предотвращение ошибок, как сказал SLaks - если кто-то пишет в словарь, когда вы пытаетесь его прочитать, у вас будут проблемы.В этом случае вторая блокировка никогда не будет достигнута.
Если два потока вызывают одновременно, и идентификатор не существует, один поток заблокирует и введет TryGetValue, это вернет false и установит для u значение по умолчанию,Эта первая блокировка снова, чтобы предотвратить ошибки, описанные SLaks.В этот момент этот первый поток снимет блокировку, а второй поток войдет и сделает то же самое.Затем оба установят для «u» информацию из «RetrieveUser (id)»;это должна быть та же информация.Затем один поток заблокирует словарь и присвоит _users [id] значение u.Эта вторая блокировка такова, что два потока пытаются записать значения в одни и те же области памяти одновременно и портят эту память.Я не знаю, что будет делать второй поток, когда он входит в назначение.Он либо вернет досрочно игнорирующее обновление, либо перезапишет существующие данные из первого потока.В любом случае, словарь будет содержать одну и ту же информацию, поскольку оба потока должны были получить одинаковые данные в 'u' от RetrieveUser.
Для повышения производительности автор сравнил два сценария - приведенный выше сценарий, который будет крайне редким иблокировать, в то время как два потока пытаются записать одни и те же данные, а второй - куда более вероятно, что два потока вызовут запрос данных для объекта, который должен быть записан, и объекта, который существует.Например, threadA и threadB вызывают одновременно, а ThreadA блокирует идентификатор, который не существует.Нет никаких причин заставлять threadB ожидать поиска, пока threadA работает над RetriveUser.Эта ситуация, вероятно, гораздо более вероятна, чем повторяющиеся идентификаторы, описанные выше, поэтому для повышения производительности автор решил не блокировать весь блок.