Làm thế nào để biết các chủ đề khác đã hoàn thành?


125

Tôi có một đối tượng với một phương thức có tên StartDownload(), bắt đầu ba luồng.

Làm thế nào để tôi nhận được thông báo khi mỗi luồng đã thực hiện xong?

Có cách nào để biết một (hoặc tất cả) của luồng đã kết thúc hay vẫn đang thực thi?


Câu trả lời:


227

Có một số cách bạn có thể làm điều này:

  1. Sử dụng Thread.join () trong luồng chính của bạn để chờ theo kiểu chặn cho mỗi Chủ đề hoàn thành hoặc
  2. Kiểm tra Thread.isAlive () theo kiểu bỏ phiếu - thường không được khuyến khích - để đợi cho đến khi mỗi Chủ đề hoàn thành, hoặc
  3. Không chính thống, đối với mỗi Chủ đề được đề cập, hãy gọi setUncaughtExceptionHandler để gọi một phương thức trong đối tượng của bạn và lập trình cho mỗi Chủ đề để ném Ngoại lệ chưa được xử lý khi hoàn thành hoặc
  4. Sử dụng khóa hoặc đồng bộ hóa hoặc cơ chế từ java.util.conc hiện , hoặc
  5. Chính thống hơn, tạo một trình lắng nghe trong Chủ đề chính của bạn, sau đó lập trình từng Chủ đề của bạn để nói với người nghe rằng họ đã hoàn thành.

Làm thế nào để thực hiện ý tưởng số 5? Vâng, một cách đầu tiên là tạo giao diện:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

sau đó tạo lớp sau:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

và sau đó mỗi Chủ đề của bạn sẽ mở rộng NotifyingThreadvà thay vì triển khai, run()nó sẽ thực hiện doRun(). Do đó, khi họ hoàn thành, họ sẽ tự động thông báo cho bất cứ ai đang chờ thông báo.

Cuối cùng, trong lớp chính của bạn - lớp bắt đầu tất cả Chủ đề (hoặc ít nhất là đối tượng đang chờ thông báo) - sửa đổi lớp đó thành implement ThreadCompleteListenervà ngay sau khi tạo mỗi Chủ đề thêm chính nó vào danh sách người nghe:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

sau đó, khi mỗi luồng thoát ra, notifyOfThreadCompletephương thức của bạn sẽ được gọi với đối tượng Thread vừa hoàn thành (hoặc bị lỗi).

Lưu ý rằng tốt hơn là nên implements Runnablechứ không phải extends Threadcho NotifyingThreadnhư mở rộng chủ đề thường được khuyến khích trong mã mới. Nhưng tôi đang mã hóa cho câu hỏi của bạn. Nếu bạn thay đổi NotifyingThreadlớp để triển khai Runnablethì bạn phải thay đổi một số mã quản lý Chủ đề, điều này khá đơn giản để thực hiện.


Chào!! Tôi thích ý tưởng cuối cùng. Tôi để thực hiện một người nghe để làm điều đó? Cảm ơn
Ricardo Felgueiras

4
nhưng bằng cách sử dụng phương pháp này, các notifiyListener được gọi bên trong run () vì vậy nó sẽ được gọi là insisde thread và các cuộc gọi tiếp theo cũng sẽ được thực hiện ở đó, phải không?
Jordi Puigdellívol 15/03/13

1
@Eddie Jordi đã hỏi, nếu có thể gọi notifyphương thức không bắt buộc runphương thức, nhưng sau nó.
Tomasz Dzięcielewski

1
Câu hỏi thực sự là: làm thế nào để bạn có được TẮT luồng thứ cấp ngay bây giờ. Tôi biết rằng nó đã kết thúc, nhưng làm thế nào để tôi truy cập vào Chủ đề chính bây giờ?
Patrick

1
Chủ đề này có an toàn không? Dường như notifyListener (và do đó notifyOfThreadComplete) sẽ được gọi trong NotifyingThread, thay vì trong luồng đã tạo ra chính Listener.
Aaron

13

Giải pháp sử dụng CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

nhãn0 - hàng rào tuần hoàn được tạo với số lượng bên bằng số lượng luồng thực thi cộng với một luồng cho luồng thực thi chính (trong đó startD Download () đang được thực thi)

nhãn 1 - Tải xuống thứ nread vào phòng chờ

nhãn 3 - NUMBER_OF_DOWNLOADING_THREADS đã vào phòng chờ. Chuỗi xử lý chính giải phóng chúng để bắt đầu thực hiện các công việc tải xuống của chúng trong ít nhiều cùng một lúc

nhãn 4 - chủ đề thực hiện chính vào phòng chờ. Đây là phần 'khó nhất' của mã để hiểu. Không có vấn đề gì chủ đề sẽ vào phòng chờ lần thứ hai. Điều quan trọng là bất kỳ luồng nào vào phòng cuối cùng đều đảm bảo rằng tất cả các luồng tải xuống khác đã hoàn thành công việc tải xuống của họ.

nhãn 2 - n-th DownloadingThread đã hoàn thành công việc tải xuống và vào phòng chờ. Nếu đó là cái cuối cùng, tức là đã có SỐ_OF_DOWNLOADING_THREADS đã nhập nó, bao gồm cả luồng thực thi chính, luồng chính sẽ chỉ tiếp tục thực hiện khi tất cả các luồng khác đã tải xuống xong.


9

Bạn nên thực sự thích một giải pháp sử dụng java.util.concurrent. Tìm và đọc Josh Bloch và / hoặc Brian Goetz về chủ đề này.

Nếu bạn không sử dụng java.util.concurrent.*và chịu trách nhiệm sử dụng Chủ đề trực tiếp, thì có lẽ bạn nên sử dụng join()để biết khi nào một chủ đề được thực hiện. Dưới đây là một cơ chế gọi lại siêu đơn giản. Đầu tiên mở rộng Runnablegiao diện để có một cuộc gọi lại:

public interface CallbackRunnable extends Runnable {
    public void callback();
}

Sau đó, thực hiện một Executor sẽ thực thi runnable của bạn và gọi lại cho bạn khi nó được thực hiện.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

Một thứ rõ ràng khác để thêm vào CallbackRunnablegiao diện của bạn là một phương tiện để xử lý bất kỳ trường hợp ngoại lệ nào, vì vậy có thể đặt một public void uncaughtException(Throwable e);dòng trong đó và trong trình thực thi của bạn, cài đặt Thread.UncaughtExceptionHandler để gửi cho bạn phương thức giao diện đó.

Nhưng làm tất cả những gì thực sự bắt đầu có mùi như thế nào java.util.concurrent.Callable. Bạn nên thực sự xem xét sử dụng java.util.concurrentnếu dự án của bạn cho phép nó.


Tôi có một chút không rõ ràng về những gì bạn đạt được từ cơ chế gọi lại này so với việc gọi đơn giản runner.join()và sau đó bất kỳ mã nào bạn muốn sau đó, vì bạn biết chuỗi đã kết thúc. Có phải chỉ là bạn có thể định nghĩa mã đó là một thuộc tính của runnable, vì vậy bạn có thể có những thứ khác nhau cho các runnable khác nhau?
Stephen

2
Vâng, runner.join()là cách trực tiếp nhất để chờ đợi. Tôi đã giả sử OP không muốn chặn chuỗi cuộc gọi chính của họ vì họ yêu cầu được "thông báo" cho mỗi lần tải xuống, có thể hoàn thành theo bất kỳ thứ tự nào. Điều này cung cấp một cách để nhận được thông báo không đồng bộ.
broc.seib

4

Bạn có muốn đợi họ hoàn thành? Nếu vậy, sử dụng phương thức Tham gia.

Ngoài ra còn có thuộc tính isAlive nếu bạn chỉ muốn kiểm tra nó.


3
Lưu ý rằng isAlive trả về false nếu luồng chưa bắt đầu thực thi (ngay cả khi luồng của bạn đã được gọi bắt đầu trên nó).
Tom Hawtin - tackline

@ TomHawtin-tackline bạn có chắc chắn về điều đó không? Nó sẽ mâu thuẫn với tài liệu Java ("Một chủ đề còn sống nếu nó đã được bắt đầu và chưa chết" - docs.oracle.com/javase/6/docs/api/java/lang/ canh ). Nó cũng sẽ mâu thuẫn với các câu trả lời ở đây ( stackoverflow.com/questions/17293304/NH )
Stephen

@Stephen Một thời gian dài kể từ khi tôi viết nó, nhưng nó có vẻ là sự thật. Tôi tưởng tượng nó gây ra những vấn đề khác cho người khác trong ký ức của tôi chín năm trước. Chính xác những gì có thể quan sát được sẽ phụ thuộc vào việc thực hiện. Bạn kể một Threadđể start, mà thread không, nhưng trở về cuộc gọi ngay lập tức. isAlivenên là một thử nghiệm cờ đơn giản, nhưng khi tôi googled nó thì phương thức là native.
Tom Hawtin - tackline

4

Bạn có thể thẩm vấn thể hiện luồng với getState () trả về một thể hiện của phép liệt kê Thread.State với một trong các giá trị sau:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

Tuy nhiên tôi nghĩ rằng nó sẽ là một thiết kế tốt hơn để có một chủ đề chính chờ 3 đứa trẻ hoàn thành, chủ sau đó sẽ tiếp tục thực hiện khi 3 người kia đã hoàn thành.


Chờ đợi 3 đứa trẻ thoát ra có thể không phù hợp với mô hình sử dụng. Nếu đây là trình quản lý tải xuống, họ có thể muốn bắt đầu 15 lượt tải xuống và chỉ cần xóa trạng thái khỏi thanh trạng thái hoặc thông báo cho người dùng khi quá trình tải xuống hoàn tất, trong trường hợp đó, cuộc gọi lại sẽ hoạt động tốt hơn.
Digitaljoel

3

Bạn cũng có thể sử dụng Executorsđối tượng để tạo nhóm luồng ExecutorService . Sau đó sử dụng invokeAllphương thức để chạy từng luồng của bạn và truy xuất Tương lai. Điều này sẽ chặn cho đến khi tất cả đã thực hiện xong. Tùy chọn khác của bạn sẽ là thực thi từng cái bằng cách sử dụng nhóm và sau đó gọi awaitTerminationđể chặn cho đến khi nhóm thực hiện xong. Chỉ cần chắc chắn gọi shutdown() khi bạn hoàn thành thêm nhiệm vụ.


2

Nhiều thứ đã được thay đổi trong 6 năm qua trên mặt trận đa luồng.

Thay vì sử dụng join()và khóa API, bạn có thể sử dụng

1. API thực thi dịch vụ invokeAll()

Thực hiện các nhiệm vụ nhất định, trả về một danh sách các Tương lai giữ trạng thái và kết quả của chúng khi tất cả hoàn thành.

2. CountDownLatch

Một trợ giúp đồng bộ hóa cho phép một hoặc nhiều luồng chờ cho đến khi một tập hợp các hoạt động được thực hiện trong các luồng khác hoàn thành.

A CountDownLatchđược khởi tạo với một số lượng nhất định. Các phương thức chờ đợi 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 countDown()phương thức, sau đó tất cả các chuỗi chờ được phát hành và bất kỳ yêu cầu tiếp theo nào của việc chờ đợi trả về ngay lập tức. Đây là hiện tượng một lần - không thể thiết lập lại số đếm. Nếu bạn cần một phiên bản đặt lại số đếm, hãy xem xét sử dụng CyclicBarrier.

3. ForkJoinPool hoặc newWorkStealingPool()trong Executors là cách khác

4. Thực hiện thông qua tất cả các Futuretác vụ từ gửi ExecutorServicevà kiểm tra trạng thái với chặn cuộc gọi get()trên Futuređối tượng

Có một cái nhìn về các câu hỏi SE liên quan:

Làm thế nào để chờ đợi một chủ đề sinh ra chủ đề của chính nó?

Nhân viên điều hành: Làm thế nào để chờ đồng bộ cho đến khi tất cả các nhiệm vụ kết thúc nếu các tác vụ được tạo đệ quy?


2

Tôi sẽ đề nghị nhìn vào lớp javadoc cho Thread .

Bạn có nhiều cơ chế để thao tác luồng.

  • Chủ đề chính của bạn có thể join()ba chủ đề một cách thanh thản, và sau đó sẽ không tiến hành cho đến khi cả ba chủ đề được thực hiện.

  • Thăm dò trạng thái luồng của các luồng sinh sản trong khoảng thời gian.

  • Đặt tất cả các chủ đề được sinh ra thành một riêng biệt ThreadGroupvà thăm dò ý kiến activeCount()trên ThreadGroupvà chờ đợi nó đến 0.

  • Thiết lập một kiểu gọi lại tùy chỉnh hoặc loại giao diện cho giao tiếp giữa các luồng.

Tôi chắc chắn có nhiều cách khác tôi vẫn còn thiếu.


1

Đây là một giải pháp đơn giản, ngắn gọn, dễ hiểu và hoạt động hoàn hảo với tôi. Tôi cần phải vẽ lên màn hình khi một chủ đề khác kết thúc; nhưng không thể vì luồng chính có quyền điều khiển màn hình. Vì thế:

(1) Tôi đã tạo biến toàn cục: boolean end1 = false;Chuỗi đặt nó thành true khi kết thúc. Điều đó được chọn trong vòng lặp được bảo trì bởi vòng lặp "postDelayed", nơi nó được phản hồi.

(2) Chủ đề của tôi chứa:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) May mắn thay, "postDelayed" chạy trong luồng chính, vì vậy đó là nơi kiểm tra luồng khác mỗi giây một lần. Khi luồng khác kết thúc, điều này có thể bắt đầu bất cứ điều gì chúng ta muốn làm tiếp theo.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) Cuối cùng, bắt đầu toàn bộ hoạt động ở đâu đó trong mã của bạn bằng cách gọi:

void startThread()
{
   myThread();
   checkThread();
}

1

Tôi đoán cách dễ nhất là sử dụng ThreadPoolExecutorlớp.

  1. Nó có một hàng đợi và bạn có thể đặt bao nhiêu luồng nên hoạt động song song.
  2. Nó có các phương thức gọi lại tốt đẹp:

Phương pháp móc

Lớp này cung cấp bảo vệ overridable beforeExecute(java.lang.Thread, java.lang.Runnable)afterExecute(java.lang.Runnable, java.lang.Throwable)các phương thức được gọi trước và sau khi thực hiện từng tác vụ. Chúng có thể được sử dụng để thao túng môi trường thực thi; ví dụ: khởi tạo lại ThreadLocals, thu thập số liệu thống kê hoặc thêm các mục nhật ký. Ngoài ra, phương thức terminated()có thể được ghi đè để thực hiện bất kỳ xử lý đặc biệt nào cần được thực hiện khi Executor đã kết thúc hoàn toàn.

đó chính xác là những gì chúng ta cần. Chúng tôi sẽ ghi đè afterExecute()để nhận cuộc gọi lại sau khi mỗi luồng được thực hiện và sẽ ghi đè terminated()để biết khi nào tất cả các luồng được thực hiện.

Vì vậy, đây là những gì bạn nên làm

  1. Tạo một người thi hành:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. Và bắt đầu chủ đề của bạn:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

Phương thức bên trong informUiThatWeAreDone();làm bất cứ điều gì bạn cần làm khi tất cả các luồng được thực hiện, ví dụ, cập nhật giao diện người dùng.

LƯU Ý: Đừng quên sử dụngsynchronized các phương thức vì bạn thực hiện công việc của mình song song và RẤT CAUTIOUS nếu bạn quyết định gọi synchronizedphương thức từ synchronizedphương thức khác ! Điều này thường dẫn đến bế tắc

Hi vọng điêu nay co ich!


0

Bạn cũng có thể sử dụng SwingWorker, có hỗ trợ thay đổi thuộc tính tích hợp. Xem addPropertyChangeListener () hoặc phương thức get () để biết ví dụ về trình lắng nghe thay đổi trạng thái.


0

Nhìn vào tài liệu Java cho lớp Thread. Bạn có thể kiểm tra trạng thái của chủ đề. Nếu bạn đặt ba luồng trong các biến thành viên, thì cả ba luồng có thể đọc trạng thái của nhau.

Tuy nhiên, bạn phải cẩn thận một chút vì bạn có thể gây ra tình trạng chủng tộc giữa các luồng. Chỉ cần cố gắng tránh logic phức tạp dựa trên trạng thái của các luồng khác. Chắc chắn tránh nhiều chủ đề viết cho cùng một biến.

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.