Для генерации у ссылочных типов учитывается поток на котором запрашивается вызов GetHashCode, у каждого потока есть свой генератор hash кодов. Для структур дописать
При первом вызове функции GetHashCode результат запоминается в SyncBlockIndex'e, если на этот объект не была наложена блокировка. Если была, то он запоминается в таблице объектов синхронизации по индексу равному числу записанному в SyncBlockIndex'e. Если сначала был вызван метод GetHashCode, а потом объект был взят в блокировку, тогда Hash Code записанный в SyncBlockIndex'e копируется в выделенный объект синхронизации, а в SyncBlockIndex записывается индекс этого блока синхронизации в таблице
Если блок синхронизации долго не применяется, то он будет собран сборщиком мусора и hashcode снова запишется в заголовок объекта, что на данный момент записано в заголовке - индекс блока синхронизации или hashcode определяется по одному из битов заголовка объекта.
С CLR2.0 была введена оптимизация, при которой не создается объект синхронизации если lock берется одним потоком, это называется тонкой блокировкой. Тогда только один поток владеет ресурсом нет нужны выделять блок синхронизации с монитором(Win32EventHandle). В таком случае CLR сохраняет в слове заголовка объекта(интересно, что это такое) id потока, который владеет объектом в данный момент. Если какой-то другой поток попытается получить блокировку и в течение некоторого времени объект, владеющий ресурсом, не снимет блокировку, то будет выделен SyncBlock и SyncBlockIndex объекта перезапишется индексом выделенного SyncBlock'a.