The solution to the problem you presented is to have one critical section for each object _AND_ this is very important: the sequence in which the critical sections are acquired by the threads MUST be pre-established and always be the same.
IOW, if there are n critical sections, you establish an order in which they must be acquired, for example's sake, it may be: cs1, cs2, cs3...csN. This means if a thread needs control over object cs2 and cs7, it must acquire cs2 first then acquire cs7. Never should any thread acquire cs7 first then attempt to acquire cs2 (the different sequence is what produces deadlocks.)
To maximize concurrency, it is usually best to have an all or nothing policy, that is, if a thread needs cs2, cs4 and cs7 and has acquired cs2 and cs4 but failed to acquire cs7, release cs2 and cs4 and try again, holding cs2 and cs4 while waiting for cs7 causes other threads interested in cs7 to also wait. releasing cs2 and cs4 gives other threads the opportunity to obtain them.
The "downside", if it can be called that, of that method is that it is not a first come first serve, it is basically random, a "lucky" thread will "win" all the locks because it tried at the "right time". As long as the threads hold the locks for roughly equal times, this will work fine. It could be a problem if some thread(s) hold the locks for much longer than other threads.
HTH.