Pytanie
Jak utworzyć właściwy moduł ładujący w tle w Javie 8? Warunki:
- dane powinny być ładowane w tle
- po załadowaniu dane powinny zostać wyświetlone
- podczas ładowania danych nie należy przyjmować dalszych żądań
- jeśli podczas ładowania danych pojawiały się żądania, po upływie określonego czasu (np. 5 sekund) należy zaplanować kolejne ładowanie
Celem jest np. Zaakceptowanie żądań przeładowania, ale nie zalanie bazy danych żądaniami.
MCVE
Oto MCVE. Składa się z zadania w tle, które symuluje ładowanie, po prostu wywołując Thread.sleep na 2 sekundy. Zadanie jest planowane co sekundę, co oczywiście prowadzi do nakładania się zadań ładowania w tle, czego należy unikać.
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);
}
}
Dane wyjściowe są następujące:
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
Celem jest pominięcie np. Nr 1 i nr 2, ponieważ numer 0 wciąż działa.
Problem
Gdzie właściwie ustawiasz mechanizm blokujący? Czy należy korzystać z synchronizacji? A może trochę AtomicBoolean
? A jeśli tak, to czy powinno być w get()
metodzie, czy gdzie indziej?
java
multithreading
java-8
Roland
źródło
źródło
ExecutorService
z pulą wątków o rozmiarze 1?BlockingQueue
?Odpowiedzi:
Masz już pulę wątków, aby wykonać zadanie. Uruchomienie zadania w innym executorze asynchronicznym (
ForkJoinPool
gdy używaszCompletableFuture
) nie musi być skomplikowane.Uprość to:
Usługa ScheduledExecutorService zapewni uruchomienie tylko jednego zadania w czasie, gdy wywołano je za pomocą harmonogramuAtFixedRate
źródło
Przyjmując następujące wymagania:
Rozwiązanie ca należy budować w oparciu o
Executors.newSingleThreadExecutor()
,CompletableFuture
iLinkedBlockingQueue
:Po wykonaniu stdout będzie miał następujące dane wyjściowe:
źródło
Dodałem AtomicInteger, który będzie działał jako licznik do uruchamiania zadań za pomocą prostych metod lock () i unlock () z tą niewielką zmianą w twoim oryginalnym kodzie Mam wynik:
Oto moje rozwiązanie dla twojego zadania:
AKTUALIZACJA
Zmieniłem metody lock () i unlock () na prostszą formę:
źródło
Jeśli rozumiesz, masz kilka zadań jednocześnie w tle. ponieważ te zadania wykonują dokładnie to samo zadanie, nie chcesz wykonywać ich równolegle, potrzebujesz jednego zadania, aby zakończyć zadanie i udostępnić jego wyniki innym. Jeśli więc otrzymasz 10
CompletableFuture
jednocześnie, chcesz, aby jeden z nich wywołał „przeładuj” do bazy danych i udostępnił wyniki wykonania innym, w taki sposób, aby wszystkieCompletableFuture
zakończyły się normalnie z wynikiem. Zakładam to odi
jeśli moje domysły są prawidłowe, możesz wypróbować moje rozwiązanie.
Mam pewien rodzaj relacji rodzic-dziecko między zadaniami. zadanie nadrzędne jest tym, które naprawdę wykonuje swoją pracę i dzieli się wynikami z dziećmi. zadanie potomne jest zadaniem, które zostało dodane podczas wykonywania zadania nadrzędnego, zadanie potomne czeka na zakończenie zadania nadrzędnego. Ponieważ wyniki zadań rodziców są wciąż „świeże”, są kopiowane do każdego dziecka i wszystkie wypełniają swoją przyszłość.
A oto wynik:
źródło
jeśli chcesz mieć tylko jeden dostęp do wątku, prosta synchronizacja wykona zadanie ...
wynik:
kod:
źródło
Wypróbowałem rozwiązanie za pomocą podwójnego przełącznika wątku, zobacz klasę
BackgroundTaskDualSwitch
, symuluje ładowanie za pomocąCompletableFuture
. Chodzi o to, aby drugie zadanie czekało na zakończenie aktualnie uruchomionego zadania, patrz zmiana wBackgroundTask
. Zapewnia to, że wątek może uruchomić maksymalnie jedno zadanie, a wątek maksymalnie jedno zadanie czeka. Kolejne żądania są pomijane, aż uruchomione zadanie zostanie zakończone i stanie się „wolne”, aby obsłużyć następne żądanie.Dane wyjściowe to:
źródło
Pierwszy wątek, który zaczyna wykonywać kosztowną pracę, powiadomi oddzwonieniem o wyniku. Inne wątki, które próbują go wykonać, zostaną zarejestrowane w ExpensiveWork.notificables, więc gdy kosztowna praca zakończy pracę, wątek, który ją wykonał, powiadomi ich.
Tymczasem wątki sprawdzają wynik co 5 sekund.
A to jest wynik:
źródło