Java Academy · درس

المزالق: التثبيت وThreadLocals

تجنب حجب خيوط الحامل

الدرس 4 من 413 خطوة

المزالق: التثبيت وThreadLocals درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

ما قد يسوء

الخيوط الافتراضية قوية، لكن هناك مشكلتان قد تقوّضان قابليتها للتوسع دون أن تلاحظ ذلك: التثبيت والإفراط في استخدام ThreadLocals.

يشرح هذا الدرس المشكلتين وكيفية تجنبهما.

ما التثبيت

يحدث التثبيت عندما لا يستطيع خيط افتراضي إلغاء تثبيته من الخيط الحامل أثناء الحظر. ويبقى خيط نظام التشغيل الحامل عالقًا، مما يلغي فائدة قابلية التوسع.

وعندما يُثبَّت عدد كبير جدًا من الخيوط الحاملة، ينهار معدل الإنتاجية.

السبب: الحظر باستخدام synchronized

السبب التقليدي هو حدوث الحظر داخل كتلة أو طريقة synchronized. فعند الاحتفاظ بـ monitor ثم التعرض للحظر، يظل الخيط الافتراضي مثبتًا بالخيط الحامل الخاص به.

ملاحظة: في JDK 24 والإصدارات الأحدث (JEP 491)، أُزيل هذا القيد إلى حد كبير، لكنه يمثل مصدر قلق حقيقيًا في Java 21.

مثال على التثبيت يجب تجنبه

يمكن لهذا النمط أن يسبب التثبيت في Java 21: حدوث الحظر أثناء الاحتفاظ بـ monitor. سيستمر في العمل بصورة صحيحة، لكنه لا يتوسع جيدًا.

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 مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
104
الدروس
374

الأسئلة الشائعة

هل درس «المزالق: التثبيت وThreadLocals» مجاني؟

نعم — نص درس «المزالق: التثبيت وThreadLocals» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «المزالق: التثبيت وThreadLocals»؟

تجنب حجب خيوط الحامل تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.

كم من الوقت يستغرق درس «المزالق: التثبيت وThreadLocals»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. ما الخيوط الافتراضية
  2. إنشاء الخيوط الافتراضية
  3. الخيوط المنصّية مقابل الافتراضية
  4. المزالق: التثبيت وThreadLocals
← العودة إلى Java Academy