Lock: คืออะไร ประเภทของการล็อกและการใช้งานในการซิงโครไนซ์

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-19 เวลาอ่าน: 8 นาที

Lock คือกลไกการซิงโครไนซ์ที่ให้การเข้าถึงส่วนสำคัญของโค้ดแบบเอกสิทธิ์ในแอปพลิเคชันแบบหลายเธรด ตามข้อมูลของ Oracle, 2024 อินเทอร์เฟซ Lock ให้การควบคุมการซิงโครไนซ์ที่ยืดหยุ่นมากกว่าเมื่อเทียบกับบล็อก synchronized แบบดั้งเดิม รวมถึงความพยายามในการรับล็อกด้วย การหมดเวลา และรองรับคิวรอหลายรายการ

ประเด็นสำคัญ

  • Lock คืออินเทอร์เฟซสำหรับการจัดการล็อกอย่างชัดแจ้งใน Java
  • ReentrantLock คือการใช้งานพื้นฐานที่รองรับการรับล็อกซ้ำโดยเธรดเดียวกัน
  • ReadWriteLock แยกล็อกการอ่านและการเขียนเพื่อเพิ่มประสิทธิภาพ
  • Deadlock คือความเสี่ยงหลักเมื่อใช้หลายล็อกพร้อมกัน
  • แตกต่างจาก synchronized Lock รองรับการหมดเวลาและการรอที่สามารถขัดจังหวะได้

Lock คืออะไร?

Lock คืออินเทอร์เฟซจากแพ็คเกจ java.util.concurrent.locks ที่ให้การดำเนินการล็อกและปลดล็อกอย่างชัดแจ้งเพื่อซิงโครไนซ์การเข้าถึงข้อมูล แตกต่างจาก synchronized ตรงที่ Lock ให้ผู้พัฒนาควบคุมกลไกการล็อกได้อย่างสมบูรณ์

ความหมายและบทบาทในการซิงโครไนซ์

อินเทอร์เฟซ Lock ถูกนำมาใช้ใน Java 5 เพื่อเป็นทางเลือกแทนกลไก synchronized ในตัว เมธอดหลักคือ lock, unlock, tryLock และ lockInterruptibly ล็อกช่วยจัดระเบียบการเข้าถึงข้อมูลอย่างปลอดภัยในสภาพแวดล้อมแบบหลายเธรด ป้องกันสภาวะการแข่งขันและความเสียหายของข้อมูล

ข้อได้เปรียบหลักของ Lock เหนือ synchronized คือ ความยืดหยุ่น ผู้พัฒนาสามารถพยายามรับล็อกด้วยการหมดเวลา ตรวจสอบความพร้อมใช้งานโดยไม่ถูกบล็อก หรือจัดคิวรอหลายรายการที่มีลำดับความสำคัญต่างกัน

ประวัติและวิวัฒนาการ

ก่อนที่อินเทอร์เฟซ Lock จะปรากฏใน Java 5 วิธีการซิงโครไนซ์เพียงอย่างเดียวคือ synchronized ซึ่งมีข้อจำกัดหลายประการ: ไม่มีการหมดเวลา ไม่มีการรอที่สามารถขัดจังหวะได้ และคิวเดียว Doug Lea ออกแบบแพ็คเกจ java.util.concurrent โดยรวม Lock เป็นส่วนประกอบพื้นฐาน

ล็อกทำงานอย่างไร?

ล็อก จัดการการเข้าถึงผ่านแฟล็กสถานะภายในและคิวรอ เมื่อเธรดเรียก lock() กลไกจะตรวจสอบว่าล็อกว่างหรือไม่ และรับล็อกหรือวางเธรดในคิวจนกว่าจะถูกปล่อย

การรับและการปล่อยแบบอะตอมมิก

หัวใจของล็อกทุกตัวคือ การดำเนินการเปรียบเทียบและสลับแบบอะตอมมิก (CAS) เมื่อเรียก lock() เธรดจะพยายามตั้งค่าแฟล็กไม่ว่างแบบอะตอมมิก ถ้าแฟล็กถูกตั้งไว้แล้ว เธรดจะถูกบล็อก เมื่อ unlock() แฟล็กจะถูกล้างและเธรดที่รออยู่จะถูกปลุก

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // ส่วนสำคัญ
        println("เธรด ${Thread.currentThread().name} กำลังทำงาน")
    } finally {
        lock.unlock()
    }
}

คิวรอและการปลุก

ReentrantLock ใช้ภายในรายการเชื่อมโยงสองทิศทาง (คิวล็อก CLH) โดยแต่ละเธรดที่รอจะแสดงด้วยโหนด เมื่อล็อกถูกปล่อย โหนดหัวของคิวจะถูกปลุก โหมดยุติธรรม (fair) รับประกันลำดับ FIFO ในขณะที่โหมดไม่ยุติธรรมอนุญาตให้เธรดใหม่รับล็อกก่อนเธรดที่รอเพื่อเพิ่มปริมาณงาน

ประเภทหลักของล็อก

ในระบบนิเวศ Java สมัยใหม่ มีการใช้งาน ล็อก หลายแบบ แต่ละแบบถูกปรับให้เหมาะสมสำหรับสถานการณ์เฉพาะ การเลือกล็อกที่ถูกต้องส่งผลโดยตรงต่อประสิทธิภาพและความน่าเชื่อถือของแอปพลิเคชันแบบหลายเธรด

ReentrantLock

ReentrantLock คือการใช้งาน Lock พื้นฐานและใช้บ่อยที่สุด รองรับการรับล็อกซ้ำโดยเธรดเดียวกัน: ถ้าเธรดถือล็อกอยู่แล้ว การเรียก lock() อีกครั้งจะไม่บล็อกเธรดนั้น ซึ่งป้องกัน deadlock ในการเรียกซ้ำ

ReentrantReadWriteLock

ReadWriteLock แยกล็อกเป็นสองโหมด: อ่าน และเขียน หลายเธรดสามารถถือล็อกอ่านพร้อมกันได้ แต่การเขียนต้องการการเข้าถึงแบบเอกสิทธิ์ ซึ่งช่วยปรับปรุงประสิทธิภาพอย่างมากเมื่ออ่านบ่อยและเขียนน้อย

StampedLock

StampedLock คือการใช้งานล่าสุดที่นำมาใช้ใน Java 8 รองรับสามโหมด: เขียน อ่าน และอ่านแบบมองโลกในแง่ดี การอ่านแบบมองโลกในแง่ดีไม่บล็อกเธรดอื่นและตรวจสอบความถูกต้องของข้อมูลหลังจากอ่าน ซึ่งให้ประสิทธิภาพดีขึ้น 10-20% เมื่อเทียบกับ ReadWriteLock

ล็อกเวอร์ชัน Javaโหมดประสิทธิภาพ
ReentrantLockJava 5เอกสิทธิ์สูง
ReadWriteLockJava 5อ่าน + เขียนปานกลาง
StampedLockJava 8อ่าน + เขียน + มองโลกในแง่ดีสูงมาก

ReentrantLock และคุณสมบัติ

ReentrantLock คือการใช้งาน Lock ที่ได้รับความนิยมมากที่สุด ซึ่งมีคุณสมบัติหลายอย่างที่ไม่สามารถใช้ได้ใน synchronized การเข้าใจคุณสมบัติของมันเป็นสิ่งจำเป็นสำหรับการทำงานหลายเธรดอย่างมีประสิทธิภาพ

ความยุติธรรมของล็อก (fairness)

คอนสตรัคเตอร์ของ ReentrantLock รับพารามิเตอร์ fair เมื่อเป็น true ล็อกรับประกัน ลำดับ FIFO เมื่อเป็น false เธรดใหม่อาจรับล็อกก่อนเธรดที่รอ โหมดยุติธรรมป้องกันการอดอยาก แต่ลดปริมาณงานลง 10-20% เนื่องจากค่าใช้จ่ายในการดูแลคิว

การหมดเวลาและการรอที่สามารถขัดจังหวะได้

แตกต่างจาก synchronized ReentrantLock รองรับ tryLock แบบมีกำหนดเวลา ถ้าไม่สามารถรับล็อกภายในเวลาที่กำหนด เธรดจะดำเนินการต่อแทนที่จะถูกบล็อกอย่างไม่มีกำหนด เมธอด lockInterruptibly อนุญาตให้ขัดจังหวะเธรดที่รอผ่าน Thread.interrupt()

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("รับล็อกแล้ว")
        } finally {
            lock.unlock()
        }
    } else {
        println("ไม่สามารถรับล็อกได้")
    }
}

เงื่อนไข (Conditions)

ReentrantLock รองรับ ตัวแปรเงื่อนไข หลายตัวผ่านเมธอด newCondition() แต่ละ Condition มีคิวรอของตัวเอง ซึ่งช่วยให้สถานการณ์การปลุกที่ซับซ้อนเป็นไปได้ เมธอด await() และ signal() แทนที่ wait() และ notify() จากบล็อก synchronized แต่รองรับหลายคิว

ReadWriteLock และ StampedLock

ReadWriteLock และ StampedLock จัดการกับการเพิ่มประสิทธิภาพการเข้าถึงเมื่อการอ่านมีอิทธิพลเหนือการเขียน มีประสิทธิภาพมากกว่า ReentrantLock อย่างมากในสถานการณ์ที่การอ่านเกิดขึ้นบ่อยกว่าการเขียน

ReadWriteLock ในทางปฏิบัติ

อินเทอร์เฟซ ReadWriteLock มีสองเมธอด: readLock() และ writeLock() ล็อก อ่าน สามารถถือโดยหลายเธรดพร้อมกัน ในขณะที่ล็อกเขียนเป็นเอกสิทธิ์ ตัวอย่างทั่วไปคือแคชที่ปลอดภัยสำหรับเธรด: หลายเธรดอ่านข้อมูลในขณะที่มีเพียงหนึ่งเธรดที่อัปเดตเป็นระยะ

kotlin
class SafeCache<K, V> {
    private val map = mutableMapOf<K, V>()
    private val rwLock = ReentrantReadWriteLock()

    fun get(key: K): V? {
        rwLock.readLock().lock()
        return try { map[key] } finally { rwLock.readLock().unlock() }
    }

    fun put(key: K, value: V) {
        rwLock.writeLock().lock()
        return try { map[key] = value } finally { rwLock.writeLock().unlock() }
    }
}

StampedLock และการอ่านแบบมองโลกในแง่ดี

StampedLock เพิ่มโหมดที่สาม — tryOptimisticRead โหมดนี้ไม่บล็อกเธรดอื่น แต่เพียงบันทึกตราประทับ (stamp) ของสถานะ หลังจากอ่าน ผู้พัฒนาเรียก validate(stamp) เพื่อตรวจสอบว่าข้อมูลเปลี่ยนแปลงระหว่างการอ่านหรือไม่ ถ้าข้อมูลเปลี่ยนแปลง การดำเนินการต้องทำซ้ำ

ล็อกในการพัฒนามือถือ

ในแอปพลิเคชันมือถือ ล็อก ถูกใช้เพื่อประสานงานการเข้าถึงข้อมูลที่ใช้ร่วมกันระหว่างเธรด อย่างไรก็ตาม การใช้งานต้องใช้ความระมัดระวังเป็นพิเศษเนื่องจากทรัพยากรอุปกรณ์มีจำกัดและความจำเป็นในการรักษาการตอบสนองของอินเทอร์เฟซ

ล็อกใน Android (Kotlin)

บน Android ReentrantLock มีประโยชน์เมื่อทำงานกับ Room แคช และไฟล์ สิ่งสำคัญที่ต้องจำ: ห้ามรับล็อกบนเธรดหลัก สำหรับโค้ดแบบอะซิงโครนัส coroutine และ Mutex จาก kotlinx.coroutines ดีกว่า เพราะจะระงับ coroutine แทนที่จะบล็อกเธรด

ล็อกใน iOS (Swift)

ใน iOS มีการใช้ NSLock มาตรฐานน้อยกว่า — นักพัฒนาชอบ DispatchQueue ที่มีแฟล็ก barrier หรือ os_unfair_lock Swift 5.7+ มีกลไกการซิงโครไนซ์ที่ทันสมัยผ่าน actors ซึ่งปกป้องสถานะโดยอัตโนมัติ

swift
import Foundation

actor DataStore {
    private var items: [String] = []

    func add(_ item: String) {
        items.append(item)
    }

    func getAll() -> [String] {
        items
    }
}

คำแนะนำในการป้องกัน deadlock

เพื่อหลีกเลี่ยง deadlock ให้ปฏิบัติตาม ลำดับการรับล็อกที่สอดคล้องกัน ทั่วทั้งโปรเจกต์ ใช้ tryLock แบบมีกำหนดเวลาแทน lock() ทุกที่ที่อาจมีการบล็อกเป็นเวลานาน พิจารณาใช้อัลกอริทึมแบบ Lock-Free (AtomicReference, ConcurrentHashMap) แทนล็อกแบบดั้งเดิม

แนวปฏิบัติที่ดีที่สุดในการทำงานกับ Lock

การใช้ Lock ต้องมีวินัยและการปฏิบัติตามกฎหลายข้อที่ป้องกัน deadlock และประสิทธิภาพที่ลดลง แนวปฏิบัติเหล่านี้ได้รับการพัฒนาโดยชุมชน Java ตลอด 20 ปีของการใช้แพ็คเกจ java.util.concurrent

ปล่อยใน finally

รูปแบบที่สำคัญที่สุดคือ ล็อกใน finally ไม่ว่าส่วนสำคัญจะเสร็จสมบูรณ์หรือเกิดข้อยกเว้น ล็อกต้องถูกปล่อย ซึ่งรับประกันว่าเธรดอื่นจะไม่ถูกบล็อกตลอดไปเนื่องจากข้อผิดพลาดเดียว ใน Kotlin รูปแบบนี้แก้ไขอย่างสง่างามผ่านส่วนขยาย withLock

ลดระยะเวลาการถือครอง

ส่วนสำคัญควร สั้นที่สุดเท่าที่จะทำได้ อย่าทำ I/O คำขอเครือข่าย หรือการคำนวณที่ยาวนานภายในล็อก ถ้าคุณต้องการอ่านข้อมูลจากเซิร์ฟเวอร์ ให้รับข้อมูลก่อน จากนั้นรับล็อกเพียงเพื่ออัปเดตสถานะที่ใช้ร่วมกัน ซึ่งลดการแข่งขันและเพิ่มปริมาณงานของระบบ

ลำดับการรับล็อกที่สอดคล้องกัน

เพื่อป้องกัน deadlock เมื่อทำงานกับหลายล็อก ให้กำหนด ลำดับการรับล็อกทั่วโลก ในโปรเจกต์ ถ้า lockA ถูกรับก่อน จากนั้น lockB — ลำดับย้อนกลับใดๆ ต้องถูกห้ามโดยกฎการตรวจสอบโค้ด ใช้ตัววิเคราะห์แบบคงที่เช่น SpotBugs และ IntelliJ Inspections สำหรับการตรวจสอบอัตโนมัติ

คำถามที่พบบ่อย

ความแตกต่างระหว่าง Lock และ synchronized คืออะไร?

Lock คืออินเทอร์เฟซที่ชัดแจ้งพร้อมรองรับการหมดเวลาและการรอที่สามารถขัดจังหวะได้ synchronized รับและปล่อยมอนิเตอร์โดยอัตโนมัติ แต่ไม่อนุญาตให้ใช้ tryLock, lockInterruptibly หรือหลาย Conditions Lock ยืดหยุ่นกว่าแต่ต้องการการปล่อยด้วยตนเองใน finally

ล็อกแบบยุติธรรม (fair lock) คืออะไร?

ล็อกแบบยุติธรรมรับประกัน ลำดับ FIFO: เธรดที่รอนานที่สุดจะได้รับล็อกก่อน ล็อกแบบไม่ยุติธรรมอาจให้การเข้าถึงแก่เธรดใหม่ก่อนเธรดที่รอ ซึ่งเพิ่มปริมาณงานแต่อาจทำให้เธรดที่รอเกิดการอดอยาก

วิธีหลีกเลี่ยง deadlock เมื่อใช้ Lock?

ปฏิบัติตาม ลำดับที่แน่นอน สำหรับการรับล็อกทั้งหมด ใช้ tryLock แบบมีกำหนดเวลาแทน lock() ที่ไม่มีเงื่อนไข และลดจำนวนล็อกที่ถือพร้อมกัน การใช้โครงสร้างข้อมูลแบบ Lock-Free ก็ลดความเสี่ยงของ deadlock เช่นกัน

Condition ใน Lock คืออะไร?

Condition คืออะนาล็อกของ wait/notify สำหรับ Lock ซึ่งช่วยให้มีคิวรออิสระหลายคิว แต่ละการเรียก newCondition() สร้างคิวแยกต่างหาก ให้การควบคุมการปลุกเธรดที่แม่นยำกว่าเมื่อเทียบกับคิวเดียวของ synchronized

ควรเลือกล็อกใดสำหรับแอปพลิเคชันมือถือ?

สำหรับ Android ที่ใช้ coroutine ให้ใช้ Mutex จาก kotlinx.coroutines — มันระงับ coroutine แทนที่จะบล็อกเธรด สำหรับ iOS ที่ใช้ Swift 5.7+ actors ดีกว่าเพราะซิงโครไนซ์การเข้าถึงสถานะโดยอัตโนมัติ เก็บ ReentrantLock ไว้สำหรับโค้ดเก่าและสถานการณ์ระดับต่ำ

สรุป

  • Lock คืออินเทอร์เฟซการจัดการล็อกอย่างชัดแจ้งจาก java.util.concurrent.locks
  • ReentrantLock คือการใช้งานหลักที่รองรับการรับซ้ำและความยุติธรรม
  • ReadWriteLock แยกล็อกอ่านและเขียนสำหรับสถานการณ์ที่เน้นการอ่าน
  • StampedLock เพิ่มการอ่านแบบมองโลกในแง่ดีเพื่อประสิทธิภาพสูงสุด
  • การหมดเวลาและ Conditions คือข้อได้เปรียบหลักของ Lock เหนือ synchronized
  • Deadlock ถูกป้องกันด้วยลำดับการรับล็อกที่สอดคล้องกันและการใช้ tryLock
  • ในการพัฒนามือถือ แนะนำ coroutine (Android) และ actors (iOS)

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม