String instances are thread-safe. String variables are not.
Let's define what I mean by "instances" and "variables";
Instances are those string entities which live out in the heap that represents a string value.
Variables are the things you declare, either local, global, or a field of a class or record.
Recently I noticed there was some confusion about this, and in fact a test case surfaced that purported to demonstrate how strings were not thread-safe. The problem was that the demonstration merely showed exactly what I stated above; that string variables are not thread-safe.
Because multiple string variables may be referencing the instance or value, the chance of that value being accessed across threads is greater than zero. Even if the variables are properly guarded against simultaneous cross-thread access, that does nothing to help the values out on the heap. This is why the internal implementation of strings uses atomic operations to manage the reference count. This is key to ensuring the reference count is always correct and consistent.
Just like any other variable that is being read from or written to from many threads, string variables require the same level of protection as you would give to a simple Integer variable.
I suspect the confusion about this stems from the fact that strings are treated like "value objects" while they are implemented as a "reference objects". The other bit of confusion likely comes from the fact that the internal implementation details of strings is visible for all to see. When folks looked into it, they saw a LOCK INC XXXX instruction or an AtomicIncrement() standard function and thought, "Aha! That is there for thread-safety, so therefor strings are 'thread-safe'".
So the point here is that you should treat a string variable like any other variable in threaded code. Don't worry about the string instances; the runtime has that part taken care of.