The COM behavior makes sense if you think about it. The code executes inside the service process. If you mark the the class to run in Single Threaded Apartment then only one thread executes at a time. Concurrent calls are stacked up in the message qeueu executing one at a time. If a Multithreaded Apartment is specified then the code can execute concurrently. To accomplish this in the running process multiple threads are spun up to acheive this behavior.
If you wanted to avoid concurrency issues you need to put your class in a dll. Then you still really have multiple threads but each one executes in the calling process so you don't need to worry about state.
There is no way to execute any block of code concurrently without threads. Even if you execute in seperate processes there are still multiple threads involved they are just seperated by the process boundary.
I'm only belaboring the obviouse becayse I once worked for a company that just could not get this through their heads. :-) They thought they could avoid threading issues by executing in seperate processes and using shared memory (memory mapped files)! This worked (kind of) when their code ran on a single CPU and they added a "repair" app to fix the inconsistancies. On the last install they moved to multiple CPUs and everything really fell apart. Then I left, the end.