ฉันมีวัตถุที่มีเมธอดชื่อStartDownload()
ซึ่งเริ่มต้นสามเธรด
ฉันจะรับการแจ้งเตือนได้อย่างไรเมื่อแต่ละเธรดดำเนินการเสร็จสิ้น
มีวิธีที่จะทราบว่าเธรดหนึ่ง (หรือทั้งหมด) เสร็จสิ้นหรือยังคงดำเนินการอยู่หรือไม่?
ฉันมีวัตถุที่มีเมธอดชื่อStartDownload()
ซึ่งเริ่มต้นสามเธรด
ฉันจะรับการแจ้งเตือนได้อย่างไรเมื่อแต่ละเธรดดำเนินการเสร็จสิ้น
มีวิธีที่จะทราบว่าเธรดหนึ่ง (หรือทั้งหมด) เสร็จสิ้นหรือยังคงดำเนินการอยู่หรือไม่?
คำตอบ:
มีหลายวิธีที่คุณสามารถทำได้:
จะใช้ Idea # 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
เมธอดของคุณจะถูกเรียกใช้ด้วยอินสแตนซ์เธรดที่เพิ่งเสร็จสิ้น (หรือล้มเหลว)
โปรดทราบว่าดีกว่าจะimplements Runnable
มากกว่าextends Thread
สำหรับNotifyingThread
การขยายกระทู้มักจะเป็นกำลังใจในรหัสใหม่ แต่ฉันกำลังเขียนคำถามของคุณ หากคุณเปลี่ยนNotifyingThread
คลาสเพื่อใช้งานRunnable
คุณจะต้องเปลี่ยนโค้ดบางส่วนของคุณที่จัดการเธรดซึ่งค่อนข้างตรงไปตรงมาที่จะทำ
notify
วิธีการไม่ได้แทรกซึม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-th DownloadingThread เข้าสู่ห้องรอ
ป้ายกำกับ 3 - NUMBER_OF_DOWNLOADING_THREADS เข้าห้องรอ เธรดหลักของการดำเนินการเผยแพร่เพื่อเริ่มทำการดาวน์โหลดงานในเวลาเดียวกันไม่มากก็น้อย
ป้ายกำกับ 4 - เธรดหลักของการดำเนินการเข้าสู่ห้องรอ นี่คือส่วนที่ 'ยากที่สุด' ของโค้ดที่จะต้องทำความเข้าใจ ไม่สำคัญว่าเธรดใดจะเข้าสู่ห้องรอเป็นครั้งที่สอง สิ่งสำคัญคือเธรดใด ๆ ก็ตามที่เข้ามาในห้องสุดท้ายเพื่อให้แน่ใจว่าเธรดการดาวน์โหลดอื่น ๆ ทั้งหมดเสร็จสิ้นการดาวน์โหลดงานแล้ว
ป้ายกำกับ 2 - n-th DownloadingThread เสร็จสิ้นการดาวน์โหลดงานและเข้าสู่ห้องรอ หากเป็นรายการสุดท้ายเช่นป้อนไปแล้ว NUMBER_OF_DOWNLOADING_THREADS รวมถึงเธรดหลักของการดำเนินการเธรดหลักจะดำเนินการต่อเมื่อเธรดอื่น ๆ ทั้งหมดเสร็จสิ้นการดาวน์โหลดเท่านั้น
คุณควรจริงๆjava.util.concurrent
ต้องการแก้ปัญหาที่ใช้ ค้นหาและอ่าน Josh Bloch และ / หรือ Brian Goetz ในหัวข้อนี้
หากคุณไม่ได้ใช้งานjava.util.concurrent.*
และรับผิดชอบในการใช้เธรดโดยตรงคุณควรjoin()
ทราบเมื่อเธรดเสร็จสิ้น นี่คือกลไกการโทรกลับที่เรียบง่ายสุด ๆ ขั้นแรกให้ขยายRunnable
อินเทอร์เฟซเพื่อให้มีการติดต่อกลับ:
public interface CallbackRunnable extends Runnable {
public void callback();
}
จากนั้นสร้าง Executor ที่จะเรียกใช้ runnable ของคุณและโทรกลับหาคุณเมื่อดำเนินการเสร็จสิ้น
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);
บรรทัดไว้ที่นั่นและในตัวดำเนินการของคุณติดตั้งเธรด UncaughtExceptionHandler เพื่อส่งคุณไปยังวิธีการเชื่อมต่อนั้น
แต่การทำทั้งหมดนั้นเริ่มมีกลิ่นหอมjava.util.concurrent.Callable
จริงๆ คุณควรพิจารณาใช้java.util.concurrent
ว่าโครงการของคุณอนุญาตหรือไม่
runner.join()
จากนั้นรหัสใด ๆ ที่คุณต้องการหลังจากนั้นเนื่องจากคุณรู้ว่าเธรดเสร็จสิ้นแล้ว เป็นเพียงการที่คุณกำหนดรหัสนั้นเป็นคุณสมบัติของ runnable ดังนั้นคุณอาจมีสิ่งที่แตกต่างกันสำหรับ runnables ที่แตกต่างกัน?
runner.join()
เป็นวิธีที่ตรงที่สุดในการรอ ฉันสมมติว่า OP ไม่ต้องการบล็อกเธรดการโทรหลักของพวกเขาเนื่องจากพวกเขาขอให้ "แจ้งเตือน" สำหรับการดาวน์โหลดแต่ละครั้งซึ่งสามารถดำเนินการตามลำดับใดก็ได้ นี่เป็นวิธีหนึ่งในการรับการแจ้งเตือนแบบอะซิงโครนัส
คุณต้องการรอให้เสร็จหรือไม่ ในกรณีนี้ให้ใช้วิธีเข้าร่วม
นอกจากนี้ยังมีคุณสมบัติ isAlive หากคุณต้องการตรวจสอบ
Thread
ถึงstart
ซึ่งเธรดทำ แต่การโทรกลับทันที isAlive
ควรจะมีการทดสอบธงที่เรียบง่าย แต่เมื่อฉัน googled native
มันเป็นวิธีการที่
คุณสามารถสอบถามอินสแตนซ์เธรดด้วย getState () ซึ่งส่งคืนอินสแตนซ์ของ Thread.State enumeration ด้วยค่าใดค่าหนึ่งต่อไปนี้:
* 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
วิธีการเรียกใช้แต่ละเธรดของคุณและดึงข้อมูล Futures สิ่งนี้จะบล็อกจนกว่าทั้งหมดจะเสร็จสิ้นการดำเนินการ ตัวเลือกอื่นของคุณคือดำเนินการแต่ละตัวโดยใช้พูลจากนั้นเรียกawaitTermination
ไปยังบล็อกจนกว่าพูลจะดำเนินการเสร็จสิ้น อย่าลืมโทรหาshutdown
() เมื่อคุณเพิ่มงานเสร็จแล้ว
มีการเปลี่ยนแปลงหลายอย่างในช่วง 6 ปีที่ผ่านมาในด้านหน้าแบบมัลติเธรด
แทนที่จะใช้join()
และล็อก API คุณสามารถใช้ไฟล์
1. ExecutorService invokeAll()
API
ดำเนินงานที่กำหนดส่งคืนรายการ Futures ที่มีสถานะและผลลัพธ์เมื่อทั้งหมดเสร็จสมบูรณ์
ตัวช่วยในการซิงโครไนซ์ที่อนุญาตให้เธรดตั้งแต่หนึ่งเธรดขึ้นไปรอจนกว่าชุดของการดำเนินการในเธรดอื่นจะเสร็จสิ้น
A
CountDownLatch
เริ่มต้นด้วยจำนวนที่กำหนด วิธีการรอคอยบล็อกจนกว่าจำนวนปัจจุบันจะถึงศูนย์เนื่องจากการเรียกใช้countDown()
เมธอดหลังจากนั้นเธรดที่รอทั้งหมดจะถูกปล่อยออกและการเรียกใช้การรอคอยที่ตามมาจะส่งคืนทันที นี่เป็นปรากฏการณ์หนึ่งช็อต - ไม่สามารถรีเซ็ตการนับได้ หากคุณต้องการเวอร์ชันที่รีเซ็ตการนับให้พิจารณาใช้ CyclicBarrier
3. ForkJoinPoolหรือ newWorkStealingPool()
ในExecutorsเป็นวิธีอื่น
4. ฝึกฝนFuture
งานทั้งหมดจากการส่งExecutorService
และตรวจสอบสถานะด้วยการปิดกั้นการโทรget()
บนFuture
วัตถุ
ดูคำถาม SE ที่เกี่ยวข้อง:
จะรอด้ายที่เกิดด้ายของตัวเองได้อย่างไร?
ผู้ดำเนินการ: จะรอให้งานทั้งหมดเสร็จสิ้นได้อย่างไรหากมีการสร้างงานซ้ำ ๆ
ฉันขอแนะนำให้ดูที่ javadoc สำหรับ คลาสThread
คุณมีกลไกหลายอย่างสำหรับการจัดการเธรด
เธรดหลักของคุณอาจjoin()
เป็นสามเธรดแบบต่อเนื่องและจะไม่ดำเนินการต่อจนกว่าทั้งสามจะเสร็จสิ้น
สำรวจสถานะเธรดของเธรดที่เกิดในช่วงเวลา
ใส่ทั้งหมดของหัวข้อกลับกลายเป็นแยกต่างหากThreadGroup
และสำรวจactiveCount()
บนThreadGroup
และรอคอยที่จะได้รับ 0
ตั้งค่าอินเทอร์เฟซการโทรกลับหรือตัวฟังที่กำหนดเองสำหรับการสื่อสารระหว่างเธรด
ฉันแน่ใจว่ายังมีวิธีอื่นอีกมากมายที่ฉันยังพลาด
นี่คือวิธีแก้ปัญหาที่ง่ายสั้นเข้าใจง่ายและเหมาะกับฉัน ฉันต้องการวาดไปที่หน้าจอเมื่อเธรดอื่นสิ้นสุดลง แต่ทำไม่ได้เนื่องจากเธรดหลักมีการควบคุมหน้าจอ ดังนั้น:
(1) ฉันสร้างตัวแปรส่วนกลาง: boolean end1 = false;
เธรดตั้งค่าเป็นจริงเมื่อสิ้นสุด ที่หยิบขึ้นมาใน mainthread โดยลูป "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()
สามารถถูกแทนที่เพื่อดำเนินการประมวลผลพิเศษใด ๆ ที่จำเป็นต้องทำเมื่อ Executor สิ้นสุดลงอย่างสมบูรณ์
ซึ่งเป็นสิ่งที่เราต้องการ เราจะลบล้าง 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
}
Inside method informUiThatWeAreDone();
ทำทุกอย่างที่คุณต้องทำเมื่อเธรดทั้งหมดเสร็จสิ้นเช่นอัปเดต UI
หมายเหตุ:อย่าลืมใช้synchronized
วิธีการเนื่องจากคุณทำงานควบคู่กันไปและควรระวังอย่างยิ่งหากคุณตัดสินใจที่จะเรียกsynchronized
วิธีการจากsynchronized
วิธีอื่น! สิ่งนี้มักนำไปสู่การหยุดชะงัก
หวังว่านี่จะช่วยได้!
คุณยังสามารถใช้ SwingWorker ซึ่งมีการสนับสนุนการเปลี่ยนแปลงคุณสมบัติในตัว ดูaddPropertyChangeListener ()หรือget ()วิธีการสำหรับตัวอย่างตัวฟังการเปลี่ยนแปลงสถานะ
ดูเอกสาร Java สำหรับคลาสเธรด คุณสามารถตรวจสอบสถานะของเธรดได้ หากคุณใส่สามเธรดในตัวแปรสมาชิกเธรดทั้งสามจะอ่านสถานะของกันและกันได้
อย่างไรก็ตามคุณต้องระวังเล็กน้อยเพราะคุณอาจทำให้เกิดสภาวะการแข่งขันระหว่างเธรดได้ เพียงพยายามหลีกเลี่ยงตรรกะที่ซับซ้อนตามสถานะของเธรดอื่น ๆ หลีกเลี่ยงการเขียนหลายเธรดไปยังตัวแปรเดียวกันอย่างแน่นอน