JavaRush /Java Blog /Random-KO /스레드로 Java를 망칠 수는 없습니다: 4부 - Callable, Future 및 친구들
Viacheslav
레벨 3

스레드로 Java를 망칠 수는 없습니다: 4부 - Callable, Future 및 친구들

Random-KO 그룹에 게시되었습니다

소개

우리는 이미 첫 번째 부분 에서 스레드가 어떻게 생성되는지 살펴보았습니다 . 다시 기억합시다. 스레드로 Java를 망칠 수는 없습니다: 4부 - Callable, Future 및 친구들 - 1스레드는 Thread그 안에서 실행되는 것이므로 tutorialspoint java 온라인 컴파일러를run 사용하여 다음 코드를 실행해 보겠습니다.
public class HelloWorld {

    public static void main(String []args){
        Runnable task = () -> {
            System.out.println("Hello World");
        };
        new Thread(task).start();
    }
}
이것이 스레드에서 작업을 실행하는 유일한 옵션입니까?

java.util.concurrent.Callable

java.lang.Runnable 에는 형제가 있고 그의 이름은 java.util.concurrent.Callable 이며 그는 Java 1.5에서 태어났습니다. 차이점은 무엇입니까? 이 인터페이스의 JavaDoc을 자세히 살펴보면 와 달리 Runnable새 인터페이스는 call결과를 반환하는 메서드를 선언한다는 것을 알 수 있습니다. 또한 기본적으로 Exception이 발생합니다. 즉, 확인된 예외에 대한 블록을 작성할 필요가 없습니다 try-catch. 벌써 나쁘지 않죠? Runnable이제 대신 새로운 작업이 생겼 습니다.
Callable task = () -> {
	return "Hello, World!";
};
하지만 그걸로 무엇을 해야 할까요? 결과를 반환하는 스레드에서 실행되는 작업이 필요한 이유는 무엇입니까? 분명히, 우리는 미래에 수행될 행동의 결과를 미래에 받을 것으로 기대합니다. 영어로 미래 - 미래. 그리고 정확히 같은 이름을 가진 인터페이스가 있습니다.java.util.concurrent.Future

java.util.concurrent.Future

java.util.concurrent.Future 인터페이스는 결과를 얻기 위한 방법, 상태를 확인하기 위한 방법 등 미래에 결과를 얻을 계획인 작업에 대한 작업을 위한 API를 설명합니다. 우리는 java.util.concurrent.FutureTaskFuture 구현에 관심이 있습니다 . 즉 , 이것이 에서 실행될 것입니다 . 이 구현에서 흥미로운 점은 및 를 구현한다는 것입니다 . 이것은 스레드에서 작업을 수행하는 이전 모델과 새 모델(Java 1.5에 등장했다는 점에서 새로운 모델)의 일종의 어댑터라고 생각할 수 있습니다. 예는 다음과 같습니다. TaskFutureRunnable
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class HelloWorld {

    public static void main(String []args) throws Exception {
        Callable task = () -> {
            return "Hello, World!";
        };
        FutureTask<String> future = new FutureTask<>(task);
        new Thread(future).start();
        System.out.println(future.get());
    }
}
예제에서 볼 수 있듯이 방법을 사용하여 get문제의 결과를 얻습니다 task. (!)중요한, 메서드를 사용하여 결과를 얻는 순간 get실행이 동기식으로 이루어집니다. 여기서는 어떤 메커니즘이 사용될 것이라고 생각하시나요? 맞습니다. 동기화 블록이 없습니다. 따라서 JVisualVM에서 WAITING은monitor 또는 가 아닌 wait동일한 것으로 표시됩니다 park(메커니즘이 사용되므로 LockSupport).

기능적 인터페이스

다음으로 Java 1.8의 클래스에 대해 이야기할 것이므로 간략하게 소개하는 것이 도움이 될 것입니다. 다음 코드를 살펴보겠습니다.
Supplier<String> supplier = new Supplier<String>() {
	@Override
	public String get() {
		return "String";
	}
};
Consumer<String> consumer = new Consumer<String>() {
	@Override
	public void accept(String s) {
		System.out.println(s);
	}
};
Function<String, Integer> converter = new Function<String, Integer>() {
	@Override
	public Integer apply(String s) {
		return Integer.valueOf(s);
	}
};
불필요한 코드가 많죠? 선언된 각 클래스는 단일 기능을 수행하지만 이를 설명하기 위해 불필요한 보조 코드를 많이 사용합니다. 그리고 Java 개발자도 그렇게 생각했습니다. 따라서 그들은 일련의 "기능적 인터페이스"( @FunctionalInterface)를 도입했으며 이제 Java 자체가 중요한 것을 제외한 모든 것을 "생각"하기로 결정했습니다.
Supplier<String> supplier = () -> "String";
Consumer<String> consumer = s -> System.out.println(s);
Function<String, Integer> converter = s -> Integer.valueOf(s);
Supplier- 제공자. 매개변수는 없지만 무언가를 반환합니다. 즉, 무언가를 제공합니다. Consumer- 소비자. 이는 입력(매개변수 s)으로 무언가를 취하고 이를 사용하여 무언가를 수행합니다. 즉, 무언가를 소비합니다. 또 다른 기능이 있습니다. 이는 입력(매개변수 s)으로 무언가를 취하고, 무언가를 수행하고, 무언가를 반환합니다. 보시다시피 제네릭이 적극적으로 사용됩니다. 확실하지 않은 경우 기억해 두고 " Java의 제네릭 이론 또는 괄호를 실제로 사용하는 방법 "을 읽어 보세요.

완성 가능한 미래

시간이 지남에 따라 Java 1.8에는 CompletableFuture. 인터페이스를 구현합니다 . 이는 미래에 Future우리 인터페이스가 실행될 것이며 실행 하고 결과를 얻을 수 있음을 의미합니다. 그러나 그는 또한 일부 . 번역에서 그 목적은 이미 분명합니다. 그것은 일종의 계산의 특정 단계입니다. 주제에 대한 간략한 소개는 " CompletionStage 및 CompletableFuture 소개 " 개요에서 찾을 수 있습니다. 바로 요점을 살펴보겠습니다. 시작하는 데 도움이 되는 사용 가능한 정적 메서드 목록을 살펴보겠습니다. 사용 옵션은 다음과 같습니다. taskgetCompletionStage스레드로 Java를 망칠 수는 없습니다: 4부 - Callable, Future 및 친구들 - 2
import java.util.concurrent.CompletableFuture;
public class App {
    public static void main(String []args) throws Exception {
        // CompletableFuture уже содержащий результат
        CompletableFuture<String> completed;
        completed = CompletableFuture.completedFuture("Просто meaning");
        // CompletableFuture, запускающий (run) новый поток с Runnable, поэтому он Void
        CompletableFuture<Void> voidCompletableFuture;
        voidCompletableFuture = CompletableFuture.runAsync(() -> {
            System.out.println("run " + Thread.currentThread().getName());
        });
        // CompletableFuture, запускающий новый поток, результат которого возьмём у Supplier
        CompletableFuture<String> supplier;
        supplier = CompletableFuture.supplyAsync(() -> {
            System.out.println("supply " + Thread.currentThread().getName());
            return "Значение";
        });
    }
}
이 코드를 실행하면 생성이 CompletableFuture전체 체인의 출시를 의미한다는 것을 알 수 있습니다. 따라서 Java8의 SteamAPI와 일부 유사점이 있지만 이것이 이러한 접근 방식의 차이점입니다. 예를 들어:
List<String> array = Arrays.asList("one", "two");
Stream<String> stringStream = array.stream().map(value -> {
	System.out.println("Executed");
	return value.toUpperCase();
});
이는 Java 8 Stream Api의 예입니다(자세한 내용은 " 그림 및 예제의 Java 8 스트림 API 가이드 "에서 읽을 수 있음). 이 코드를 실행하면 Executed표시되지 않습니다. 즉, Java에서 스트림을 생성할 때 스트림은 즉시 시작되지 않고 값이 필요할 때까지 기다립니다. 그러나 CompletableFuture계산된 값을 요청할 때까지 기다리지 않고 즉시 실행을 위해 체인을 시작합니다. 나는 이것을 이해하는 것이 중요하다고 생각합니다. 따라서 CompletableFuture가 있습니다. 체인을 어떻게 만들 수 있으며 어떤 수단을 갖게 됩니까? 이전에 작성한 기능적 인터페이스에 대해 기억해 봅시다.
  • FunctionA를 취하고 B를 반환하는 함수( )가 있습니다. 이 함수에는 apply(적용)이라는 단일 메서드가 있습니다.
  • ConsumerA를 받아들이고 아무것도 반환하지 않는 소비자( )가 있습니다 ( Void ). (수락) 이라는 한 가지 방법만 있습니다 accept.
  • Runnable수락하거나 반환하지 않는 스레드에서 실행되는 코드가 있습니다 . (실행) 이라는 단일 메서드가 있습니다 run.
두 번째로 기억해야 할 점은 CompletalbeFuture작업 시 Runnable소비자와 기능을 사용한다는 것입니다. 이를 고려하면 CompletableFuture다음과 같이 할 수 있다는 것을 항상 기억할 수 있습니다.
public static void main(String []args) throws Exception {
        AtomicLong longValue = new AtomicLong(0);
        Runnable task = () -> longValue.set(new Date().getTime());
        Function<Long, Date> dateConverter = (longvalue) -> new Date(longvalue);
        Consumer<Date> printer = date -> {
            System.out.println(date);
            System.out.flush();
        };
        // CompletableFuture computation
        CompletableFuture.runAsync(task)
                         .thenApply((v) -> longValue.get())
                         .thenApply(dateConverter)
                         .thenAccept(printer);
}
메소드에는 버전이 thenRun있습니다 . _ 이는 이러한 단계가 새 스레드에서 실행된다는 것을 의미합니다. 특별한 풀에서 가져오기 때문에 새로운 흐름인지 오래된 흐름인지 미리 알 수 없습니다. 그것은 모두 작업이 얼마나 어려운지에 달려 있습니다. 이러한 방법 외에도 세 가지 흥미로운 가능성이 더 있습니다. 명확성을 위해 어딘가에서 메시지를 수신하고 시간이 걸리는 특정 서비스가 있다고 가정해 보겠습니다. thenApplythenAcceptAsync
public static class NewsService {
	public static String getMessage() {
		try {
			Thread.currentThread().sleep(3000);
			return "Message";
		} catch (InterruptedException e) {
			throw new IllegalStateException(e);
		}
	}
}
이제, 가 제공하는 다른 기능을 살펴보겠습니다 CompletableFuture. CompletableFuture결과 를 다른 결과와 결합할 수 있습니다 CompletableFuture.
Supplier newsSupplier = () -> NewsService.getMessage();

CompletableFuture<String> reader = CompletableFuture.supplyAsync(newsSupplier);
CompletableFuture.completedFuture("!!")
				 .thenCombine(reader, (a, b) -> b + a)
				 .thenAccept(result -> System.out.println(result))
				 .get();
기본적으로 스레드는 데몬 스레드이므로 명확성을 위해 get결과를 기다리는 데 사용한다는 점은 주목할 가치가 있습니다. 그리고 우리는 결합(결합)할 수 있을 뿐만 아니라 다음을 반환할 수도 있습니다 CompletableFuture:
CompletableFuture.completedFuture(2L)
				.thenCompose((val) -> CompletableFuture.completedFuture(val + 2))
                               .thenAccept(result -> System.out.println(result));
여기서는 간결성을 위해 방법이 사용되었다는 점에 주목하고 싶습니다 CompletableFuture.completedFuture. 이 메소드는 새 스레드를 생성하지 않으므로 체인의 나머지 부분은 호출된 동일한 스레드에서 실행됩니다 completedFuture. 방법도 있습니다 thenAcceptBoth. 와 매우 유사 accept하지만 thenAccept을 허용 하면 또 다른 +를 입력으로 허용합니다 consumer. 즉 , 하나가 아닌 2개의 소스를 입력으로 허용합니다. 이 단어에는 또 다른 흥미로운 가능성이 있습니다 . 이러한 메서드는 대안을 받아들이고 먼저 실행되는 메서드에서 실행됩니다 . 그리고 또 다른 흥미로운 기능인 오류 처리로 이 리뷰를 마무리하고 싶습니다 . thenAcceptBothCompletableStageBiConsumerconsumerEither스레드로 Java를 망칠 수는 없습니다: 4부 - Callable, Future 및 친구들 - 3CompletableStageCompletableStageCompletableFuture
CompletableFuture.completedFuture(2L)
				 .thenApply((a) -> {
					throw new IllegalStateException("error");
				 }).thenApply((a) -> 3L)
				 //.exceptionally(ex -> 0L)
				 .thenAccept(val -> System.out.println(val));
이 코드는 아무 작업도 수행하지 않습니다. 왜냐하면... 예외가 발생하고 아무 일도 일어나지 않습니다. 그러나 주석을 제거하면 exceptionally동작을 정의하게 됩니다. 이 주제에 대한 CompletableFuture다음 비디오를 시청하는 것이 좋습니다 . 내 소견으로는 이러한 비디오는 인터넷에서 가장 시각적인 비디오 중 일부입니다. 모든 것이 어떻게 작동하는지, 우리가 어떤 무기고를 가지고 있는지, 그리고 그것이 왜 필요한지 그들에게 명확하게 알려야 합니다.

결론

이제 계산된 계산을 검색하기 위해 스레드를 사용하는 방법이 명확해졌기를 바랍니다. 추가 자료: #비아체슬라프
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION