ในชีวิตจริงเรามักจะพบกับสถานการณ์เช่นนี้ ก่อนที่เราจะทำกิจกรรมเราต้องรอจนกว่าเราจะมีทุกคนก่อนที่เราจะเริ่ม ตัวอย่างเช่นเมื่อรับประทานอาหารคุณต้องรอจนกว่าทั้งครอบครัวจะอยู่บนที่นั่งก่อนที่คุณจะเดินหน้าต่อไปเมื่อเดินทางคุณต้องรอจนกว่าทุกคนจะมาที่นี่ก่อนที่คุณจะออกเดินทางและเมื่อนักกีฬาอยู่ในสนามคุณต้องรอจนกว่านักกีฬาจะอยู่ในศาลก่อนที่จะเริ่ม แพ็คเกจ JUC ช่วยให้เรามีคลาสเครื่องมือการซิงโครไนซ์ที่สามารถจำลองสถานการณ์ประเภทนี้ได้เป็นอย่างดีซึ่งเป็นคลาส Cyclicbarrier การใช้คลาส Cyclicbarrier กลุ่มของเธรดสามารถนำไปใช้เพื่อรอซึ่งกันและกันแล้วดำเนินการต่อไปเมื่อเธรดทั้งหมดมาถึงจุดกีดขวางที่แน่นอน รูปต่อไปนี้แสดงกระบวนการนี้
มีเคาน์เตอร์ภายในคลาส Cyclicbarrier แต่ละเธรดจะเรียกวิธีการรอคอยเพื่อบล็อกตัวเองเมื่อถึงจุดกีดขวาง ในเวลานี้ตัวนับจะลดลง 1 เมื่อตัวนับลดลงเป็น 0 เธรดทั้งหมดที่ถูกบล็อกโดยการเรียกวิธีการรอคอยจะถูกปลุก นี่คือหลักการของการใช้ชุดเธรดที่รอซึ่งกันและกัน ก่อนอื่นมาดูว่าตัวแปรสมาชิก Cyclicbarrier มีอะไรบ้าง
// การดำเนินการแบบซิงโครนัสล็อคส่วนตัวครั้งสุดท้าย reentrantlock ล็อค = ใหม่ reentrantlock (); // เธรด interceptor private condition trip = lock.newCondition (); // จำนวนของเธรดที่สกัดกั้นทุกครั้งที่เข้าร่วมการผลิตครั้งสุดท้าย GenerationPrivate Class Static Static {boolean broken = false;}ตัวแปรสมาชิกทั้งหมดของ CyclicBarrier ถูกโพสต์ด้านบน คุณจะเห็นว่า Cyclicbarrier ภายในบล็อกเธรดผ่านการเดินทางคิวแบบมีเงื่อนไขและจะรักษาสองตัวแปร int-type และนับ ภาคีแสดงจำนวนเธรดที่สกัดกั้นในแต่ละครั้งและค่านี้ถูกกำหนดในระหว่างการก่อสร้าง Count เป็นตัวนับภายในค่าเริ่มต้นของมันจะเหมือนกับฝ่ายต่างๆและลดลง 1 ครั้งด้วยการโทรแต่ละครั้งของวิธีการรอคอยจนกว่าจะลดลงเหลือ 0 และตื่นขึ้นมาทั้งหมด Cyclicbarrier มีการสร้างชั้นเรียนภายในแบบคงที่และวัตถุของคลาสนี้เป็นตัวแทนของรุ่นปัจจุบันของรั้วเช่นเดียวกับเกมที่แสดงโดยเกมเมื่อเล่นเกมโดยใช้มันเพื่อตระหนักถึงการรอลูป BarrierCommand ระบุงานที่ดำเนินการก่อนการเปลี่ยน เมื่อการนับลดลงเป็น 0 นั่นหมายความว่าเกมจะจบลงแล้วและจำเป็นต้องถ่ายโอนไปยังเกมถัดไป กระทู้ที่ปิดกั้นทั้งหมดจะถูกปลุกให้ตื่นก่อนที่จะเข้าสู่เกมถัดไป ก่อนที่จะตื่นขึ้นมาทั้งหมดคุณสามารถทำงานของคุณเองได้โดยการระบุ barriercommand ต่อไปเราจะดูตัวสร้าง
// constructor 1Public CyclicBarrier (Int Parties, Runnable Barrieraction) {ถ้า (ฝ่าย <= 0) โยน unlegalargumentException ใหม่ (); this.parties = ฝ่าย; this.count = ฝ่าย; this.barriercommand = barrieraction;} // constructor 2public cyclicbarrier (ฝ่าย int) {สิ่งนี้ (ภาคี, null);}Cyclicbarrier มีสองตัวสร้างโดยที่ Constructor 1 เป็นตัวสร้างหลัก ที่นี่คุณสามารถระบุจำนวนผู้เข้าร่วมในเกมนี้ (จำนวนเธรดที่จะสกัดกั้น) และงานที่จะดำเนินการในตอนท้ายของเกมนี้ นอกจากนี้คุณยังสามารถเห็นได้ว่าค่าเริ่มต้นของการนับนับถูกตั้งค่าเป็นคู่สัญญา ฟังก์ชั่นหลักของคลาส Cyclicbarrier คือการบล็อกเธรดที่มาถึงจุดกีดขวางก่อนและรอเธรดที่ตามมา มันมีสองวิธีในการรอคือการรอคอยที่กำหนดเวลาและการรอที่ไม่ได้กำหนดเวลา
// ไม่รอการรอคอยสาธารณะ int รอ () พ่น InterruptedException, BrokenBarrieRexception {ลอง {return dowait (false, 0l); } catch (timeoutexception toe) {โยนข้อผิดพลาดใหม่ (toe); }} // หมดเวลารออยู่ในที่สาธารณะรออยู่ (การหมดเวลานาน, หน่วยเวลา) พ่น InterruptedException, BrokenBarrieRexception, TimeoutException {return dowait (true, unit.tonanos (หมดเวลา));}จะเห็นได้ว่าไม่ว่าจะเป็นการรอกำหนดเวลาหรือไม่ได้กำหนดเวลาพวกเขาเรียกวิธี Dowait แต่พารามิเตอร์ที่ส่งผ่านจะแตกต่างกัน ลองมาดูกันว่าวิธี Dowait ทำอะไร
// วิธีการรอหลักของ Private Int Dowait (เวลาบูลีน, นาโนยาว) พ่น InterruptedException, BrokenBarrieRexception, TimeoutException {Final ReentrantLock Lock = this.lock; lock.lock (); ลอง {รุ่นสุดท้าย G = Generation; // ตรวจสอบว่ารั้วปัจจุบันถูกกระแทกถ้า (g.broken) {โยน brokenbarrierexception ใหม่ (); } // ตรวจสอบว่าเธรดปัจจุบันถูกขัดจังหวะถ้า (thread.interrupted ()) {// ถ้าเธรดปัจจุบันถูกขัดจังหวะสามสิ่งต่อไปนี้จะเสร็จสิ้น // 1 ระเบิดรั้วปัจจุบัน // 2 ปลุกเธรดที่สกัดกั้นทั้งหมด // 3 โยนข้อยกเว้นขัดจังหวะ breakbarrier (); โยน InterruptedException ใหม่ (); } // ทุกครั้งลดค่าตัวนับด้วย 1 int index = -count; // ลดค่าตัวนับเป็น 0 เธรดทั้งหมดต้องตื่นขึ้นมาและแปลงเป็นรุ่นต่อไปถ้า (ดัชนี == 0) {บูลีน ranaction = false; ลอง {// ดำเนินการงานที่ระบุก่อนที่จะตื่นเธรดทั้งหมดคำสั่ง runnable สุดท้าย runnable = barrierCommand; if (command! = null) {command.run (); } ranaction = true; // ปลุกเธรดทั้งหมดและไปที่รุ่นต่อไป NextGeneration (); กลับ 0; } ในที่สุด {// ตรวจสอบให้แน่ใจว่าสามารถปลุกเธรดทั้งหมดได้หาก (! ranaction) {breakbarrier (); }}} // ถ้าตัวนับไม่ได้เป็น 0 ให้ดำเนินการลูปนี้สำหรับ (;;) {ลอง {// ป้องกันเพื่อตรวจสอบว่าจะรอเวลาหรือไม่หมดเวลาถ้า (หมดเวลา) {trip.await (); } อื่นถ้า (nanos> 0l) {nanos = trip.awaitnanos (nanos); }} catch (interruptedException IE) {// ถ้าเธรดปัจจุบันถูกขัดจังหวะระหว่างการรอให้ล้มรั้วเพื่อปลุกเธรดอื่น ๆ ถ้า (g == Generation &&! G.Broken) {Breakbarrier (); โยนคือ; } else {// หากการรอคอยบนรั้วเสร็จสมบูรณ์ก่อนที่จะจับข้อยกเว้นการขัดจังหวะการดำเนินการขัดจังหวะจะเรียกว่า tread.currentthread โดยตรง () interrupt (); }} // หากเธรดถูกปลุกขึ้นมาเนื่องจากการดำเนินการพลิกคว่ำข้อยกเว้นจะถูกโยนลงถ้า (g.broken) {โยน brokenbarrierexception ใหม่ (); } // ถ้าเธรดถูกปลุกเนื่องจากการดำเนินการทดแทนจะส่งคืนค่าของตัวนับถ้า (g! = generation) {return index; } // ถ้าเธรดถูกปลุกเนื่องจากเวลามันจะเคาะรั้วและโยนข้อยกเว้นถ้า (หมดเวลา && nanos <= 0l) {breakbarrier (); โยน TimeoutException ใหม่ (); }}} ในที่สุด {lock.unlock (); -ความคิดเห็นในรหัสที่โพสต์ด้านบนมีรายละเอียดค่อนข้างมากดังนั้นเราจะเลือกเพียงบางอย่างที่สำคัญ คุณจะเห็นว่าการนับลดลง 1 ทุกครั้งในวิธี Dowait หลังจากการลดลงจะมีการพิจารณาทันทีเพื่อดูว่ามันเท่ากับ 0 หรือไม่ถ้าเท่ากับ 0 มันจะดำเนินการก่อนงานที่ระบุไว้ก่อน หลังจากดำเนินการแล้วมันจะเรียกวิธี NextGeneration เพื่อถ่ายโอนรั้วไปยังรุ่นต่อไป ในวิธีนี้เธรดทั้งหมดจะถูกปลุกค่าเคาน์เตอร์จะถูกรีเซ็ตเป็นคู่สัญญาและการสร้างรั้วจะถูกรีเซ็ต หลังจากดำเนินการวิธีการ NextGeneration หมายความว่าเกมจะเข้าสู่เกมถัดไป หากตัวนับไม่เท่ากับ 0 ในเวลานี้มันจะเข้าสู่การวนรอบ ตัดสินใจว่าจะโทรหาทริป Awaitnanos (นาโน) หรือการเดินทาง Await () ตามพารามิเตอร์ ทั้งสองวิธีนี้สอดคล้องกับเวลาและการรอคอยที่ไม่ได้กำหนดเวลา หากเธรดปัจจุบันถูกขัดจังหวะในระหว่างการรอคอยวิธี Breakbarrier จะถูกดำเนินการ วิธีนี้เรียกว่า Breaking the Fence ซึ่งหมายความว่าเกมถูกตัดออกไปครึ่งทางตั้งค่าสถานะที่แตกหักให้เป็นจริงและตื่นขึ้นมาทั้งหมด ในขณะเดียวกันก็หมายความว่าในระหว่างกระบวนการรอเธรดจะถูกขัดจังหวะและเกมทั้งหมดสิ้นสุดลงและเธรดที่ถูกบล็อกก่อนหน้านี้ทั้งหมดจะถูกปลุก หลังจากที่เธรดตื่นขึ้นมามันจะทำการตัดสินสามครั้งต่อไปนี้เพื่อดูว่ามันตื่นขึ้นมาหรือไม่เพราะเรียกวิธี Breakbarrier ถ้าเป็นเช่นนั้นมันจะโยนข้อยกเว้น; ดูว่ามันถูกปลุกให้ตื่นขึ้นโดยการดำเนินการทดแทนปกติหรือไม่ ถ้าเป็นเช่นนั้นมันจะส่งคืนค่าของตัวนับ; ดูว่ามันตื่นขึ้นมาเพราะหมดเวลาหรือไม่ ถ้าเป็นเช่นนั้นมันจะเรียก Breakbarrier เพื่อทำลายรั้วและโยนข้อยกเว้น ควรสังเกตที่นี่ว่าหากหนึ่งในเธรดออกเพราะพวกเขารอการหมดเวลาเกมทั้งหมดจะจบลงและเธรดอื่น ๆ จะถูกปลุก โพสต์ต่อไปนี้รหัสเฉพาะสำหรับวิธี NextGeneration และ Breakbarrier
// สลับรั้วเป็นโมฆะส่วนตัวรุ่นต่อไป NextGeneration () {// ปลุกเธรดทั้งหมดในการเดินทางคิวเงื่อนไข SIGNALALL (); // ตั้งค่าตัวนับเป็นจำนวนเธรดที่จำเป็นต้องมีการนับจำนวน = คู่สัญญา; // รีเซ็ตการสร้างรั้ว = รุ่นใหม่ ();} // พลิกรั้วปัจจุบันเป็นโมฆะส่วนตัว Breakbarrier () {// ตั้งสถานะรั้วปัจจุบันเพื่อคว่ำการสร้าง. broken = true; // ตั้งค่าตัวนับเป็นจำนวนเธรดที่จำเป็นต้องมีการนับจำนวน = คู่สัญญา; // ปลุกเธรดทั้งหมด trip.signalall ();}เราได้อธิบายหลักการของ cyclicbarrier ผ่านซอร์สโค้ดด้านบนโดยทั่วไป มาเรียนรู้เพิ่มเติมเกี่ยวกับการใช้งานผ่านตัวอย่างการแข่งม้า
คลาสม้าใช้งาน Runnable {counter int private static = 0; ID int สุดท้ายส่วนตัว = ตัวนับ ++; Int Strides ส่วนตัว = 0; Rand แบบสุ่มแบบคงที่ส่วนตัว = ใหม่สุ่ม (47); สิ่งกีดขวาง cyclicbarrier แบบคงที่ส่วนตัว; ม้าสาธารณะ (CyclicBarrier b) {barrier = b; } @Override โมฆะสาธารณะเรียกใช้ () {ลอง {ในขณะที่ (! thread.interrupted ()) {ซิงโครไนซ์ (นี่) {// การแข่งขันม้าวิ่งแบบสุ่มหลายขั้นตอนทุกครั้งที่พวกเขาวิ่งก้าว += rand.nextint (3); } barrier.await (); }} catch (exception e) {e.printstacktrace (); }} แทร็กสตริงสาธารณะ () {stringbuilder s = new StringBuilder (); สำหรับ (int i = 0; i <getStrides (); i ++) {s.append ("*"); } s.append (id); กลับ s.toString (); } public synchronized int getStrides () {return strides; } สตริงสาธารณะ toString () {return "horse" + id + ""; }} คลาสสาธารณะ Horserace ใช้งาน Runnable {Private Static Final Int Finish_line = 75; รายการคงที่ส่วนตัว <Horse> HORSES = New ArrayList <Horse> (); ExecutorService Exection ส่วนตัว = Executors.newCachedThreadPool (); @Override โมฆะสาธารณะเรียกใช้ () {StringBuilder s = new StringBuilder (); // พิมพ์ขอบเขตแทร็กสำหรับ (int i = 0; i <finish_line; i ++) {s.append ("="); } system.out.println (s); // พิมพ์แทร็กแข่งม้าสำหรับ (ม้า: ม้า) {system.out.println (horse.tracks ()); } // อภิปรายว่าจะจบลงสำหรับ (ม้า: ม้า) {ถ้า (horse.getStrides ()> = finish_line) {System.out.println (Horse + "Won!"); exec.shutdownnow (); กลับ; }} // พักเวลาที่กำหนดไปยังรอบถัดไปลอง {timeunit.milliseconds.sleep (200); } catch (interruptedException e) {system.out.println ("การนอนหลับของสิ่งกีดขวางที่ถูกขัดจังหวะ"); }} โมฆะคงที่สาธารณะหลัก (สตริง [] args) {cyclicbarrier barrier = new cyclicbarrier (7, New Horserace ()); สำหรับ (int i = 0; i <7; i ++) {ม้า = ม้าตัวใหม่ (สิ่งกีดขวาง); Horses.add (ม้า); exec.execute (ม้า); -โปรแกรมการแข่งม้านี้ส่วนใหญ่ใช้แทร็กปัจจุบันของการแข่งม้าแต่ละครั้งบนคอนโซลเพื่อให้ได้เอฟเฟกต์การแสดงผลแบบไดนามิก มีหลายรอบของการแข่งขันทั้งหมด การแข่งขันม้าแต่ละครั้งจะใช้เวลาสองสามขั้นตอนโดยการสุ่มแล้วโทรหาวิธีรอคอยเพื่อรอ เมื่อม้าทุกตัวเสร็จสิ้นหนึ่งรอบงานจะดำเนินการเพื่อพิมพ์แทร็กปัจจุบันของการแข่งขันม้าทั้งหมดไปยังคอนโซล ด้วยวิธีนี้วิถีของการแข่งขันม้าแต่ละครั้งยังคงเติบโตในแต่ละรอบ เมื่อวิถีของการแข่งขันม้าครั้งแรกเพิ่มขึ้นเป็นค่าที่กำหนดครั้งแรกการแข่งขันทั้งหมดจะสิ้นสุดลงและการแข่งขันม้าจะกลายเป็นผู้ชนะของการแข่งขันทั้งหมด! ผลการทำงานของโปรแกรมมีดังนี้:
ณ จุดนี้เราจะเปรียบเทียบ cyclicbarrier อย่างหลีกเลี่ยงไม่ได้กับ Countdownlatch ทั้งสองคลาสสามารถใช้ชุดเธรดที่รอก่อนถึงเงื่อนไขที่แน่นอน พวกเขามีเคาน์เตอร์ภายใน เมื่อค่าตัวนับลดลงอย่างต่อเนื่องเป็น 0 เธรดการบล็อกทั้งหมดจะถูกปลุก ความแตกต่างคือตัวนับของ cyclicbarrier ถูกควบคุมด้วยตัวเองในขณะที่ตัวนับของ Countdownlatch ถูกควบคุมโดยผู้ใช้ ใน Cyclicbarrier เธรดเรียกวิธีการรอคอยจะไม่เพียง แต่บล็อกตัวเอง แต่ยังลดตัวนับด้วย 1 ใน Countdownlatch เธรดเรียกวิธีการรอคอยจะบล็อกตัวเองโดยไม่ลดค่าของตัวนับ นอกจากนี้ Countdownlatch สามารถสกัดกั้นรอบหนึ่งในขณะที่ CyclicBarrier สามารถใช้การสกัดกั้นแบบวงกลมได้ โดยทั่วไปแล้วการใช้ CyclicBarrier สามารถใช้ฟังก์ชั่นของ Countdownlatch ได้ แต่ไม่เช่นนั้นก็ไม่สามารถทำได้ ตัวอย่างเช่นโปรแกรมการแข่งม้าด้านบนสามารถนำไปใช้ได้โดยใช้ CyclicBarrier เท่านั้น ในระยะสั้นความคล้ายคลึงและความแตกต่างระหว่างสองหมวดหมู่นี้จะเหมือนกัน สำหรับเวลาที่จะใช้ Cyclicbarrier และ Countdownlatch ผู้อ่านยังต้องเข้าใจด้วยตัวเอง
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น