diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp
index d3bb1c871a..af71987e88 100644
--- a/src/core/hle/kernel/k_thread.cpp
+++ b/src/core/hle/kernel/k_thread.cpp
@@ -723,10 +723,10 @@ void KThread::UpdateState() {
     ASSERT(kernel.GlobalSchedulerContext().IsLocked());
 
     // Set our suspend flags in state.
-    const ThreadState old_state = thread_state;
+    const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
     const auto new_state =
         static_cast<ThreadState>(this->GetSuspendFlags()) | (old_state & ThreadState::Mask);
-    thread_state = new_state;
+    thread_state.store(new_state, std::memory_order_relaxed);
 
     // Note the state change in scheduler.
     if (new_state != old_state) {
@@ -738,8 +738,8 @@ void KThread::Continue() {
     ASSERT(kernel.GlobalSchedulerContext().IsLocked());
 
     // Clear our suspend flags in state.
-    const ThreadState old_state = thread_state;
-    thread_state = old_state & ThreadState::Mask;
+    const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
+    thread_state.store(old_state & ThreadState::Mask, std::memory_order_relaxed);
 
     // Note the state change in scheduler.
     KScheduler::OnThreadStateChanged(kernel, this, old_state);
@@ -1079,17 +1079,10 @@ void KThread::IfDummyThreadTryWait() {
         return;
     }
 
-    // Block until we can grab the lock.
-    KScopedSpinLock lk{dummy_wait_lock};
-}
-
-void KThread::IfDummyThreadBeginWait() {
-    if (!IsDummyThread()) {
-        return;
-    }
-
-    // Ensure the thread will block when IfDummyThreadTryWait is called.
-    dummy_wait_lock.Lock();
+    // Block until we are no longer waiting.
+    std::unique_lock lk(dummy_wait_lock);
+    dummy_wait_cv.wait(
+        lk, [&] { return GetState() != ThreadState::Waiting || kernel.IsShuttingDown(); });
 }
 
 void KThread::IfDummyThreadEndWait() {
@@ -1097,8 +1090,8 @@ void KThread::IfDummyThreadEndWait() {
         return;
     }
 
-    // Ensure the thread will no longer block.
-    dummy_wait_lock.Unlock();
+    // Wake up the waiting thread.
+    dummy_wait_cv.notify_one();
 }
 
 void KThread::BeginWait(KThreadQueue* queue) {
@@ -1107,9 +1100,6 @@ void KThread::BeginWait(KThreadQueue* queue) {
 
     // Set our wait queue.
     wait_queue = queue;
-
-    // Special case for dummy threads to ensure they block.
-    IfDummyThreadBeginWait();
 }
 
 void KThread::NotifyAvailable(KSynchronizationObject* signaled_object, ResultCode wait_result_) {
@@ -1158,10 +1148,11 @@ void KThread::SetState(ThreadState state) {
     SetMutexWaitAddressForDebugging({});
     SetWaitReasonForDebugging({});
 
-    const ThreadState old_state = thread_state;
-    thread_state =
-        static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask));
-    if (thread_state != old_state) {
+    const ThreadState old_state = thread_state.load(std::memory_order_relaxed);
+    thread_state.store(
+        static_cast<ThreadState>((old_state & ~ThreadState::Mask) | (state & ThreadState::Mask)),
+        std::memory_order_relaxed);
+    if (thread_state.load(std::memory_order_relaxed) != old_state) {
         KScheduler::OnThreadStateChanged(kernel, this, old_state);
     }
 }
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index c141fc11b8..4892fdf76e 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -6,6 +6,8 @@
 
 #include <array>
 #include <atomic>
+#include <condition_variable>
+#include <mutex>
 #include <span>
 #include <string>
 #include <utility>
@@ -257,11 +259,11 @@ public:
     [[nodiscard]] std::shared_ptr<Common::Fiber>& GetHostContext();
 
     [[nodiscard]] ThreadState GetState() const {
-        return thread_state & ThreadState::Mask;
+        return thread_state.load(std::memory_order_relaxed) & ThreadState::Mask;
     }
 
     [[nodiscard]] ThreadState GetRawState() const {
-        return thread_state;
+        return thread_state.load(std::memory_order_relaxed);
     }
 
     void SetState(ThreadState state);
@@ -643,7 +645,6 @@ public:
     // blocking as needed.
 
     void IfDummyThreadTryWait();
-    void IfDummyThreadBeginWait();
     void IfDummyThreadEndWait();
 
 private:
@@ -764,12 +765,13 @@ private:
     bool resource_limit_release_hint{};
     StackParameters stack_parameters{};
     Common::SpinLock context_guard{};
-    KSpinLock dummy_wait_lock{};
 
     // For emulation
     std::shared_ptr<Common::Fiber> host_context{};
     bool is_single_core{};
     ThreadType thread_type{};
+    std::mutex dummy_wait_lock;
+    std::condition_variable dummy_wait_cv;
 
     // For debugging
     std::vector<KSynchronizationObject*> wait_objects_for_debugging;