Java Academy · บทเรียน

ข้อควรระวัง: การตรึงและ ThreadLocals

หลีกเลี่ยงการบล็อกเธรดพาหะ

บทเรียน 4 จาก 413 ขั้นตอน

ข้อควรระวัง: การตรึงและ ThreadLocals เป็นบทเรียน Java Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Java Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

สิ่งที่อาจผิดพลาดได้

เธรดเสมือนมีประสิทธิภาพสูง แต่ข้อควรระวังสองประการอาจบั่นทอนความสามารถในการรองรับการขยายได้อย่างเงียบ ๆ ได้แก่ การตรึงเธรดและการใช้ ThreadLocals มากเกินไป

บทเรียนนี้จะอธิบายทั้งสองเรื่องและวิธีหลีกเลี่ยง

การตรึงเธรดคืออะไร

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

เมื่อมีเธรดพาหะถูกตรึงมากเกินไป ปริมาณงานที่ประมวลผลได้จะลดลงอย่างรวดเร็ว

สาเหตุ: การรอแบบขัดขวางใน synchronized

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

หมายเหตุ: ตั้งแต่ JDK 24 ขึ้นไป (JEP 491) ข้อจำกัดนี้ถูกนำออกไปเป็นส่วนใหญ่แล้ว แต่ใน Java 21 ยังคงเป็นข้อควรระวังที่สำคัญ

ตัวอย่างการตรึงเธรดที่ควรหลีกเลี่ยง

รูปแบบนี้อาจทำให้เกิดการตรึงเธรดใน Java 21 นั่นคือการรอแบบขัดขวางขณะถือครองมอนิเตอร์ โค้ดยังคงทำงานได้อย่างถูกต้อง แต่ไม่สามารถรองรับการขยายได้

public class Main {
    static final Object lock = new Object();

    static void risky() {
        synchronized (lock) {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::risky);
        t.join();
        System.out.println("Done (but this blocked inside synchronized)");
    }
}

วิธีแก้: ReentrantLock

แทนที่ synchronized ด้วย ReentrantLock เมื่อส่วนวิกฤตอาจเข้าสู่ภาวะรอแบบขัดขวาง ส่วนติดต่อสำหรับล็อกเป็นมิตรต่อเธรดเสมือนและเปิดให้ถอดเธรดออกได้

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    static final ReentrantLock lock = new ReentrantLock();

    static void safe() {
        lock.lock();
        try {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::safe);
        t.join();
        System.out.println("Done without pinning");
    }
}

การวินิจฉัยการตรึงเธรด

คุณสามารถสั่งให้ JVM พิมพ์ร่องรอยสแตกทุกครั้งที่เกิดการตรึงเธรดได้โดยเปิดใช้งานด้วย:

  • -Djdk.tracePinnedThreads=full สำหรับร่องรอยทั้งหมด
  • -Djdk.tracePinnedThreads=short สำหรับผลลัพธ์แบบบรรทัดเดียว

วิธีนี้ช่วยให้คุณค้นหาส่วน synchronized ที่เป็นต้นเหตุได้

ThreadLocals มีต้นทุนสูงขึ้น

ThreadLocal เก็บสถานะเฉพาะเธรดไว้ สำหรับเธรดแพลตฟอร์มไม่กี่เธรด เรื่องนี้ไม่มีปัญหา แต่เมื่อมีเธรดเสมือนหลายล้านเธรดและแต่ละเธรดมีสำเนาของตนเอง การใช้หน่วยความจำจะเพิ่มขึ้นอย่างมาก

ตรวจสอบไลบรารีของคุณ: การเก็บข้อมูลไว้ใช้ซ้ำ การจัดรูปแบบ และวัตถุบริบทมักซ่อน ThreadLocals ไว้ภายใน

ThreadLocal ยังคงใช้งานได้

ไม่ได้ห้ามใช้ ThreadLocal เพียงแต่ควรใช้เท่าที่จำเป็น เธรดเสมือนแต่ละเธรดจะได้รับค่าของตนเองตามที่คาดไว้

public class Main {
    static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(() -> {
            CONTEXT.set("request-42");
            System.out.println("Context: " + CONTEXT.get());
            CONTEXT.remove();
        });
        t.join();
    }
}

ควรเลือกใช้ ScopedValue

Java เพิ่ม ScopedValue เป็นทางเลือกที่ใช้ทรัพยากรน้อยกว่าสำหรับแบ่งปันข้อมูลที่ไม่เปลี่ยนแปลงและมีอายุการใช้งานที่จำกัดขอบเขต วิธีนี้หลีกเลี่ยงต้นทุนของพื้นที่เก็บข้อมูลแบบเปลี่ยนแปลงได้ต่อเธรดของ ThreadLocal และเข้ากับรูปแบบการทำงานพร้อมกันแบบมีโครงสร้าง

เมื่อคุณเพียงต้องส่งบริบทแบบอ่านอย่างเดียวลงไปตามสายการเรียกใช้ ให้เลือกใช้สิ่งนี้แทน

อย่ารวมเธรดเป็นกลุ่ม

ข้อควรระวังอีกประการหนึ่งคือการปฏิบัติต่อเธรดเสมือนราวกับเป็นทรัพยากรหายากแล้วรวมไว้เป็นกลุ่ม วิธีดังกล่าวทำให้เกิดการแย่งใช้ทรัพยากรและการรั่วไหลของ ThreadLocal ระหว่างงานขึ้นมาอีกครั้ง

สร้างเธรดเสมือนหนึ่งเธรดต่องานเสมอ แล้วปล่อยให้เธรดสิ้นสุดการทำงาน

รายการตรวจสอบเพื่อรองรับการขยาย

ก่อนนำเธรดเสมือนไปใช้จริง:

  • แทนที่ synchronized ที่มีการรอแบบขัดขวางด้วย ReentrantLock
  • เปิดใช้ jdk.tracePinnedThreads ในการทดสอบ
  • ลดการใช้ ThreadLocal และเลือกใช้ ScopedValue แทน
  • อย่ารวมเธรดเสมือนเป็นกลุ่ม

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ระบุสิ่งทดแทนที่ปลอดภัยสำหรับบล็อก synchronized ที่มีการรอแบบขัดขวาง

สรุป

คุณได้เรียนรู้ข้อควรระวังหลักแล้ว:

  • การตรึงเธรด: การรอแบบขัดขวางภายใน synchronized ทำให้เธรดพาหะไม่ว่าง แก้ไขด้วย ReentrantLock
  • วินิจฉัยด้วย -Djdk.tracePinnedThreads
  • ต้นทุนของ ThreadLocal เพิ่มขึ้นตามจำนวนเธรด ควรเลือกใช้ ScopedValue แทน
  • อย่ารวมเธรดเสมือนเป็นกลุ่ม

หลักสูตรเธรดเสมือนจบลงเพียงเท่านี้

เริ่มต้นได้ฟรี

เรียนรู้ Java ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
104
บทเรียน
374

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

บทเรียน “ข้อควรระวัง: การตรึงและ ThreadLocals” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ข้อควรระวัง: การตรึงและ ThreadLocals” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Java Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Java Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ข้อควรระวัง: การตรึงและ ThreadLocals”

หลีกเลี่ยงการบล็อกเธรดพาหะ คุณปฏิบัติ Java Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Java Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Java Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “ข้อควรระวัง: การตรึงและ ThreadLocals” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Java Academy นี้ได้ไหม

ได้ บทเรียน Java Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เธรดเสมือนคืออะไร
  2. การสร้างเธรดเสมือน
  3. เธรดแพลตฟอร์มเทียบกับเธรดเสมือน
  4. ข้อควรระวัง: การตรึงและ ThreadLocals
← กลับไปที่ Java Academy