정의
Caller : 호출하는 함수
Callee : 호출 당하는 함수
Functional Interface (함수형 인터페이스)
- 함수형 프로그래밍을 지원하기 위해서 Java 8부터 도입
- 1개의 추상 메소드를 갖고 있는 인터페이스
- 함수를 1급 객체로 사용할 수 있다.
- 함수를 변수에 할당
- 함수를 인자로 전달
- 함수를 반환값으로 사용
- Function, Consumer, Supplier, Runnable . . .
- 함수형 인터페이스를 구현한 익명 클래스를 람다식으로 변경 가능
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
- 함수형 인터페이스는 호출한 Thread에서 실행된다.
public static void main(String[] args) {
var consumer = getConsumer();
consumer.accept(1);
var consumerAsLambda = getConsumerAsLambda();
consumerAsLambda.accept(1);
handleConsumer(consumer);
}
public static Consumer<Integer> getConsumer() {
Consumer<Integer> returnValue = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
log.info("value in interface: {}", integer);
}
};
return returnValue;
}
public static Consumer<Integer> getConsumerAsLambda() {
return integer -> log.info("value in lambda: {}", integer);
}
public static void handleConsumer(Consumer<Integer> consumer) {
log.info("handleConsumer");
consumer.accept(1);
}
동기 & 비동기
동기
- caller는 callee의 결과에 관심이 있다.
- caller는 결과를 이용하여 다음 action을 수행한다.
- caller는 callee를 이용하기 때문에 동기화
@Slf4j
public class A {
public static void main(String[] args) {
log.info("Start main");
var result = getResult();
var nextValue = result + 1;
assert nextValue == 1;
log.info("Finish main");
}
public static int getResult() {
log.info("Start getResult");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
var result = 0;
try {
return result;
} finally {
log.info("Finish getResult");
}
}
}
비동기
- caller는 callee의 결과에 관심이 없다.
- callee는 결과를 이용하여 callback을 수행
- caller는 callee를 이용하지 않기 때문에 동기화 X
- getResult()는 결과를 이용해서 함수형 인터페이스 실행
@Slf4j
public class B {
public static void main(String[] args) {
log.info("Start main");
getResult(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
var nextValue = integer + 1;
assert nextValue == 1;
}
});
log.info("Finish main");
}
public static void getResult(Consumer<Integer> cb) {
log.info("Start getResult");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
var result = 0;
cb.accept(result);
log.info("Finish getResult");
}
}
Blocking & non-Blocking
Blocking (동기 Blocking / 비동기 Blocking)
- caller는 callee가 완료될 때까지 대기
- 제어권을 callee가 가짐 (완료의 주체)
- caller와 다른 별도의 thread가 필요 X (추가로 쓸 수도 있음)
- 방식의 차이는 존재
- A는 getResult의 결과를 돌려받기 전까지 아무것도 X (동기 Blocking)
- B는 getResult가 결과를 구하고, callback을 실행하기 전까지 아무것도 X (비동기 Blocking)
non-Blocking (동기 non-Blocking)
 |  |
|---|
- caller는 callee를 기다리지 않고, 본인의 일을 진행
- 제어권을 caller가 가짐 (본인의 일 진행 가능하므로)
- caller와 다른 별도의 thread가 필요 O (기다리지 않으려면 작업을 위임)
- 방식의 차이
- A는 getResult가 완료되기 전까지는 caller는 자신의 일 수행 X (동기 Blocking)
- C는 getResult가 완료되지 않더라도, caller는 자신의 일 수행 O (동기 non-Blocking)
- C의 케이스는 "동기 = Blocking" 에 대한 반례?이다.
- 동기는 결과 값을 기다리고, Blocking은 callee의 결과를 기다리니 개념상 비슷하게 이해될 수도 있지만, 반례가 존재한다.
- 동기 non-Blocking은 기다리면서 main Thread가 작동할 수 있다.
- main()
- getResult()을 호출하면 내부에서 선언한 Single Thread는 물리적으로 1000ms를 sleep() 한다.
- 하지만 main Thread와는 별개이기 때문에, 1000ms를 기다리는 동안 main Thread는 getResult()의 'result'가 return되기 전까지 while loop에서 100ms씩 10번을 카운팅 할 것이다.
- result가 return 된다면, 생성한 Single Thread를 shutdown하고, main도 작업을 마친다.
public static void main(String[] args) throws InterruptedException, ExecutionException {
log.info("Start main");
var count = 1;
Future<Integer> result = getResult();
while (!result.isDone()) {
log.info("Waiting for result, count: {}", count++);
Thread.sleep(100);
}
}
var nextValue = result.get() + 1;
assert nextValue == 1;
log.info("Finish main");
public static Future<Integer> getResult() {
var executor = Executors.newSingleThreadExecutor();
try {
return executor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
log.info("Start getResult");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
var result = 0;
try {
return result;
} finally {
log.info("Finish getResult");
}
}
});
} finally {
executor.shutdown();
}
}
non-Blocking (동기 non-Blocking)
- caller는 callee의 결과에 관심이 없다. callback 작업까지 알아서 수행하니까.
- getResult 호출 후, getResult가 완료되지 않더라도, main은 본인의 작업을 수행
- 비동기 non-Blocking
결론
- 동기 Blocking : caller 중단 / 결과를 얻고 직접처리
- 비동기 Blocking : caller 중단 / callee가 처리 (callback)
- 동기 non-Blocking : caller 할 일함 / 결과를 얻고 직접처리
- 비동기 non-Blocking : caller 할 일함 / callee가 처리 (callback)