Echoing this sentiment, if you have many threads hitting the same shared objects and trying to do work in parallel to one another and in complete disregard for one another, you will find yourself battling stochastic probability distributions when you ought to be debugging.
I think that sharing objects among threads is good for about one thing: to avoid copying things in memory. And that advantage, quite frankly, is dubious at best, given the amount of instability that it can cause. (When that thing goes into production, you need to know that it will be rock-solid for weeks at a time.)
I prefer therefore to simplify the architecture; to reduce the number of possibilities, to an amount that is limited, and that can be simulated in advance of the system’s actual construction. I prefer to have one thread that is the producer (and the destructor) of all shared data, and the worker threads are consumers who withdraw the data from a queue, and who place them back onto another queue for eventual destruction, filing, or recycling as the case may be. (And when you do that, of course, the “shared data” part of it probably goes away and I say, “good riddance.”) The whole scenario is now greatly simplified: one actor both creates the objects and disposes of them; each actor is working on just one thing at a time albeit in parallel; each thing is being worked on, at any particular instant, by only one actor at a time even though many things may be being worked on at a time. That is a design that can run continuously forever. If the work being performed by each worker is substantial and if the number of those workers can be throttled, backlogs and shortages are unlikely to develop (as simulations will predict). And is it (perhaps except in the bleeding-edge situations that I know BrowserUK does routinely work with ...) “fast enough?” (IMLE) Yes, away from that edge, surely. Our world is filled with just such designs.