CompletableFuture i wykonanie asynchroniczne

0

Hej!

Próbuję ogarnąć CompletableFuture, ale nie mogę zrozumieć jednego aspektu, coś czuję, że nie rozumiem wykonania asynchronicznego.
Mam pewną bazę (in-memory), którą zapełniam przy startupie pewnymi danymi, przykładowo:

CREATE TABLE USERS (
  ID       INT PRIMARY KEY AUTO_INCREMENT,
  NAME     VARCHAR(255) NOT NULL,
  PASSWORD VARCHAR(255) NOT NULL,
  AGE      INT,
  ADMIN    BOOLEAN
);
INSERT INTO USERS (ID, NAME, PASSWORD, AGE, ADMIN) values (1, 'Armin', 'zwdTJIb', 70, false);

i pomyślałem, że zrobię to asynchronicznie, ponieważ web server też potrzebuje sekundy. Chciałem, aby te dwa elementy wykonywały się niezależnie od siebie, przyspieszyło by mi to procedurę uruchomienia wszystkiego (nie, żeby trwała długo czy coś, ale pobawić się też można).
Mam klasę initialize:

package com.burdzi0.rest;

import com.burdzi0.rest.model.User;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

public class Initialize {

	private EntityManagerFactory factory;

	public Initialize(EntityManagerFactory factory) {
		this.factory = factory;
	}

	protected void start() {
		EntityManager manager = factory.createEntityManager();
		manager.getTransaction().begin();
		Query q = manager.createNativeQuery(getSQLFileContent());
		q.executeUpdate();
		manager.persist(new User("XYZ", "PASSWORD", true, 999));
		manager.persist(new User("ABC", "PASSWORD1", true, 998));
		manager.getTransaction().commit();
		manager.close();
	}

	private String getSQLFileContent() {
		ClassLoader classLoader = getClass().getClassLoader();

		String sqlFileName = "sql/start.sql";
		Optional<Path> filePath = Optional.ofNullable(Paths.get(classLoader.getResource(sqlFileName).getPath()));

		StringBuilder stringBuilder = new StringBuilder();
		Path path = filePath.orElseThrow(IllegalStateException::new);

		try {
			Files.lines(path).forEach(stringBuilder::append);
		} catch (IOException e) {
			throw new IllegalStateException("Couldn't find sql startup file");
		}

		if (stringBuilder.toString().equals(""))
			throw new IllegalStateException("Couldn't load data from sql startup file");
		return stringBuilder.toString();
	}
}

Jak widać wywołuję kod sql z pliku i dodaję ręcznie dwa rekordy. Wywołuję kod następująco:

CompletableFuture.runAsync(() -> new Initialize(entityManagerFactory).start()).get();

ale kod ewidentnie nie zmienił swojego wykonania. Ciągle jest to zwykły kod synchroniczny - czas wystartowania servera i bazy oraz output (kolejność logów) są takie same.
Czytałem trochę, próbowałem się bawić tym CompletableFuture, ale czego bym nie zrobił ciągle jest to kod synchroniczny. Przykład:

CompletableFuture.runAsync(() -> {
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).get();
IntStream.range(0,1000).forEach(System.out::println);

najpierw zaczeka 10 sekund, a następnie dopiero wypisze pętlę. Po co w takim razie stworzono ten CompletableFuture, skoro nic kompletnie nie zmienia? Gdzie popełniam błąd w rozumieniu tego tworu?

0
Burdzi0 napisał(a):

Hej!

Próbuję ogarnąć CompletableFuture, ale nie mogę zrozumieć jednego aspektu, coś czuję, że nie rozumiem wykonania asynchronicznego.
Mam pewną bazę (in-memory), którą zapełniam przy startupie pewnymi danymi, przykładowo:

CREATE TABLE USERS (
  ID       INT PRIMARY KEY AUTO_INCREMENT,
  NAME     VARCHAR(255) NOT NULL,
  PASSWORD VARCHAR(255) NOT NULL,
  AGE      INT,
  ADMIN    BOOLEAN
);
INSERT INTO USERS (ID, NAME, PASSWORD, AGE, ADMIN) values (1, 'Armin', 'zwdTJIb', 70, false);

i pomyślałem, że zrobię to asynchronicznie, ponieważ web server też potrzebuje sekundy. Chciałem, aby te dwa elementy wykonywały się niezależnie od siebie, przyspieszyło by mi to procedurę uruchomienia wszystkiego (nie, żeby trwała długo czy coś, ale pobawić się też można).
Mam klasę initialize:

package com.burdzi0.rest;

import com.burdzi0.rest.model.User;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;

public class Initialize {

	private EntityManagerFactory factory;

	public Initialize(EntityManagerFactory factory) {
		this.factory = factory;
	}

	protected void start() {
		EntityManager manager = factory.createEntityManager();
		manager.getTransaction().begin();
		Query q = manager.createNativeQuery(getSQLFileContent());
		q.executeUpdate();
		manager.persist(new User("XYZ", "PASSWORD", true, 999));
		manager.persist(new User("ABC", "PASSWORD1", true, 998));
		manager.getTransaction().commit();
		manager.close();
	}

	private String getSQLFileContent() {
		ClassLoader classLoader = getClass().getClassLoader();

		String sqlFileName = "sql/start.sql";
		Optional<Path> filePath = Optional.ofNullable(Paths.get(classLoader.getResource(sqlFileName).getPath()));

		StringBuilder stringBuilder = new StringBuilder();
		Path path = filePath.orElseThrow(IllegalStateException::new);

		try {
			Files.lines(path).forEach(stringBuilder::append);
		} catch (IOException e) {
			throw new IllegalStateException("Couldn't find sql startup file");
		}

		if (stringBuilder.toString().equals(""))
			throw new IllegalStateException("Couldn't load data from sql startup file");
		return stringBuilder.toString();
	}
}

Jak widać wywołuję kod sql z pliku i dodaję ręcznie dwa rekordy. Wywołuję kod następująco:

CompletableFuture.runAsync(() -> new Initialize(entityManagerFactory).start()).get();

ale kod ewidentnie nie zmienił swojego wykonania. Ciągle jest to zwykły kod synchroniczny - czas wystartowania servera i bazy oraz output (kolejność logów) są takie same.
Czytałem trochę, próbowałem się bawić tym CompletableFuture, ale czego bym nie zrobił ciągle jest to kod synchroniczny. Przykład:

CompletableFuture.runAsync(() -> {
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).get();
IntStream.range(0,1000).forEach(System.out::println);

najpierw zaczeka 10 sekund, a następnie dopiero wypisze pętlę. Po co w takim razie stworzono ten CompletableFuture, skoro nic kompletnie nie zmienia? Gdzie popełniam błąd w rozumieniu tego tworu?

Nie znam wybitnie CompletableFuture jednak z API możesz się dowiedzieć, że get() czeka na wynik danego Future'a. Czyli pomimo tego, że odpalasz coś w osobnym wątku to i tak czekasz i blokujesz

0
Executors.newCachedThreadPool().submit(() -> CompletableFuture.runAsync(() -> new Initialize().start()).get());

lub

ExecutorService executor = Executors.newFixedThreadPool(4);
CompletableFuture future = CompletableFuture.runAsync(() -> new Initialize().start());
future.completeAsync(() -> null, executor);

get() czeka na wynik, odpalając w oddzielnej puli wątków czekanie odbędzie się w niej nie blokując głównego wątku

0

Jak wyżej po prostu nie powinno się używać get(). Dotyczy to zresztą wszystkich monad (i prawie monad). (Optional, Try, Either, Future itp).

1 użytkowników online, w tym zalogowanych: 0, gości: 1