Hi Matthew,
How to avoid optimistic locking exceptions depends on the process model in use and the API interactions that custom code makes. Thus, there is no general how-to-avoid-optimistic-locking guide. In general, optimistic locking exceptions occur whenever the process engine updates one database entity from multiple commands in parallel. This can for example be when your code updates the same variable, or when the process engine does something similar internally.
In the case of multi-instance, the process engine manages the multi-instance variables that are updated whenever a single instance completes and the execution tree that is updated whenever the structure of the process instance changes, for example when a single instance completes. The latter is typically important for synchronization in the process instance, e.g. to be able to reliably decide when the entire multi-instance activity has finished and can be completed.
These optimistic locking exceptions can never be avoided completely, as they lie in the nature of the problem and how the engine works and are therefore expected behavior. In that sense, having optimistic locking exceptions occur does not mean that anything is incorrect. By using asynchronous continuations, you can reduce the impact of such exceptions (i.e. you can reduce the amount of work that gets rolled back). For example, if you declare the end event in your multi-instance subprocess as async after, then any optimistic locking occurring due to synchronization of the multi-instance construct will only roll back to that point.
I hope that helps.
Cheers,
Thorben