CountDownLatch ใช้ใน Java Multithreading อย่างไร


183

มีใครช่วยให้ฉันเข้าใจว่า Java CountDownLatchคืออะไรและใช้เมื่อใด?

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

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {
    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        System.out.println("Started.");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        latch.countDown();
    }
}

// ------------------------------------------------ -----

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0

        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool

        for(int i=0; i < 3; i++) {
            executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
        }

        try {
            latch.await();  // wait until latch counted down to 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Completed.");
    }

}


8
ฉันเพิ่งใช้รหัสตัวอย่างคำถามของคุณสำหรับชุดบริการแบบขนานของ Android และมันใช้งานได้อย่างมีเสน่ห์ ขอบคุณมาก!
Roisgoen

มาที่นี่จากวิดีโอนี้จากปี 2012 ซึ่งแสดงความคล้ายคลึงกับตัวอย่างที่แสดงที่นี่ สำหรับทุกคนที่สนใจนี่เป็นส่วนหนึ่งของชุดการสอนหลายเธรด Java จากผู้ชายที่ชื่อ John ฉันชอบจอห์น แนะนำเป็นอย่างยิ่ง
Elia Grady

คำตอบ:


194

ใช่คุณเข้าใจถูกต้อง CountDownLatchทำงานในหลักการสลัก, เธรดหลักจะรอจนกว่าประตูจะเปิด หนึ่งรอด้ายสำหรับnCountDownLatchหัวข้อที่ระบุไว้ในขณะที่การสร้าง

เธรดใด ๆ ซึ่งมักจะเป็นเธรดหลักของแอปพลิเคชันซึ่งการโทรCountDownLatch.await()จะรอจนกว่าการนับจะถึงศูนย์หรือถูกขัดจังหวะโดยเธรดอื่น หัวข้ออื่น ๆ ทั้งหมดจะต้องนับถอยหลังด้วยการโทรCountDownLatch.countDown()เมื่อเสร็จสมบูรณ์หรือพร้อม

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

แก้ไข:

ใช้ CountDownLatchเมื่อหนึ่งเธรด (เช่นเธรดหลัก) ต้องรอเธรดหนึ่งเธรดขึ้นไปให้เสร็จสมบูรณ์ก่อนที่จะสามารถดำเนินการต่อได้

ตัวอย่างคลาสสิกของการใช้CountDownLatchใน Java เป็นแอปพลิเคชัน Java ฝั่งเซิร์ฟเวอร์หลักซึ่งใช้สถาปัตยกรรมบริการโดยมีหลายบริการที่มีให้หลายเธรดและแอปพลิเคชันไม่สามารถเริ่มการประมวลผลได้จนกว่าบริการทั้งหมดจะเริ่มต้นได้สำเร็จ

คำถามของ PS OP มีตัวอย่างที่ค่อนข้างตรงไปตรงมาดังนั้นฉันจึงไม่ได้รวมไว้


1
ขอบคุณที่ตอบ. คุณสามารถยกตัวอย่างให้กับฉันที่จะใช้สลักแบบนับถอยหลังได้หรือไม่
amal

11
บทช่วยสอนวิธีใช้ CountDownLatch อยู่ที่นี่howtodoinjava.com/2013/07/18/ …
thiagoh

1
@NikolaB แต่ในตัวอย่างนี้เราสามารถบรรลุผลลัพธ์เดียวกันโดยใช้วิธีการเข้าร่วมใช่มั้ย
Vikas Verma

3
ฉันจะพิจารณาถึงความได้เปรียบที่ไม่สามารถนำกลับมาใช้ใหม่: คุณแน่ใจว่าไม่มีใครสามารถรีเซ็ตได้หรือเพิ่มจำนวน
ataulm

3
คำอธิบายที่ดี แต่ฉันก็ไม่เห็นด้วยกับประเด็นOne thread waits for n number of threads specified while creating CountDownLatch in Javaนี้เล็กน้อย CyclicBarrierหากคุณต้องการกลไกดังกล่าวแล้วจะระมัดระวังกับการใช้งาน ความแตกต่างทางแนวคิดพื้นฐานระหว่างสองสิ่งนี้ดังที่ได้กล่าวไว้Java concurrency in Practiceคือ: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()จะเข้าสู่สถานะการปิดกั้น
Rahul Dev Mishra

43

CountDownLatchใน Java เป็นประเภทของซิงโครไนซ์ที่อนุญาตให้Thread รออย่างน้อยหนึ่งThreads ก่อนที่จะเริ่มการประมวลผล

CountDownLatchทำงานได้บนหลักการสลักเกลียวจะรอจนกว่าประตูจะเปิด หนึ่งรอด้ายสำหรับจำนวนเธรดระบุขณะที่การสร้างnCountDownLatch

เช่น final CountDownLatch latch = new CountDownLatch(3);

ที่นี่เราตั้งค่าตัวนับเป็น 3

หัวข้อใดหัวข้อมักจะหลักของโปรแกรมซึ่งสายจะรอจนกว่าถึงนับเป็นศูนย์หรือมันขัดจังหวะอีกCountDownLatch.await() Threadหัวข้ออื่น ๆ ทั้งหมดจะต้องนับถอยหลังด้วยการโทรCountDownLatch.countDown()เมื่อเสร็จสมบูรณ์หรือพร้อมที่จะทำงาน ทันทีที่จำนวนถึงศูนย์การThreadรอที่จะเริ่มต้นทำงาน

นี่คือการนับจะลดลงโดยCountDownLatch.countDown()วิธีการ

Threadซึ่งเรียกว่าawait()วิธีการจะรอจนกว่าการนับเริ่มต้นถึงศูนย์

ในการทำให้จำนวนศูนย์เธรดอื่น ๆ ต้องเรียกใช้ countDown()เมธอด เมื่อการนับกลายเป็นศูนย์เธรดที่เรียกใช้await()เมธอดจะทำงานต่อ (เริ่มการประมวลผล)

ข้อเสียของCountDownLatchคือไม่สามารถใช้ซ้ำได้: เมื่อจำนวนกลายเป็นศูนย์จะไม่สามารถใช้งานได้อีกต่อไป


เราจะใช้ในnew CountDownLatch(3)ขณะที่เรามี 3 กระทู้จากที่newFixedThreadPool กำหนดไว้?
Chaklader Asfak Arefe

ไม่ควร "ก่อนที่จะเริ่มการประมวลผล" เป็น "ก่อนที่จะดำเนินการต่อ"
Maria Ines Parnisari

@Arefe ใช่มันเป็นจำนวนกระทู้ผ่านบล็อกของคุณรหัส
Vishal Akkalkote

23

NikolaB อธิบายได้ดีมากอย่างไรก็ตามตัวอย่างจะมีประโยชน์ในการเข้าใจดังนั้นนี่คือตัวอย่างง่ายๆ ...

 import java.util.concurrent.*;


  public class CountDownLatchExample {

  public static class ProcessThread implements Runnable {

    CountDownLatch latch;
    long workDuration;
    String name;

    public ProcessThread(String name, CountDownLatch latch, long duration){
        this.name= name;
        this.latch = latch;
        this.workDuration = duration;
    }


    public void run() {
        try {
            System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
            Thread.sleep(workDuration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+ "completed its works");
        //when task finished.. count down the latch count...

        // basically this is same as calling lock object notify(), and object here is latch
        latch.countDown();
    }
}


public static void main(String[] args) {
    // Parent thread creating a latch object
    CountDownLatch latch = new CountDownLatch(3);

    new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
    new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
    new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs


    System.out.println("waiting for Children processes to complete....");
    try {
        //current thread will get notified if all chidren's are done 
        // and thread will resume from wait() mode.
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("All Process Completed....");

    System.out.println("Parent Thread Resuming work....");



     }
  }

22

มันถูกใช้เมื่อเราต้องการรอมากกว่าหนึ่งเธรดเพื่อให้งานเสร็จสมบูรณ์ มันคล้ายกับการเข้าร่วมในหัวข้อ

ที่ที่เราสามารถใช้ CountDownLatch

พิจารณาสถานการณ์ที่เรามีความต้องการที่เรามีสามกระทู้ "A", "B" และ "C" และเราต้องการที่จะเริ่มหัวข้อ "C" เฉพาะเมื่อหัวข้อ "A" และ "B" เสร็จสมบูรณ์หรือบางส่วนทำให้งานของพวกเขาสำเร็จ

มันสามารถนำไปใช้กับสถานการณ์ไอทีในโลกแห่งความเป็นจริง

พิจารณาสถานการณ์ที่ผู้จัดการแบ่งโมดูลระหว่างทีมพัฒนา (A และ B) และเขาต้องการมอบหมายให้ทีม QA ทำการทดสอบเฉพาะเมื่อทั้งสองทีมทำงานให้เสร็จ

public class Manager {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
        MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
        teamDevA.start();
        teamDevB.start();
        countDownLatch.await();
        MyQATeam qa = new MyQATeam();
        qa.start();
    }   
}

class MyDevTeam extends Thread {   
    CountDownLatch countDownLatch;
    public MyDevTeam (CountDownLatch countDownLatch, String name) {
        super(name);
        this.countDownLatch = countDownLatch;       
    }   
    @Override
    public void run() {
        System.out.println("Task assigned to development team " + Thread.currentThread().getName());
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
                ex.printStackTrace();
        }
    System.out.println("Task finished by development team Thread.currentThread().getName());
            this.countDownLatch.countDown();
    }
}

class MyQATeam extends Thread {   
    @Override
    public void run() {
        System.out.println("Task assigned to QA team");
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("Task finished by QA team");
    }
}

ผลลัพธ์ของโค้ดด้านบนจะเป็น:

มอบหมายงานให้กับทีมพัฒนา devB

มอบหมายงานให้กับทีมพัฒนา devA

งานเสร็จโดยทีมพัฒนา devB

งานเสร็จโดยทีมพัฒนา devA

มอบหมายงานให้กับทีมงาน QA

งานเสร็จโดยทีมงาน QA

ที่นี่await ()วิธีการรอให้ countdownlatch ตั้งค่าเป็น 0 และcountDown ()วิธีการลดการตั้งค่าสถานะนับถอยหลังโดย 1

ข้อ จำกัด ของการเข้าร่วม: ตัวอย่างข้างต้นสามารถทำได้ด้วยการเข้าร่วม แต่การเข้าร่วมไม่สามารถใช้ในสองสถานการณ์:

  1. เมื่อเราใช้ ExecutorService แทน Class Thread เพื่อสร้างเธรด
  2. แก้ไขตัวอย่างข้างต้นที่ผู้จัดการต้องการส่งมอบรหัสให้กับทีมงาน QA ทันทีที่การพัฒนาดำเนินงานได้สำเร็จ 80% หมายความว่า CountDownLatch ช่วยให้เราสามารถปรับใช้งานซึ่งสามารถใช้เพื่อรอเธรดอื่นสำหรับการดำเนินการบางส่วน

3

CoundDownLatch ช่วยให้คุณสามารถรอเธรดจนกว่าเธรดอื่นทั้งหมดจะเสร็จสิ้นด้วยการดำเนินการของพวกเขา

โค้ดหลอกสามารถ:

// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution

คุณอาจต้องการย้ายคำอธิบายทั้งหมดของคุณออกจากบล็อครหัส
Paul Lo

ความคิดเห็นที่ดีที่สุดแม้ว่า ฉันชอบความคิดเห็น "ตรงประเด็น" เหล่านี้แทนคำอธิบายเชิงทฤษฎี
renatoaraujoc

2

ตัวอย่างที่ดีอย่างหนึ่งของการใช้สิ่งนี้คือเมื่อใช้ Java Simple Serial Connector เข้าถึงพอร์ตอนุกรม โดยทั่วไปแล้วคุณจะเขียนอะไรบางอย่างไปยังพอร์ตและในแบบอื่น ๆ บนอุปกรณ์อื่นจะตอบสนองใน SerialPortEventListener โดยทั่วไปคุณจะต้องหยุดหลังจากเขียนไปยังพอร์ตเพื่อรอการตอบกลับ การจัดการล็อคเธรดสำหรับสถานการณ์นี้ด้วยตนเองนั้นมีความยุ่งยากอย่างยิ่ง แต่การใช้ Countdownlatch นั้นง่ายมาก ก่อนที่คุณจะไปคิดว่าคุณสามารถทำได้อีกทางหนึ่งให้ระมัดระวังเกี่ยวกับสภาพการแข่งขันที่คุณไม่เคยคิด !!

pseudocode:

CountDownLatch latch;
void writeData() { 
   latch = new CountDownLatch(1);
   serialPort.writeBytes(sb.toString().getBytes())
   try {
      latch.await(4, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
   }
}
class SerialPortReader implements SerialPortEventListener {
    public void serialEvent(SerialPortEvent event) {
        if(event.isRXCHAR()){//If data is available
            byte buffer[] = serialPort.readBytes(event.getEventValue());
            latch.countDown();
         }
     }
}


2

หากคุณเพิ่มการแก้ไขข้อบกพร่องบางอย่างหลังจากการโทรไปยัง latch.countDown () สิ่งนี้อาจช่วยให้คุณเข้าใจพฤติกรรมของมันได้ดีขึ้น

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

ผลลัพธ์จะแสดงจำนวนกำลังลดลง 'count' นี้มีประสิทธิภาพจำนวนงาน Runnable (วัตถุตัวประมวลผล) ที่คุณเริ่มต้นซึ่ง countDown () ไม่ได้ถูกเรียกใช้และด้วยเหตุนี้จึงถูกบล็อกเธรดหลักในการเรียกไปยัง latch.await ()

DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]

2

จากเอกสาร Oracle เกี่ยวกับCountDownLatch :

ตัวช่วยการซิงโครไนซ์ที่อนุญาตให้เธรดตั้งแต่หนึ่งเธรดขึ้นไปรอจนกว่าชุดการดำเนินการที่ดำเนินการในเธรดอื่นจะเสร็จสิ้น

CountDownLatchจะเริ่มต้นด้วยการนับจำนวนที่กำหนด awaitวิธีการป้องกันจนกระทั่งถึงปัจจุบันนับเป็นศูนย์เนื่องจากการสวดของcountDown()วิธีการหลังจากที่ทุกกระทู้รอมีการเปิดตัวและสวด ๆ ที่ตามมาของการกลับมารอคอยทันที นี่เป็นปรากฏการณ์ครั้งเดียว - การนับไม่สามารถรีเซ็ตได้

CountDownLatch เป็นเครื่องมือการซิงโครไนซ์อเนกประสงค์และสามารถใช้สำหรับวัตถุประสงค์หลายประการ

การCountDownLatchเริ่มต้นด้วยการนับหนึ่งทำหน้าที่เป็นสลักเปิด / ปิดง่ายหรือเกต: กระทู้ทั้งหมดที่รอคอยรอที่เกตจนกระทั่งมันถูกเปิดโดยเธรดที่เรียกใช้ countDown ()

การกำหนดCountDownLatchค่าเริ่มต้นเป็น N สามารถใช้เพื่อทำให้เธรดหนึ่งเธรดรอจนกระทั่งเธรด N เสร็จสิ้นการดำเนินการบางอย่างหรือบางการดำเนินการเสร็จสิ้นแล้ว N ครั้ง

public void await()
           throws InterruptedException

ทำให้เธรดปัจจุบันรอจนกระทั่งสลักถูกนับเป็นศูนย์ยกเว้นว่าเธรดถูกขัดจังหวะ

หากการนับปัจจุบันเป็นศูนย์แล้ววิธีนี้จะส่งกลับทันที

public void countDown()

ลดจำนวนของสลักลงและปล่อยเธรดที่รออยู่ทั้งหมดหากจำนวนถึงศูนย์

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

คำอธิบายตัวอย่างของคุณ

  1. คุณตั้งให้นับเป็น 3 สำหรับlatchตัวแปร

    CountDownLatch latch = new CountDownLatch(3);
  2. คุณได้แชร์สิ่งนี้latchกับเธรดผู้ทำงาน:Processor

  3. Runnableอินสแตนซ์ของสามรายการProcessorถูกส่งไปยังExecutorService executor
  4. เธรดหลัก ( App) กำลังรอการนับเป็นศูนย์โดยมีคำสั่งด้านล่าง

     latch.await();  
  5. Processor thread หลับเป็นเวลา 3 วินาทีจากนั้นจะลดค่านับด้วย latch.countDown()
  6. ครั้งแรกProcessเช่นจะมีการเปลี่ยนแปลงนับสลักเป็น 2 latch.countDown()หลังจากที่เสร็จสิ้นเนื่องจาก

  7. ประการที่สองProcessเช่นจะมีการเปลี่ยนแปลงนับสลักเป็น 1 latch.countDown()หลังจากที่เสร็จสิ้นเนื่องจาก

  8. ประการที่สามProcessเช่นจะมีการเปลี่ยนแปลงนับสลักเป็น 0 latch.countDown()หลังจากที่เสร็จสิ้นเนื่องจาก

  9. การนับเป็นศูนย์บนสลักทำให้เธรดหลักAppออกมาawait

  10. โปรแกรมแอปพิมพ์ผลลัพธ์นี้ทันที: Completed


2

ตัวอย่างจากJava Docช่วยให้ฉันเข้าใจแนวคิดอย่างชัดเจน:

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

การตีความภาพ:

ป้อนคำอธิบายรูปภาพที่นี่

เห็นได้ชัดว่าCountDownLatchอนุญาตให้หนึ่งเธรด (ที่นี่Driver) รอจนกว่าจะมีเธรดที่รันอยู่ (ที่นี่Worker) เสร็จสิ้นด้วยการประมวลผล


1

ดังที่กล่าวไว้ใน JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) CountDownLatch เป็นตัวช่วยในการซิงโครไนซ์ที่นำมาใช้ใน Java 5 ที่นี่การประสานไม่ได้ หมายถึงการ จำกัด การเข้าถึงส่วนที่สำคัญ แต่แทนที่จะจัดลำดับการกระทำของเธรดที่แตกต่างกัน ประเภทของการซิงโครไนซ์ที่ทำได้ด้วย CountDownLatch นั้นคล้ายกับของ Join สมมติว่ามีเธรด "M" ซึ่งต้องรอเธรดอื่นของผู้ทำงาน "T1", "T2", "T3" เพื่อทำงานให้เสร็จสิ้นก่อน Java 1.5 วิธีที่สามารถทำได้คือ M รันโค้ดต่อไปนี้

    T1.join();
    T2.join();
    T3.join();

โค้ดด้านบนทำให้แน่ใจว่า thread M จะทำงานต่อหลังจาก T1, T2, T3 ทำงานเสร็จสมบูรณ์ T1, T2, T3 สามารถทำงานให้เสร็จสมบูรณ์ตามลำดับ สามารถทำได้เช่นเดียวกันผ่าน CountDownLatch โดยที่ T1, T2, T3 และเธรด M แชร์วัตถุ CountDownLatch เดียวกัน
คำขอ "M": countDownLatch.await();
ตำแหน่งที่ "T1", "T2", "T3" ทำ countDownLatch.countdown();

ข้อเสียอย่างหนึ่งของวิธีการเข้าร่วมคือ M ต้องรู้เกี่ยวกับ T1, T2, T3 หากมีเธรดของผู้ปฏิบัติงานใหม่ที่เพิ่มในภายหลัง T4 ดังนั้น M ต้องระวังเช่นกัน สิ่งนี้สามารถหลีกเลี่ยงได้ด้วย CountDownLatch หลังจากดำเนินการตามลำดับของการดำเนินการจะเป็น [T1, T2, T3] (ลำดับของ T1, T2, T3 อาจเป็นอย่างไรก็ตาม) -> [M]



0
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.