У мене є об'єкт із методом StartDownload()
, який називається , який починає три нитки.
Як мені отримати сповіщення, коли кожен потік завершує виконання?
Чи є спосіб дізнатися, чи закінчена одна (або вся) нитка чи все ще виконується?
У мене є об'єкт із методом StartDownload()
, який називається , який починає три нитки.
Як мені отримати сповіщення, коли кожен потік завершує виконання?
Чи є спосіб дізнатися, чи закінчена одна (або вся) нитка чи все ще виконується?
Відповіді:
Є кілька способів зробити це:
Як реалізувати Ідею №5? Ну, один із способів - спочатку створити інтерфейс:
public interface ThreadCompleteListener {
void notifyOfThreadComplete(final Thread thread);
}
тоді створіть такий клас:
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();
}
а потім кожен з ваших потоків буде поширюватися NotifyingThread
і замість реалізації run()
вона буде здійснювати doRun()
. Таким чином, коли вони завершать, вони автоматично повідомлять усіх, хто чекає сповіщення.
Нарешті, у вашому головному класі - тому, що запускає всі теми (або принаймні об’єкт, який очікує на сповіщення) - модифікуйте цей клас implement ThreadCompleteListener
та одразу після створення кожної теми додайте себе до списку слухачів:
NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start(); // Start the Thread
тоді, коли кожна тема notifyOfThreadComplete
буде завершена , ваш метод буде викликаний з екземпляром Thread, який щойно завершився (або вийшов з ладу).
Зауважте, що краще було б implements Runnable
замість того, extends Thread
щоб NotifyingThread
розширити нитку, як правило, не відволікається на новий код. Але я кодую ваше запитання. Якщо ви змінили NotifyingThread
клас на реалізацію, Runnable
вам доведеться змінити частину свого коду, який керує потоками, що досить просто зробити.
notify
метод не insinde run
методом, а після нього.
Розчин за допомогою 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");
}
}
label0 - циклічний бар'єр створюється з кількістю сторін, рівній кількості виконуваних потоків плюс один для основного потоку виконання (в якому виконується startDownload ())
мітка 1 - n-а ЗавантаженняThread заходить у зал очікування
мітка 3 - NUMBER_OF_DOWNLOADING_THREADS увійшли до зали очікування. Основний потік виконання звільняє їх від того, щоб почати виконувати завдання з завантаження більш-менш за один і той же час
мітка 4 - головна нитка виконання надходить у зал очікування. Це "найскладніша" частина коду, яку потрібно зрозуміти. Не має значення, яка нитка ввійде в зал очікування вдруге. Важливо, щоб будь-яка нитка входила в приміщення останньою, гарантувала, що всі інші потоки для завантаження закінчили роботу над завантаженням.
мітка 2 - n-а програма DownloadingThread закінчила роботу з завантаження і входить у зал очікування. Якщо він останній, тобто вже введено його NUMBER_OF_DOWNLOADING_THREADS, включаючи основний потік виконання, головний потік продовжить його виконання лише тоді, коли всі інші потоки закінчать завантаження.
Ви повинні дійсно віддати перевагу рішення , яке використовує java.util.concurrent
. Знайдіть і прочитайте Джоша Блоха та / або Брайана Геца з цієї теми.
Якщо ви не використовуєте java.util.concurrent.*
та берете на себе відповідальність за використання join()
потоків безпосередньо, то, ймовірно, вам слід скористатися, щоб знати, коли буде виконано нитку. Ось надзвичайно простий механізм зворотного виклику. Спочатку розгорніть Runnable
інтерфейс, щоб мати зворотний дзвінок:
public interface CallbackRunnable extends Runnable {
public void callback();
}
Потім зробіть Виконавця, який виконає ваш запуск і передзвонить вам, коли це буде зроблено.
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();
}
}
}
Інший вид очевидної речі, який потрібно додати до вашого CallbackRunnable
інтерфейсу, - це спосіб обробляти будь-які винятки, тому, можливо, покладіть public void uncaughtException(Throwable e);
рядок туди і у виконавця, встановіть Thread.UncaughtExceptionHandler, щоб надіслати вас до цього методу інтерфейсу.
Але робити все, що насправді починає пахнути java.util.concurrent.Callable
. Ви дійсно повинні поглянути на використання, java.util.concurrent
якщо ваш проект це дозволяє.
runner.join()
а потім будь-який код, який ви хочете після цього, оскільки ви знаєте, що нитка закінчена. Чи просто ви маєте визначити цей код як властивість запущеного, щоб у вас могли бути різні речі для різних бігунів?
runner.join()
це найбільш прямий спосіб почекати. Я припускав, що ОП не хотіла блокувати їх основний потік дзвінка, оскільки вони просили отримувати "повідомлення" про кожне завантаження, яке може завершитися в будь-якому порядку. Це запропонувало один із способів отримувати сповіщення асинхронно.
Ви хочете чекати, коли вони закінчать? Якщо так, використовуйте метод приєднання.
Існує також властивість isAlive, якщо ви просто хочете його перевірити.
Thread
щоб start
, що нитка робить, але виклик повертається негайно. isAlive
повинен бути простим тестом на прапор, але коли я google це був метод native
.
Ви можете допитати екземпляр потоку за допомогою getState (), який повертає екземпляр перерахунку Thread.State з одним із наступних значень:
* 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.
Однак я думаю, що було б кращим дизайном мати головну нитку, яка чекає, коли 3 дитини закінчать, потім майстер продовжить виконання, коли інші 3 закінчать.
Ви також можете використовувати Executors
об'єкт для створення пулу потоків ExecutorService . Потім скористайтеся invokeAll
методом, щоб запустити кожну свою нитку та отримати майбутні ф'ючерси. Це буде заблоковано, поки всі не закінчать виконання. Вашим іншим варіантом буде виконати кожен за допомогою пулу, а потім зателефонувати, awaitTermination
щоб заблокувати, поки пул не буде закінчено виконувати. Просто shutdown
додайте (), коли ви додасте завдання.
Багато речей було змінено за останні 6 років на багатонитковому фронті.
Замість використання join()
та блокування API ви можете використовувати
1. API ExecutorService invokeAll()
Виконує задані завдання, повертаючи список Futures з їх статусом та результатами, коли всі вони завершені.
Допомога синхронізації, яка дозволяє одному або більше потокам чекати, поки не завершиться набір операцій, що виконуються в інших потоках.
A
CountDownLatch
ініціалізується із заданим числом. Блокують методи очікування, поки кількість поточного числа не досягне нуля через викликиcountDown()
методу, після чого всі потоки очікування звільняються та будь-які наступні виклики повернення очікують негайно. Це явище однократне - підрахунок не може бути скинутий. Якщо вам потрібна версія, яка скидає кількість, подумайте про використання CyclicBarrier.
3. ForkJoinPool або newWorkStealingPool()
в виконавців інший шлях
4.Переводити всі Future
завдання від подачі ExecutorService
і перевіряти стан з блокуванням виклику get()
на Future
об'єкті
Подивіться на відповідні питання SE:
Як чекати, коли нитка, яка породжує власну нитку?
Виконавці: Як синхронно дочекатися завершення всіх завдань, якщо завдання створюються рекурсивно?
Я б запропонував поглянути на javadoc для класу Thread .
У вас є кілька механізмів маніпулювання нитками.
Ваша основна нитка могла б join()
три потоки послідовно, і тоді вона не продовжуватиметься, поки всі три не будуть виконані.
Опитуйте стан ниток породжених ниток з інтервалом.
Покладіть всі породжені нитки в окремий ThreadGroup
і опитуйте activeCount()
на ThreadGroup
течії та зачекайте, поки вона досягне 0.
Налаштуйте користувальницький тип інтерфейсу для зворотного дзвінка або прослуховувача для міжпотокового зв'язку.
Я впевнений, що існує багато інших способів, яких я все ще пропускаю.
Ось таке рішення, яке є простим, коротким, легким для розуміння і прекрасно працює для мене. Мені потрібно було намалювати на екрані, коли закінчується інша нитка; але не вдалося, оскільки головна нитка контролює екран. Так:
(1) Я створив глобальну змінну: boolean end1 = false;
потік встановлює її як істинну при закінченні. Це підхоплюється в основному потоці циклом "postDelayed", де він відповідає.
(2) Моя нитка містить:
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) На щастя, "postDelayed" працює в основній нитці, тож саме тут слід перевіряти інший потік раз на секунду. Коли інша нитка закінчиться, це може початися все, що ми хочемо зробити далі.
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) Нарешті, почніть все, що працює десь у вашому коді, зателефонувавши:
void startThread()
{
myThread();
checkThread();
}
Я думаю, найпростіший спосіб - це використовувати ThreadPoolExecutor
клас.
Гакові методи
Цей клас забезпечує захищені перезаписи
beforeExecute(java.lang.Thread, java.lang.Runnable)
таafterExecute(java.lang.Runnable, java.lang.Throwable)
методи, які викликаються до та після виконання кожного завдання. Вони можуть бути використані для маніпулювання середовищем виконання; наприклад, повторна ініціалізація ThreadLocals, збір статистики або додавання записів журналу. Крім того, методterminated()
може бути замінений для виконання будь-якої спеціальної обробки, яку необхідно виконати після повного припинення Виконавця.
що саме те, що нам потрібно. Ми будемо заміняти afterExecute()
зворотні виклики після того, як буде виконано кожен потік, і будемо заміняти, terminated()
щоб знати, коли всі потоки виконані.
Тож ось що ви повинні зробити
Створіть виконавця:
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();
}
}
І починайте свої теми:
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
}
Внутрішнім методом informUiThatWeAreDone();
зробіть все, що вам потрібно зробити, коли всі потоки виконані, наприклад, оновіть інтерфейс користувача.
Примітка: Не забувайте про використання synchronized
методів , так як ви робите свою роботу паралельно і бути дуже обережними , якщо ви вирішите виклик synchronized
метод з іншого synchronized
методу! Це часто призводить до тупиків
Сподіваюся, це допомагає!
Ви також можете використовувати SwingWorker, який має вбудовану підтримку змін власності. Див. AddPropertyChangeListener () або метод get () для прикладу слухача змін стану.
Подивіться на документацію Java для класу Thread. Ви можете перевірити стан потоку. Якщо розмістити три потоки в змінних членів, то всі три потоки можуть читати стани один одного.
Ти повинен бути трохи обережним, тому що ти можеш викликати перегони між потоками. Просто спробуйте уникати складної логіки, заснованої на стані інших потоків. Однозначно уникайте запису декількох потоків до одних і тих же змінних.