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?
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:
Có một số cách bạn có thể làm điều này:
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 NotifyingThread
và 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 ThreadCompleteListener
và 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, notifyOfThreadComplete
phươ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 Runnable
chứ không phải extends Thread
cho NotifyingThread
như 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 NotifyingThread
lớp để triển khai Runnable
thì 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.
notify
phương thức không bắt buộc run
phương thức, nhưng sau nó.
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.
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 Runnable
giao 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 CallbackRunnable
giao 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.concurrent
nếu dự án của bạn cho phép 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?
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ộ.
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ó.
Thread
để start
, mà thread không, nhưng trở về cuộc gọi ngay lập tức. isAlive
nê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
.
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.
Bạn cũng có thể sử dụng Executors
đối tượng để tạo nhóm luồng ExecutorService . Sau đó sử dụng invokeAll
phươ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ụ.
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.
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ủacountDown()
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 Future
tác vụ từ gửi ExecutorService
và 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ó?
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 ThreadGroup
và thăm dò ý kiến activeCount()
trên ThreadGroup
và 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.
Đâ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();
}
Tôi đoán cách dễ nhất là sử dụng ThreadPoolExecutor
lớ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)
và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ứcterminated()
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
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();
}
}
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 synchronized
phương thức từ synchronized
phươ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!
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.
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.