Correction: "changes to static members by one thread _might_ be visible
to other threads". One needs to use some synchronization mechanism to
guarantee that they are visible to other threads. And of course if one
has implemented some synchronization mechanism, then at least by design
the method is thread-safe.
My understanding is that on x86/x64 hardware, Intel's memory model
results in volatile or near-volatile semantics for all memory access, so
the main reason one would see thread safety issues absent
synchronization on those CPUs is because of optimizations, not due to
memory coherency issues. But even those can be an issue, and of course
on other hardware the memory coherency issues are more significant.
In situations where optimizations and/or memory coherency issues come
up, in your code example it is even possible for each thread to
essentially have its own copy of each static field, so that the output
from each thread is entirely consistent within the thread, but the
output is interleaved and independent between threads.
All that said, the bottom line here is the same what you essentially
describe and as I wrote before: for a _plain_ C# program, without any
explicit synchronization, static methods are not synchronized. In other
contexts, this may or may not continue to hold to be true.