@synchronized
By examining the code (ADC registration required) that implements
objc_sync_enterandobc_sync_exit, we can see that on every@synchronized(foo)block, we are actually paying for 3 lock/unlock sequences.objc_sync_entercallsid2data, which is responsible for getting the lock associated withfoo, and then locks it.objc_sync_exitalso callsid2datato get the lock associated withfoo, and then unlocks it. And,id2datamust lock/unlock its own internal data structures so that it can safely get the lock associated withfoo, so we pay for that on each call as well.
Plus the overhead of entering an exception handling block, which is currently expensive in Objective-C.
Update: two faster versions. The first takes advantage of a guarantee that (Objective-)C++ makes, and the second uses swizzling to replace the method with one that isn’t synchronized once the initial race condition is over.