Pertanyaan
Bagaimana Anda membuat background loader yang tepat di Java 8? Ketentuan:
- data harus dimuat di latar belakang
- setelah memuat data harus ditampilkan
- sementara data dimuat, tidak ada permintaan lebih lanjut harus diterima
- jika ada permintaan saat data dimuat, pemuatan lain harus dijadwalkan setelah batas waktu tertentu (mis. 5 detik)
Tujuannya adalah mis. Agar permintaan memuat ulang diterima, tetapi database tidak dibanjiri dengan permintaan.
MCVE
Inilah MCVE. Ini terdiri dari tugas latar belakang yang mensimulasikan pemuatan dengan hanya mengaktifkan Thread.sleep selama 2 detik. Tugas dijadwalkan setiap detik yang secara alami mengarah pada tumpang tindih tugas pemuatan latar belakang, yang harus dihindari.
public class LoadInBackgroundExample {
/**
* A simple background task which should perform the data loading operation. In this minimal example it simply invokes Thread.sleep
*/
public static class BackgroundTask implements Runnable {
private int id;
public BackgroundTask(int id) {
this.id = id;
}
/**
* Sleep for a given amount of time to simulate loading.
*/
@Override
public void run() {
try {
System.out.println("Start #" + id + ": " + Thread.currentThread());
long sleepTime = 2000;
Thread.sleep( sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Finish #" + id + ": " + Thread.currentThread());
}
}
}
/**
* CompletableFuture which simulates loading and showing data.
* @param taskId Identifier of the current task
*/
public static void loadInBackground( int taskId) {
// create the loading task
BackgroundTask backgroundTask = new BackgroundTask( taskId);
// "load" the data asynchronously
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(new Supplier<String>() {
@Override
public String get() {
CompletableFuture<Void> future = CompletableFuture.runAsync(backgroundTask);
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return "task " + backgroundTask.id;
}
});
// display the data after they are loaded
CompletableFuture<Void> future = completableFuture.thenAccept(x -> {
System.out.println( "Background task finished:" + x);
});
}
public static void main(String[] args) {
// runnable which invokes the background loader every second
Runnable trigger = new Runnable() {
int taskId = 0;
public void run() {
loadInBackground( taskId++);
}
};
// create scheduler
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> beeperHandle = scheduler.scheduleAtFixedRate(trigger, 0, 1, TimeUnit.SECONDS);
// cancel the scheudler and the application after 10 seconds
scheduler.schedule(() -> beeperHandle.cancel(true), 10, TimeUnit.SECONDS);
try {
beeperHandle.get();
} catch (Throwable th) {
}
System.out.println( "Cancelled");
System.exit(0);
}
}
Outputnya adalah ini:
Start #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Start #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Finish #0: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 0
Finish #1: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 1
Start #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #2: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 2
Start #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Start #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #3: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Background task finished:task 3
Start #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #4: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 4
Finish #5: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 5
Start #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #6: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Start #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 6
Start #9: Thread[ForkJoinPool.commonPool-worker-4,5,main]
Finish #7: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Background task finished:task 7
Start #10: Thread[ForkJoinPool.commonPool-worker-2,5,main]
Finish #8: Thread[ForkJoinPool.commonPool-worker-6,5,main]
Background task finished:task 8
Cancelled
Tujuannya adalah agar mis. # 1 dan # 2 dilewati karena # 0 masih berjalan.
Masalah
Di mana Anda mengatur mekanisme pemblokiran dengan benar? Haruskah sinkronisasi digunakan? Atau beberapa AtomicBoolean
? Dan jika demikian, haruskah itu di dalam get()
metode atau tempat lain?
sumber
ExecutorService
dengan ukuran kumpulan utas 1?BlockingQueue
?Jawaban:
Anda sudah memiliki threadpool untuk menjalankan tugas. Itu belum tentu dan membuat hal menjadi rumit untuk menjalankan tugas di pelaksana async lain (
ForkJoinPool
saat Anda menggunakanCompletableFuture
)Sederhanakan:
ScheduledExecutorService akan memastikan hanya satu tugas yang dijalankan pada saat Anda memintanya dengan scheduleAtFixedRate
sumber
Mengambil yang berikut sebagai persyaratan:
Solusi dapat dibangun berdasarkan
Executors.newSingleThreadExecutor()
,CompletableFuture
danLinkedBlockingQueue
:Setelah eksekusi, stdout akan memiliki output berikut:
sumber
Saya telah menambahkan AtomicInteger yang akan bertindak sebagai penghitung untuk menjalankan tugas dengan metode kunci sederhana () dan membuka kunci () dengan perubahan kecil ini ke kode asli Anda, saya mendapat hasil:
Inilah solusi saya untuk tugas Anda:
MEMPERBARUI
Saya telah mengubah metode kunci () dan membuka kunci () ke bentuk yang lebih sederhana:
sumber
Jika mengerti, Anda memiliki beberapa tugas di latar belakang secara bersamaan. karena tugas-tugas ini melakukan pekerjaan yang sama persis Anda tidak ingin menjalankannya secara paralel, Anda perlu satu tugas untuk menyelesaikan pekerjaan dan membagikan hasilnya kepada orang lain. Jadi, jika Anda mendapatkan 10
CompletableFuture
secara bersamaan, Anda ingin salah satu dari mereka memanggil 'reload' ke db dan membagikan hasil eksekusi kepada orang lain dengan cara yang semuaCompletableFuture
akan diselesaikan secara normal dengan hasil. Saya menganggap ini daridan
jika tebakan saya benar, Anda dapat mencoba solusi saya.
Saya memiliki semacam hubungan orangtua-anak di antara tugas-tugas. tugas orang tua adalah yang benar-benar melakukan tugasnya dan membagikan hasil kepada anak-anaknya. tugas anak adalah tugas yang ditambahkan saat tugas orang tua masih dieksekusi, tugas anak menunggu sampai tugas orang tua menyelesaikan eksekusi. Karena hasil tugas orang tua masih 'segar' mereka disalin ke setiap anak dan mereka semua menyelesaikan masa depan mereka.
Dan inilah hasilnya:
sumber
jika Anda hanya ingin satu utas pengaksesan yang disinkronkan sederhana akan melakukan pekerjaan ...
keluaran:
kode:
sumber
Saya mencoba solusi menggunakan saklar ganda Thread, lihat kelas
BackgroundTaskDualSwitch
, itu mensimulasikan memuat menggunakanCompletableFuture
. Idenya adalah membiarkan tugas kedua menunggu sampai tugas yang sedang berjalan selesai, lihat perubahanBackgroundTask
. Ini memastikan bahwa satu tugas Max berjalan dan max satu tugas Thread menunggu. Permintaan lebih lanjut dilewati sampai tugas yang berjalan selesai dan menjadi 'bebas' untuk menangani permintaan berikutnya.Output adalah:
sumber
Utas pertama yang mulai melakukan pekerjaan mahal akan memberi tahu dengan panggilan balik hasilnya. Utas lain yang mencoba menjalankannya akan terdaftar di ExpensiveWork.notificables, jadi setelah pekerjaan mahal selesai utas yang melakukan pekerjaan akan memberi tahu mereka.
Sementara itu, utas sedang memeriksa hasilnya setiap 5 detik.
Dan inilah hasilnya:
sumber