1. الاستخدام الأساسي لمسبح الخيوط
1.1. لماذا تحتاج إلى تجمع الخيوط؟
في الأعمال اليومية ، إذا أردنا استخدام الخيوط المتعددة ، فسنقوم بإنشاء مؤشرات ترابط قبل بدء العمل ، وتدمير المواضيع بعد انتهاء العمل. ومع ذلك ، بالنسبة للأعمال التجارية ، فإن إنشاء وتدمير المواضيع لا علاقة له بالأعمال نفسها ، ولا يهتم سوى المهام التي يؤديها الخيط. لذلك ، آمل أن أستخدم أكبر عدد ممكن من وحدات المعالجة المركزية لأداء المهام ، بدلاً من إنشاء وتدمير المواضيع غير المرتبطة بالأعمال. تجمع الخيوط يحل هذه المشكلة. وظيفة تجمع الخيوط هي إعادة استخدام الخيوط.
1.2. ما هو الدعم الذي توفره JDK لنا
يتم عرض المخططات الفئة ذات الصلة في JDK في الشكل أعلاه.
العديد من الفئات الخاصة التي يجب ذكرها.
تشبه الفئة القابلة للاتصال الفئة القابلة للتشغيل ، ولكن الفرق هو أن القابل للاتصال له قيمة إرجاع.
Threadpoolexecutor هو تطبيق مهم لمجمعات الخيوط.
المنفذون هو فئة المصنع.
1.3. استخدام حمامات الخيوط
1.3.1. أنواع برك الخيوط
static static evecororservice newfixedthreadpool (int nthreads) {return new threadpoolexecutor (nthreads ، nthreads ، 0l ، timeUnit.milliseconds ، new LinkedBlockingqueue <Runnable>) ؛ threadpoolexecutor (1 ، 1 ، 0L ، timeUnit.millisEconds ، new LinkedBlockingqueue <Runnable>)) ؛} executorservice newCachedThreadPool ()من منظور الطريقة ، من الواضح أن FlexThreadPool و SingleThreadExecutor و CacheDthreadPool هي حالات مختلفة من ThreadPoolexecutor ، ولكن المعلمات مختلفة.
threadpoolexecutor العامة (int corepoolsize ، int maximumpoolsize ، keepalivetime الطويل ، الوحدة الزمنية ، blockingqueue <runnable> workqueue) {this (corepoolsize ، Maximumpoolsize ، keepalivetim دعنا نصف بإيجاز معنى المعلمات في مُنشئ ThreadPoolexecutor.
وبهذه الطريقة ، عند النظر إلى الثابتة المذكورة أعلاه ، فإن عدد النوى والحد الأقصى لعدد الخيوط هو نفسه ، بحيث لن يتم إنشاء مؤشرات الترابط وتدميرها أثناء العمل. عندما يكون عدد المهام كبيرة ولا يمكن استيفاء الخيوط الموجودة في تجمع الخيوط ، سيتم حفظ المهمة إلى LinkedBlockingQueue ، وحجم LinkedBlockingQueue هو integer.max_value. هذا يعني أن الإضافة المستمرة للمهام ستجعل الذاكرة تستهلك أكثر وأكثر.
المخزونات المخبأة مختلفة. رقم مؤشر الترابط الأساسي الخاص به هو 0 ، والحد الأقصى لعدد التخزين هو integer.max_value ، وقائمة انتظار الحظر الخاصة بها متزامنة ، وهي قائمة انتظار خاصة ، وحجمها هو 0. لأن عدد المواضيع الأساسية هو 0 ، من الضروري إضافة المهمة إلى متزامنة. لا يمكن أن تنجح قائمة الانتظار هذه إلا عندما يضيف مؤشر ترابط بيانات منه ويحصل مؤشر ترابط آخر منه. ستؤدي إضافة البيانات إلى قائمة الانتظار وحدها إلى إرجاع الفشل. عندما تفشل العائد ، يبدأ تجمع مؤشرات الترابط في توسيع نطاق الخيط ، وهذا هو السبب في عدم إصلاح عدد مؤشرات الترابط في CacheDthreadPool. عندما لا يتم استخدام الخيط لمدة 60s ، يتم تدمير الخيط.
1.4. أمثلة صغيرة لاستخدام تجمع الخيوط
1.4.1. تجمع الخيوط البسيطة
استيراد java.util.concurrent.executorservice ؛ استيراد java.util.concurrent.executors ؛ الفئة العامة threadpooldemo {الفئة الثابتة العامة myTask تنفذ {{ @ @thone) حاول {thread.sleep (1000) ؛ } catch (استثناء e) {E.PrintStackTrace () ؛ }}} public static void main (string [] args) {myTask myTask = new MyTask () ؛ ExecutorService es = Executors.NewFixedThreadPool (5) ؛ لـ (int i = 0 ؛ i <10 ؛ i ++) {es.submit (myTask) ؛ }}} نظرًا لاستخدام NewfixedThreadPool (5) ، ولكن يتم تشغيل 10 مؤشرات ترابط ، يتم تنفيذ 5 في وقت واحد ، ومن الواضح أن إعادة استخدام سلسلة الرسائل. يتكرر ThreadId ، أي المهام الخمس الأولى ويتم تنفيذ آخر 5 مهام من نفس الدفعة من المواضيع.
ما هو المستخدم هنا
es.submit (myTask) ؛
هناك أيضًا طريقة لتقديم:
es.execute (myTask) ؛
الفرق هو أن إرسال سيعيد كائنًا مستقبليًا ، والذي سيتم تقديمه لاحقًا.
1.4.2.ScheduledThreadPool
استيراد java.util.concurrent.executors ؛ استيراد java.util.concurrent.ScheduledExecutorService ؛ استيراد java.util.concurrent.timeunit ؛ الطبقة العامة threadpooldemo {public static void main (string [] // إذا لم يتم الانتهاء من المهمة السابقة ، فلن يبدأ الإرسال. ses.schedulewithfixeddelay (new raNnable () {Override public void run () {try {thread.sleep (1000) ؛ system.out.println (System.CurrentTimeMillis ()/1000) ؛ ثواني ، ثم تنفيذ مرة كل ثانيتين في دورة}}الإخراج:
1454832514
1454832517
1454832520
1454832523
1454832526
...
نظرًا لأن تنفيذ المهمة يستغرق ثانية واحدة ، يجب أن تنتظر جدولة المهمة إكمال المهمة السابقة. وهذا يعني أن كل ثانيتين هنا تعني أن المهمة الجديدة ستبدأ بعد ثانيتين من اكتمال المهمة السابقة.
2. تمديد وتعزيز تجمع الخيوط
2.1. واجهة رد الاتصال
هناك بعض واجهات برمجة تطبيقات رد الاتصال في مجموعة الخيوط لتزويدنا بعمليات ممتدة.
ExecutorService es = new threadpoolexecutor (5 ، 5 ، 0L ، timeUnit.Seconds ، new LinkedBlockingQueue <Runnable> ()) } override proted void afterexecute (runnable r ، thrainable t) {system.out.println ("execution upplication") ؛ } override void inited () {system.out.println ("Thread Pool Exit") ؛ }} ؛يمكننا تنفيذ أساليب Execute و AfterExecute وإنهاء ThreadPoolExecutor لتنفيذ إدارة السجل أو عمليات أخرى قبل تنفيذ وبعد مؤشرات الترابط ، خروج تجمع مؤشرات الترابط.
2.2. استراتيجية الرفض
في بعض الأحيان ، تكون المهام ثقيلة للغاية ، مما يؤدي إلى الكثير من الحمل على النظام. كما ذكر أعلاه ، عندما يزداد عدد المهام ، سيتم وضع جميع المهام في قائمة انتظار الحظر لـ FlexThreadPool ، مما يؤدي إلى الكثير من استهلاك الذاكرة وفي النهاية في تدفق الذاكرة. يجب تجنب مثل هذه المواقف. لذلك عندما نجد أن عدد المواضيع يتجاوز الحد الأقصى لعدد الخيوط ، يجب أن نتخلى عن بعض المهام. عند التخلص من ذلك ، يجب علينا كتابة المهمة بدلاً من رميها مباشرة.
هناك مُنشئ آخر في Threadpoolexecutor.
public threadpoolexecutor (int corePoolsize ، int maximumpoolsize ، keepalivetime الطويل ، الوحدة الزمنية ، blockingqueue <runnable> workqueue ، threadfactory threadfactory ، desideedExecutionHandler معالج) غير unalfalArgumentException () ؛ if (workqueue == null || threadfactory == null || handler == null) رمي nullpointerexception () ؛ this.corepoolsize = corePoolSize ؛ this.maximumpoolsize = maximumpoolsize ؛ this.workqueue = workqueue ؛ this.keepalivetime = unit.tonanos (keepalivetime) ؛ this.threadfactory = threadfactory ؛ this.handler = معالج ؛ }
سنقدم ThreadFactory لاحقًا.
يرفض المعالج تنفيذ السياسة ، والتي ستخبرنا بما يجب القيام به إذا كان لا يمكن تنفيذ المهمة.
هناك 4 استراتيجيات أعلاه.
Abortpolicy: إذا كان لا يمكن قبول المهمة ، يتم طرح استثناء.
CallerRunspolicy: إذا كان لا يمكن قبول المهمة ، فدع مؤشر ترابط الاتصال يكتمل.
DissardoldestPolicy: إذا كان لا يمكن قبول المهمة ، فسيتم التخلص من المهمة الأقدم والاحتفاظ بها بواسطة قائمة انتظار.
DiscardPolicy: إذا كان لا يمكن قبول المهمة ، فسيتم التخلص من المهمة.
ExecutorService es = new threadpoolexecutor (5 ، 5 ، 0L ، timeUnit.Seconds ، New LinkedBlockingQueue <Runnable> () ، جديد refectedExecutionHandler () { @override refectedExecution (Runnable r ، threadpoolexecutor) }) ؛ بالطبع ، يمكننا أيضًا تنفيذ واجهة DesideDexecutionHandler بأنفسنا لتحديد سياسة الرفض بأنفسنا.
2.3. تخصيص ThreadFactory
لقد رأيت للتو أنه يمكن تحديد ThreadFactory في مُنشئ ThreadPoolexecutor.
يتم إنشاء مؤشرات الترابط في تجمع الخيوط بواسطة مصنع الخيوط ، ويمكننا تخصيص مصنع الخيوط.
مصنع الموضوع الافتراضي:
فئة ثابتة DefaultThreadFactory تنفذ ThreadFactory {private static final final atomicinteger poolnumber = new AtomicInteger (1) ؛ مجموعة الخيوط النهائية الخاصة ؛ private Final AtomicInteger Threadnumber = new AtomicInteger (1) ؛ سلسلة نهائية خاصة nameprefix ؛ defaultThreadFactory () {securityManager s = system.getSecurityManager () ؛ المجموعة = (s! = فارغة)؟ S.GetThReadGroup (): thread.currentThRead (). getThReadGroup () ؛ nameprefix = "pool-" + poolnumber.getandincrement () + "-Thread-" ؛ } مؤشر الترابط العام newThread (runnable r) {thread t = new thread (group ، r ، nameprefix + threadnumber.getandincrement () ، 0) ؛ if (t.isdaemon ()) t.setdaemon (false) ؛ if (t.getPriority ()! = thread.norm_priority) t.setPriority (thread.norm_priority) ؛ العودة ر ؛ }}3. forkjoin
3.1. أفكار
هذه هي فكرة الانقسام والقهر.
الشوكة/الانضمام تشبه خوارزمية MapReduce. الفرق بين الاثنين هو: يتم تقسيم الشوكة/الانضمام إلى مهام صغيرة فقط عند الضرورة ، كما لو كانت المهمة كبيرة جدًا ، بينما تبدأ MapReduce دائمًا في إجراء الخطوة الأولى للتجزئة. يبدو أن الشوكة/الانضمام أكثر ملاءمة لمستوى مؤشر الترابط داخل JVM ، في حين أن MapReduce مناسب للأنظمة الموزعة.
4.2. استخدام الواجهة
RecursiveAction: لا قيمة الإرجاع
recursivetask: هناك قيمة إرجاع
4.3. مثال بسيط
استيراد java.util.arraylist ؛ استيراد java.util.concurrent.forkjoinpool ؛ استيراد java.util.concurrent.forkjointask ؛ استيراد java.util.concurrent.recursivetask ؛ counttask public recursivetask <brong> {private intin int thateshold ؛ بداية طويلة خاصة ؛ نهاية طويلة counttask العامة (بداية طويلة ، نهاية طويلة) {super () ؛ this.start = start ؛ this.end = نهاية ؛ } override محمية long compute () {long sum = 0 ؛ boolean cancompute = (end - start) <عتبة ؛ if (canCompute) {for (long i = start ؛ i <= end ؛ i ++) {sum = sum+i ؛ }} آخر {// تقسيم إلى 100 مهمة صغيرة خطوة طويلة = (ابدأ + نهاية)/100 ؛ ArrayList <CountTask> subtasks = new ArrayList <CountTask> () ؛ نقاط البيع طويلة = ابدأ ؛ لـ (int i = 0 ؛ i <100 ؛ i ++) {long lastone = pos+step ؛ if (lastone> end) {lastone = end ؛ } counttask subtask = new counttask (pos ، lastone) ؛ POS + = Step + 1 ؛ subtasks.add (المهام الفرعية) ؛ subtask.fork () ؛ // push subtsks to thread pool} لـ (counttask t: subtasks) {sum += t.join () ؛ // في انتظار جميع المهام الفرعية إلى نهاية}} ؛ } main static void main (string [] args) {forkjoinpool forkjoinpool = new forkjoinpool () ؛ CountTask Task = New CountTask (0 ، 200000L) ؛ forkjointask <trong> result = forkjoinpool.submit (Task) ؛ حاول {long res = result.get () ؛ System.out.println ("sum =" + res) ؛ } catch (استثناء e) {// todo: التعامل مع الاستثناء e.printStackTrace () ؛ }}} يصف المثال أعلاه مهمة تلخيص. قسّم المهام المتراكمة إلى 100 مهمة ، لا تنفذ كل مهمة سوى مجموع الأرقام ، وبعد الانضمام النهائي ، يتم تجميع المبلغ المحسوب بواسطة كل مهمة.
4.4. عناصر التنفيذ
4.4.1.Workqueue و CTL
سيكون لكل موضوع قائمة انتظار عمل
static final classequeue
في قائمة انتظار العمل ، ستكون هناك سلسلة من الحقول التي تدير مؤشرات الترابط.
تقلب int eventCount ؛ // عدد تعطيل التشفير ؛ <0 إذا كان غير نشط
int nextwait ؛ // سجل مشفر لنادل الحدث التالي
int يضيق // عدد الفولاذ
int تلميح // تلميح مؤشر الصلب
poolindex القصير. // فهرس هذا قائمة الانتظار في البلياردو
الوضع القصير النهائي ؛ // 0: LIFO ،> 0: FIFO ، <0: مشترك
تقلب int qlock ؛ // 1: مغلق ، -1: إنهاء ؛ آخر 0
قاعدة متقلبة // مؤشر الفتحة التالية للاستطلاع
int أعلى // فهرس الفتحة التالية للدفع
forkjointask <؟> [] صفيف ؛ // العناصر (غير مخصصة في البداية)
تجمع Forkjoinpool النهائي. // تجمع يحتوي (قد يكون فارغًا)
مالك ForkJoinWorkerThread النهائي ؛ // امتلاك مؤشر ترابط أو فارغ إذا مشاركة
الخيط المتقلوب باركر. // == المالك أثناء الاتصال إلى Park ؛ آخر لاغ
متقلبة forkjointask <؟> currentjoin ؛ // المهمة التي انضمت إليها في انتظار
ForkJointask <؟> CurrentSteal ؛ // يتم تنفيذ المهمة الحالية غير المحلية
تجدر الإشارة هنا إلى أن هناك فرقًا كبيرًا بين JDK7 و JDK8 في تنفيذ ForkJoin. ما نقدمه هنا هو من JDK8. في تجمع الخيوط ، في بعض الأحيان لا يتم تنفيذ جميع الخيوط ، سيتم تعليق بعض الخيوط ، وسيتم تخزين هذه الخيوط المعلقة في مكدس. يتم تمثيله داخليًا بقائمة مرتبطة.
ستشير NextWait إلى موضوع الانتظار التالي.
فهرس الفهرس للفرقة في تجمع مؤشر ترابط البركة.
EventCount عند تهيئته ، يرتبط EventCount بـ poolindex. ما مجموعه 32 بت ، تشير البت الأول إلى ما إذا كان تم تنشيطه ، و 15 بت تشير إلى عدد المرات التي تم تعليقها
EventCount ، الباقي يمثل poolindex. استخدم حقلًا واحدًا لتمثيل معاني متعددة.
يتم تمثيل WorkQueue WorkQueue بواسطة مجموعة ForkJointask <؟> []. تمثل الأعلى والقاعدة كلا طرفي قائمة الانتظار ، والبيانات بين هذين.
الحفاظ على CTL (النوع الطويل 64 بت) في forkjoinpool
تقلبات طويلة CTL.
* الحقل CTL هو معبأ طويل مع:
* AC: عدد العمال النشطين النشط ناقص التوازي المستهدف (16 بت)
* TC: عدد إجمالي العمال ناقص التوازي المستهدف (16 بت)
* ST: صحيح إذا تم إنهاء البلياردو (1 بت)
* EC: عدد الانتظار لخيط الانتظار العلوي (15 بت)
* ID: Poolindex من Top of Treiber Stack من النوادل (16 بت)
يمثل AC عدد الخيوط النشطة ناقص درجة التوازي (ربما عدد وحدات المعالجة المركزية)
TC يعني إجمالي عدد المواضيع ناقص التوازي
يشير ST إلى ما إذا تم تنشيط تجمع الخيوط نفسه
يمثل EC عدد الخيوط المعلقة في وقت الانتظار العلوي
يشير المعرف إلى poolindex في انتظار الخيط في الأعلى
من الواضح أن معرف ST+EC+هو ما أطلقنا عليه للتو EventCount.
فلماذا عليك توليف متغير مع 5 متغيرات؟ في الواقع ، فإن القدرة تشغل نفس الشيء مع 5 متغيرات.
ستكون قابلية استخدام رمز متغير أسوأ بكثير.
فلماذا استخدام متغير؟ في الواقع ، هذا هو الشيء الأكثر ذكاءً ، لأن هذه المتغيرات الخمسة ككل. في متعدد الخيوط ، إذا تم استخدام 5 متغيرات ، ثم عند تعديل أحد المتغيرات ، كيفية ضمان سلامة المتغيرات الخمسة. ثم باستخدام متغير سيحل هذه المشكلة. إذا تم حلها باستخدام الأقفال ، فسيتم تدهور الأداء.
استخدام متغير يضمن اتساق وذرية البيانات.
يتم إجراء التغييرات على Forkjoin Squadron CTL باستخدام عمليات CAS. كما هو مذكور في السلسلة السابقة من المقالات ، تعد CAS عملية خالية من القفل ولديها أداء جيد.
نظرًا لأن عمليات CAS يمكن أن تستهدف متغيرًا واحدًا فقط ، فإن هذا التصميم هو الأمثل.
4.4.2. سرقة العمل
بعد ذلك ، سنقدم سير عمل تجمع الخيوط بأكمله.
كل موضوع يستدعي Runworker
Final void Runworker (workqueue w) {w.growarray () ؛ // تخصيص قائمة انتظار لـ (int r = w.hint ؛ scan (w ، r) == 0 ؛) {r ^= r << 13 ؛ r ^= r >>> 17 ؛ r ^= r << 5 ؛ // xorshift}} وظيفة Scan () هي المسح الضوئي للمهام التي يتعين القيام بها.
R هو رقم عشوائي نسبيا.
فحص int النهائي الخاص (workqueue w ، int r) {workqueue [] ws ؛ int m ؛ طويل C = CTL ؛ // للتحقق من الاتساق إذا ((ws = workqueues)! = null && (m = ws.length - 1)> = 0 && w! = null) {for (int j = m + m + 1 ، ec = w.eventCount ؛؛) {workqueue q ؛ int b ، e ؛ forkjointask <؟> [] a ؛ forkjointask <؟> t ؛ if ((q = ws [(r - j) & m])! = null && (b = q.base) - q.top <0 && (a = q.array)! = null) {long i = (((A.Length - 1) & b) << ashift) + abase ؛ if ((t = ((forkjointask <؟>) U.GetObjectVolatile (a ، i)))! = null) {if (ec <0) helprelease (c ، ws ، w ، q ، b) ؛ آخر if (q.base == b && u.compareandswapObject (a ، i ، t ، null)) {U.PutorderedInt (q ، qbase ، b + 1) ؛ if ((B + 1) - Q.Top <0) SignalWork (WS ، Q) ؛ W.Runtask (t) ؛ } } استراحة؛ } آخر إذا (-j <0) {if ((ec | (e = (int) c)) <0) // عودة غير نشطة أو إنهاء في انتظار (w ، c ، eC) ؛ آخر إذا (ctl == c) {// حاول تعطيل و enqueue long nc = (long) eC | ((C - AC_UNIT) و (AC_MASK | TC_MASK)) ؛ W.NextWait = e ؛ W.EventCount = EC | int_sign ؛ if (! u.compareanswaplong (this ، ctl ، c ، nc)) w.eventCount = eC ؛ // Back Out} Break ؛ }}} return 0 ؛ } لنلقي نظرة على طريقة المسح. معلمة واحدة من المسح هو workqueue. كما ذكر أعلاه ، سيكون لكل مؤشر ترابط عمل ، وسيتم حفظ عمل الخيوط المتعددة في Workqueues. R هو رقم عشوائي. استخدم R للعثور على عمل واملك مهام يتعين القيام بها في WorkQueue.
ثم ، من خلال قاعدة العمل ، احصل على الإزاحة الأساسية.
ب = Q.Base
..
long i = (((A.Length - 1) & b) << ashift) + abase ؛
..
ثم احصل على المهمة الأخيرة من خلال الإزاحة وتشغيل هذه المهمة
t = ((forkjointask <؟>) U.GetObjectVolatile (a ، i))
..
W.Runtask (t) ؛
..
من خلال هذا التحليل الخشن ، وجدنا أنه بعد استدعاء الخيط الحالي طريقة المسح ، لن ينفذ المهام في العمل الحالي ، ولكن سيحصل على مهام عمل أخرى من خلال رقم عشوائي r. هذه هي واحدة من الآليات الرئيسية لـ Forkjoinpool.
لن يركز الخيط الحالي على مهامه الخاصة فحسب ، بل سيعطي الأولوية للمهام الأخرى. هذا يمنع الجوع من الحدوث. هذا يمنع بعض المواضيع من عدم قدرتها على إكمال المهام في الوقت المناسب لأسباب عالقة أو أسباب أخرى ، أو يحتوي مؤشر ترابط على كمية كبيرة من المهام ، لكن الخيوط الأخرى ليس لها ما تفعله.
ثم دعونا نلقي نظرة على طريقة Runtask
runtask void النهائي (ForkJointask <؟> Task) {if ((CurrentSteal = Task)! = null) {forkjoinworkerthread thread ؛ Task.Doexec () ؛ forkjointask <؟> [] a = array ؛ int md = mode ؛ ++ nsteals ؛ CurrentSteal = فارغة ؛ إذا (md! = 0) pollandexecall () ؛ آخر if (a! = null) {int s ، m = a.length - 1 ؛ forkjointask <؟> t ؛ بينما ((s = top - 1) - base> = 0 && (t = (forkjointask <؟>) U.GetAndSetObject (a ، (m & s) << ashift) + abase ، null))! = null) {top = s ؛ T.Doexec () ؛ }} if ((thread = elebly)! = null) // لا حاجة للقيام به في thream thread.aftertoplexec () ؛ }}هناك اسم مثير للاهتمام: CurrentSteal ، المهمة المسروقة هي بالفعل ما شرحته للتو.
Task.Doexec () ؛
سيتم الانتهاء من هذه المهمة.
بعد الانتهاء من مهام الآخرين ، سوف تكمل مهامك الخاصة.
احصل على المهمة الأولى من خلال الحصول على القمة
بينما ((s = top - 1) - base> = 0 && (t = (forkjointask <؟>) U.GetAndSetObject (a ، (m & s) << ashift) + abase ، null))! = null) {top = s ؛ T.Doexec () ؛}بعد ذلك ، استخدم رسمًا بيانيًا لتلخيص عملية تجمع الخيوط الآن.
على سبيل المثال ، هناك خيطان T1 و T2. ستحصل T1 على المهمة الأخيرة لـ T2 من خلال قاعدة T2 (بالطبع ، إنها في الواقع مهمة أخرى من مؤشر ترابط من خلال رقم عشوائي R) ، وسوف يقوم T1 أيضًا بمهمته الأولى من خلال الجزء العلوي الخاص به. على العكس من ذلك ، سيفعل T2 نفس الشيء.
تبدأ المهام التي تأخذها لخيوط أخرى من القاعدة ، والمهام التي تأخذها لنفسك تبدأ من الأعلى. هذا يقلل من الصراع
إذا لم يتم العثور على مهام أخرى
آخر إذا (-j <0) {if ((ec | (e = (int) c)) <0) // عودة غير نشطة أو تنتهي (w ، c ، eC) ؛ آخر إذا (ctl == c) {// حاول تعطيل و enqueue long nc = (long) eC | ((C - AC_UNIT) و (AC_MASK | TC_MASK)) ؛ W.NextWait = e ؛ W.EventCount = EC | int_sign ؛ if (! u.compareanswaplong (this ، ctl ، c ، nc)) w.eventCount = eC ؛ // Back Out} Break ؛ } ثم أولاً ، سيتم تغيير قيمة CTL من خلال سلسلة من عمليات التشغيل ، وسيتم الحصول على NC ، ثم سيتم تعيين القيمة الجديدة باستخدام CAS. ثم اتصل على Awaitwork () للدخول إلى حالة الانتظار (تسمى طريقة Park's Park المذكورة في السلسلة السابقة من المقالات).
ما نحتاج إلى شرحه هنا هو تغيير قيمة CTL. هنا ، أولاً ، يحتل AC -1 في CTL ، و AC أفضل 16 بت من CTL ، لذلك لا يمكن أن يكون مباشرة -1 ، ولكن بدلاً من ذلك يحقق تأثير صنع أفضل 16 بت من CTL -1 من خلال AC_UNIT (0x1000000000000) من أول 16 بت من CTL.
كما ذكرنا سابقًا ، يحفظ EventCount poolindex ، ومن خلال poolindex و nextwait في Workqueue ، يمكنك اجتياز جميع خيوط الانتظار.