Recently I've finished learning about multithreaded programming on single shared objects, but was curious about how different things would be in order to successfully program on multiple shared objects?
This is a very hot current research topic. The question of how to properly share data between multiple, concurrent, processing units, doesn't have obviously good answers yet.
Some of the issues that change and you should think about:
With multiple shared objects, you'll want to have different threads being able to work with different objects at the same time. If two objects are different, but related, you'll need to have a way of deciding whether they can be worked on independently of each other
Objects may end up moving around in memory more, as they get moved closer to where they are being used (physically closer, or logically closer)
Multiple related objects may end up cached in multiple, un-related, caches. Those caches are going to want to know when they've become invalid, which they might if one of a set of related objects is changed. Imagine, for example, a thread which draws to the screen, while other threads go about modifying the data that's being drawn. How do you make sure that what you see on the screen makes sense?
There are different ways for objects to be accessed, some with side effects, some without. The patterns of access can heavily influence performance - optimization techniques can become intractably complex
The difference is that usually when you have multiple shared objects, they live in some sort of collection (like a hash table, linked list, tree, or whatever). This requires some kind of pattern for how the locking will work.
The simplest solution is to have one lock for the collection. A thread can acquire the lock, find the object in the collection, work on that object, and then release the lock. This is very simple and for some cases is the best solution. But the big downside is that two threads can't work on two different objects in the same collection.
You can also have one lock for the collection itself and one lock for each object in the collection. You then acquire the collection lock, find the object you want, acquire the lock on that object, and then release the collection lock (assuming you aren't going to modify the collection itself). This allows other threads to search the collection and modify other objects in the collection while a thread is working on an object in the collection.
There are all kinds of other "in-between" approaches. But the basic concept is the same -- you still must ensure that a structure cannot be accessed by one thread while another thread is, or might be, modifying it.