How do you implement thread safety in a multi-threaded C# application? Explain the use of locks, Mutex, and Semaphore.