How do you implement thread safety in a multi-threaded C# application, and when would you use locks, Mutex, and Semaphore?