Làm thế nào để dừng đúng luồng trong Java?


276

Tôi cần một giải pháp để dừng đúng luồng trong Java.

Tôi có IndexProcessorlớp thực hiện giao diện Runnable:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    @Override
    public void run() {
        boolean run = true;
        while (run) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                run = false;
            }
        }

    }
}

Và tôi có ServletContextListenerlớp bắt đầu và dừng chuỗi:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        thread = new Thread(new IndexProcessor());
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            thread.interrupt();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

Nhưng khi tôi tắt tomcat, tôi nhận được ngoại lệ trong lớp IndexProcessor của mình:

2012-06-09 17:04:50,671 [Thread-3] ERROR  IndexProcessor Exception
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lt.ccl.searchengine.processor.IndexProcessor.run(IndexProcessor.java:22)
    at java.lang.Thread.run(Unknown Source)

Tôi đang sử dụng JDK 1.6. Vì vậy, câu hỏi là:

Làm thế nào tôi có thể dừng chủ đề và không ném bất kỳ ngoại lệ?

PS Tôi không muốn sử dụng .stop();phương pháp vì nó không được dùng nữa.


1
Chấm dứt một nửa luồng sẽ luôn tạo ra một ngoại lệ. Nếu đó là hành vi bình thường, thì bạn chỉ có thể bắt và bỏ qua InterruptedException. Đây là những gì tôi nghĩ, nhưng tôi cũng tự hỏi làm thế nào tiêu chuẩn là.
nhahtdh

Tôi đã không sử dụng các chủ đề rất thường xuyên vì vậy tôi khá mới trong các chủ đề, vì vậy tôi không biết liệu đó có phải là hành vi bình thường để bỏ qua ngoại lệ. Đó là lý do tại sao tôi hỏi.
Paulius Matulionis

Trong nhiều trường hợp, đó là hành vi bình thường để bỏ qua ngoại lệ và chấm dứt xử lý phương thức. Xem câu trả lời của tôi dưới đây để biết lý do tại sao điều này tốt hơn một cách tiếp cận dựa trên cờ.
Matt

1
Một lời giải thích gọn gàng của B. Goetz liên quan đến InterruptedExceptioncó thể được tìm thấy tại ibm.com/developerworks/l Library / j-jtp05236 .
Daniel

Interrupttedception không phải là vấn đề, vấn đề duy nhất của bạn trong mã được đăng là bạn không nên đăng nhập nó là một lỗi, thực sự không có lý do thuyết phục nào để ghi lại tất cả ngoại trừ việc gỡ lỗi chỉ để chứng minh điều đó xảy ra trong trường hợp bạn quan tâm . câu trả lời được chọn là không may vì nó không cho phép cắt các cuộc gọi ngắn thành các cuộc gọi như ngủ và chờ.
Nathan Hughes

Câu trả lời:


173

Trong IndexProcessorlớp, bạn cần một cách để thiết lập một cờ thông báo cho luồng rằng nó sẽ cần chấm dứt, tương tự như biến runmà bạn đã sử dụng chỉ trong phạm vi lớp.

Khi bạn muốn dừng chuỗi, bạn đặt cờ này và gọi join()trên luồng và đợi cho nó kết thúc.

Đảm bảo rằng cờ là luồng an toàn bằng cách sử dụng biến dễ bay hơi hoặc bằng cách sử dụng các phương thức getter và setter được đồng bộ hóa với biến được sử dụng làm cờ.

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

Sau đó trong SearchEngineContextListener:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        runnable = new IndexProcessor();
        thread = new Thread(runnable);
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (thread != null) {
            runnable.terminate();
            thread.join();
            LOGGER.debug("Thread successfully stopped.");
        }
    }
}

3
Tôi đã làm chính xác như bạn đã đưa ra các ví dụ trong câu trả lời của bạn ngay trước khi tôi nhìn rằng bạn đã chỉnh sửa nó. Câu trả lời chính xác! Cảm ơn bạn, bây giờ mọi thứ hoạt động hoàn hảo :)
Paulius Matulionis

1
Điều gì xảy ra nếu logic luồng phức tạp và gọi rất nhiều phương thức của các lớp khác? Không thể kiểm tra cờ boolean ở mọi nơi. Làm gì sau đó?
Soteric

Bạn sẽ phải thay đổi thiết kế mã để bạn xây dựng nó theo cách mà tín hiệu đến Runnable sẽ khiến luồng thoát ra. Hầu hết các sử dụng đều có vòng lặp này trong phương thức chạy nên thường không có vấn đề gì.
DrYap

3
Điều gì xảy ra nếu câu lệnh jo () ném Interrupttedception?
benzaita

14
Downvote để lan truyền lời khuyên xấu. cách tiếp cận cờ cuộn bằng tay có nghĩa là ứng dụng phải chờ cho đến khi giấc ngủ kết thúc, trong đó sự gián đoạn sẽ cắt ngắn giấc ngủ. Sẽ dễ dàng sửa đổi điều này để sử dụng ngắt # luồng.
Nathan Hughes

298

Sử dụng Thread.interrupt()là một cách hoàn toàn chấp nhận được để làm điều này. Trên thực tế, nó có thể thích hơn một lá cờ như được đề xuất ở trên. Lý do là nếu bạn đang trong một cuộc gọi chặn gián đoạn (như Thread.sleephoặc sử dụng các hoạt động Kênh java.nio), bạn thực sự sẽ có thể thoát ra khỏi những cuộc gọi đó ngay lập tức.

Nếu bạn sử dụng cờ, bạn phải đợi thao tác chặn kết thúc và sau đó bạn có thể kiểm tra cờ của mình. Trong một số trường hợp, bạn phải làm điều này bằng mọi cách, chẳng hạn như sử dụng tiêu chuẩn InputStream/ OutputStreamkhông bị gián đoạn.

Trong trường hợp đó, khi một luồng bị gián đoạn, nó sẽ không làm gián đoạn IO, tuy nhiên, bạn có thể dễ dàng thực hiện việc này thường xuyên trong mã của mình (và bạn nên làm điều này tại các điểm chiến lược nơi bạn có thể dừng và dọn dẹp một cách an toàn)

if (Thread.currentThread().isInterrupted()) {
  // cleanup and stop execution
  // for example a break in a loop
}

Giống như tôi đã nói, lợi thế chính Thread.interrupt()là bạn có thể thoát ra khỏi các cuộc gọi bị gián đoạn, điều mà bạn không thể làm với cách tiếp cận cờ.


32
+1 - Thread.interupt () chắc chắn là tốt hơn để thực hiện điều tương tự bằng cách sử dụng cờ ad-hoc.
Stephen C

2
Tôi cũng nghĩ rằng đây là cách hoàn hảo và hiệu quả để làm như vậy. +1
RoboAlex

4
Có một lỗi đánh máy nhỏ trong mã, Thread.cienThread () không có dấu ngoặc đơn.
Vlad V

1
Trên thực tế, không nên sử dụng cờ vì người khác tiếp xúc với luồng có thể làm gián đoạn nó từ nơi khác, khiến nó dừng lại và rất khó gỡ lỗi. Luôn luôn sử dụng một lá cờ là tốt.
JohnyTex

Trong trường hợp cụ thể này, việc gọi interrupt()có thể ổn, nhưng trong nhiều trường hợp khác thì không được (ví dụ nếu tài nguyên cần phải đóng). Nếu bất cứ ai thay đổi hoạt động bên trong của vòng lặp, bạn sẽ phải nhớ thay đổi interrupt()theo cách boolean. Tôi sẽ đi theo cách an toàn ngay từ đầu và sử dụng cờ.
m0skit0

25

Câu trả lời đơn giản: Bạn có thể dừng một chủ đề THỰC TẾ theo một trong hai cách phổ biến:

  • Phương thức chạy đạt một chương trình con trả về.
  • Chạy phương thức kết thúc, và trả về ngầm.

Bạn cũng có thể dừng chủ đề NGOẠI TỆ:

  • Gọi system.exit(điều này sẽ giết chết toàn bộ quá trình của bạn)
  • Gọi interrupt()phương thức của đối tượng luồng *
  • Xem nếu luồng có một phương thức được triển khai có vẻ như nó sẽ hoạt động (như kill()hoặc stop())

*: Kỳ vọng là điều này được cho là dừng một chuỗi. Tuy nhiên, những gì luồng thực sự làm khi điều này xảy ra hoàn toàn phụ thuộc vào những gì nhà phát triển đã viết khi họ tạo ra việc thực hiện luồng.

Một mô hình phổ biến mà bạn thấy khi triển khai phương thức chạy là a while(boolean){}, trong đó boolean thường là một cái gì đó được đặt tên isRunning, đó là một biến thành viên của lớp luồng của nó, nó dễ bay hơi và thường được truy cập bởi các luồng khác bằng phương thức sắp xếp setter, ví dụ kill() { isRunnable=false; }. Các chương trình con này là tốt bởi vì chúng cho phép luồng phát hành bất kỳ tài nguyên nào nó giữ trước khi kết thúc.


3
"Các chương trình con này là tốt bởi vì chúng cho phép luồng phát hành bất kỳ tài nguyên nào nó giữ trước khi kết thúc." Tôi không hiểu Bạn hoàn toàn có thể dọn sạch các tài nguyên bị giữ của một luồng bằng cách sử dụng trạng thái bị gián đoạn "chính thức". Chỉ cần kiểm tra nó bằng Thread.cienThread (). IsInterrupted () hoặc Thread.interrupted () (bất cứ điều gì phù hợp với nhu cầu của bạn), hoặc bắt InterruptedException và dọn dẹp. Vấn đề ở đâu?
Franz D.

Tôi không thể hiểu tại sao phương thức cờ hoạt động, bởi vì tôi đã không hiểu rằng nó dừng lại khi lượt truy cập chạy trở lại !!! Điều này thật đơn giản, thưa ngài cảm ơn bạn đã chỉ ra điều này, không ai đã làm điều này một cách rõ ràng.
thahgr

9

Bạn phải luôn kết thúc chuỗi bằng cách kiểm tra cờ trong run()vòng lặp (nếu có).

Chủ đề của bạn sẽ trông như thế này:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean execute;

    @Override
    public void run() {
        this.execute = true;
        while (this.execute) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                this.execute = false;
            }
        }
    }

    public void stopExecuting() {
        this.execute = false;
    }
}

Sau đó, bạn có thể kết thúc chuỗi bằng cách gọi thread.stopExecuting(). Bằng cách đó, chủ đề được kết thúc sạch sẽ, nhưng điều này mất đến 15 giây (do giấc ngủ của bạn). Bạn vẫn có thể gọi thread.interrupt () nếu nó thực sự khẩn cấp - nhưng cách ưu tiên luôn là kiểm tra cờ.

Để tránh phải chờ 15 giây, bạn có thể chia nhỏ giấc ngủ như thế này:

        ...
        try {
            LOGGER.debug("Sleeping...");
            for (int i = 0; (i < 150) && this.execute; i++) {
                Thread.sleep((long) 100);
            }

            LOGGER.debug("Processing");
        } catch (InterruptedException e) {
        ...

2
nó không phải là Thread- nó thực hiện Runnable- bạn không thể gọi Threadcác phương thức trên nó trừ khi bạn khai báo nó như là Threadtrong trường hợp bạn không thể gọistopExecuting()
Don Cheadle

7

Thông thường, một chủ đề bị chấm dứt khi nó bị gián đoạn. Vì vậy, tại sao không sử dụng boolean bản địa? Hãy thử isInterrupted ():

Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            while(!Thread.currentThread().isInterrupted()){
                // do stuff         
            }   
        }});
    t.start();

    // Sleep a second, and then interrupt
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {}
    t.interrupt();

ref- Làm thế nào tôi có thể giết một chủ đề? không sử dụng stop ();


5

Để đồng bộ hóa các luồng, tôi thích sử dụng CountDownLatchnó giúp các luồng chờ cho đến khi quá trình được thực hiện hoàn tất. Trong trường hợp này, lớp worker được thiết lập với một CountDownLatchthể hiện với số lượng đã cho. Một cuộc gọi đến awaitphương thức sẽ chặn cho đến khi tổng số hiện tại bằng không do các yêu cầu của countDownphương thức hoặc bộ thời gian chờ đã đạt được. Cách tiếp cận này cho phép ngắt một luồng ngay lập tức mà không phải chờ thời gian chờ đã chỉ định để trôi qua:

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);

    private final CountDownLatch countdownlatch;
    public IndexProcessor(CountDownLatch countdownlatch) {
        this.countdownlatch = countdownlatch;
    }


    public void run() {
        try {
            while (!countdownlatch.await(15000, TimeUnit.MILLISECONDS)) {
                LOGGER.debug("Processing...");
            }
        } catch (InterruptedException e) {
            LOGGER.error("Exception", e);
            run = false;
        }

    }
}

Khi bạn muốn hoàn thành việc thực hiện luồng khác, hãy thực hiện CountDown trên CountDownLatchjoin luồng luồng cho luồng chính:

public class SearchEngineContextListener implements ServletContextListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchEngineContextListener.class);

    private Thread thread = null;
    private IndexProcessor runnable = null;
    private CountDownLatch countdownLatch = null;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        countdownLatch = new CountDownLatch(1);
        Thread thread = new Thread(new IndexProcessor(countdownLatch));
        LOGGER.debug("Starting thread: " + thread);
        thread.start();
        LOGGER.debug("Background process successfully started.");
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LOGGER.debug("Stopping thread: " + thread);
        if (countdownLatch != null) 
        {
            countdownLatch.countDown();
        } 
        if (thread != null) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
            }
            LOGGER.debug("Thread successfully stopped.");
        } 
    }
}

3

Một số thông tin bổ sung. Cả cờ và ngắt đều được đề xuất trong tài liệu Java.

https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {
    blinker = null;
}

public void run() {
    Thread thisThread = Thread.currentThread();
    while (blinker == thisThread) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

Đối với một chuỗi chờ trong thời gian dài (ví dụ: cho đầu vào), hãy sử dụng Thread.interrupt

public void stop() {
     Thread moribund = waiter;
      waiter = null;
      moribund.interrupt();
 }

3
Không bao giờ bỏ qua một ngoại lệ bị gián đoạn. Nó có nghĩa là một số mã khác rõ ràng yêu cầu chủ đề của bạn chấm dứt. Một chủ đề mà bỏ qua yêu cầu đó là một chủ đề lừa đảo. Cách chính xác để xử lý InterruptedException là thoát khỏi vòng lặp.
VGR

2

Tôi đã không làm gián đoạn hoạt động trong Android, vì vậy tôi đã sử dụng phương pháp này, hoạt động hoàn hảo:

boolean shouldCheckUpdates = true;

private void startupCheckForUpdatesEveryFewSeconds() {
    threadCheckChat = new Thread(new CheckUpdates());
    threadCheckChat.start();
}

private class CheckUpdates implements Runnable{
    public void run() {
        while (shouldCheckUpdates){
            System.out.println("Do your thing here");
        }
    }
}

 public void stop(){
        shouldCheckUpdates = false;
 }

Điều này có khả năng thất bại, vì ShouldCheckUpdates thì không volatile. Xem docs.oracle.com/javase/specs/jls/se9/html/jls-17.html#jls-17.3 .
VGR

0

Thỉnh thoảng tôi sẽ thử 1000 lần trong onDestroy () / contextDestroyed () của tôi

      @Override
    protected void onDestroy() {
        boolean retry = true;
        int counter = 0;
        while(retry && counter<1000)
        {
            counter++;
            try{thread.setRunnung(false);
                thread.join();
                retry = false;
                thread = null; //garbage can coll
            }catch(InterruptedException e){e.printStackTrace();}
        }

    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.