원래는 어제 했었어야 했는데...
너무 이해가 안돼서 하루 밀렸다.
동기 프로그래밍
하나의 작업이 완료될 때까지 대기 후 다음 작업을 실행
- 작업이 오래 걸릴 경우 전체가 지연됨
- CPU 효율이 낮음 (대기 시간 발생)
- 연속된 계산, 단순 로직에 적절함.
비동기 프로그래밍
여러 작업을 동시에 실행하고, 결과가 필요할 때만 기다림
- 빠른 응답 가능
- CPU 리소스를 최적으로 활용할 수 있음
- 네트워크 요청이나 데이터베이스 조회에 적절
Future<T>는 비동기 연산의 결과를 나중에 받을 수 있도록 하는 기능.
Future<Integer>futureResult = executor.submit(() -> {
Thread.sleep(2000);
return 42;
});
Future<Integer> futureResult
Future에 Integer가 담길 것이라는 뜻executor.submit(...)
Future로 돌려줌)() -> { ... }
| 멀티 스레드 | 스레드풀 | Future |
|---|---|---|
![]() | ![]() | ![]() |
| 은행 창구 | 은행원 교대 업무 | 은행 대기표 |
| 여러 개의 창구에서 손님들을 관리 | 일에 따라 스레드들을 활성화/비활성화 | 은행에서 번호표 받고 다른 일 하다가 돌아오는 것 |
대충 이런 걸까 싶다.
get() : 결과를 반환 (작업이 끝날 때까지 블로킹)
isDone() : 작업 완료 여부 확인
cancel(true/false) : 작업 취소
public class FutureExample {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
Thread.sleep(2000);
return 42;
});
System.out.println("✅ 작업 수행 중...");
System.out.println("🍀 결과: " + future.get());
executor.shutdown();
}
}
CompletableFuture가 생긴 이유
아 방금 배웠는데 왜 한계가 있는 거야😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁😁
get() 메소드가 블로킹
비동기 작업의 결과를 가져오기 전에 get()을 호출하면, 작업이 끝날 때까지 기다려야 함
Future는 콜백 callback 체이닝을 지원하지 않음
Future는 thenApply()와 같은 체이닝 기능이 없음여러 개의 Future를 조합하기 어려움
Future는 병렬 실행된 여러 개의 작업을 조합하는 기능이 부족
Future의 확장 버전, 비동기 작업을 더 쉽게 다룰 수 있도록 개선된 클래스
Future의 경우에는 단순한 비동기 실행(나중에 결과 주는 것)만 가능하지만, CompletableFuture는 체이닝 및 예외 처리까지 가능하다.
Future의 메소드를 공유하고, 이후 추가 작업하는 메소드
supplyAsync : 새로운 비동기 작업 실행 후 결과 반환
thenApply : 이전 결과를 반환
thenAccept : 이전 결과를 사용하지만 반환값 없음
thenCombine : 두 개의 CompletableFuture를 조합
exceptionally : 예외 발생 시 대체값 반환
import java.util.concurrent.CompletableFuture;
public class CompletableFutureExample {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
System.out.println("🚀 비동기 작업: " + Thread.currentThread().getName());
try { Thread.sleep(2000); } catch (InterruptedException e) {}
return 42;
})
.thenApply(result -> result * 2)
.thenAccept(result -> System.out.println("🍀 최종 결과: " + result + " / 스레드: " + Thread.currentThread().getName()));
System.out.println("🧵 메인 스레드: " + Thread.currentThread().getName());
try { Thread.sleep(3000); } catch (InterruptedException e) {}
}
}
기존의 예제로는 비동기 작업이 잘 보이지 않아서 스레드의 이름을 바꿔보았다.
이를 실행할 경우,
🚀 비동기 작업: ForkJoinPool.commonPool-worker-1
🧵 메인 스레드: main
🍀 최종 결과: 84 / 스레드: ForkJoinPool.commonPool-worker-1
CompletableFuture.supplyAsync(() -> { ... }에 묶여있던 작업들은ForkJoinPool.commonPool-worker-1스레드로 출력되고,
메인 스레드에서의 작업은 main으로 출력되는 것을 볼 수 있었다.
public class CompletableFutureExceptionHandling {
public static void main(String[] args) {
CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("❌ 오류 발생!");
})
.exceptionally(e -> {
System.out.println("▲ 예외 발생: " + e.getMessage());
return -1; // 에러가 났을 때 대신 돌려주는 값!
})
.thenAccept(result -> System.out.println("✅ 최종 결과: " + result));
}
}
솔직히 이해는 못했지만 일단 적어놨다.
손이 외우길 빌어야겠다.
import java.util.*;
public class Lotto {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("[로또 당첨 프로그램]");
System.out.print("로또 개수를 입력해 주세요.(숫자 1 ~ 10): ");
int ticketCount = scan.nextInt();
List<Set<Integer>> tickets = new ArrayList<>();
for (int i = 0; i < ticketCount; i++) {
tickets.add(makeLotto());
}
char mark = 'A';
for (Set<Integer> ticket : tickets) {
System.out.print(mark++ + " ");
print(ticket);
}
Set<Integer> winNums = makeLotto();
System.out.println("\n[로또 발표]");
print(winNums);
System.out.println("\n[내 로또 결과]");
mark = 'A';
for (Set<Integer> ticket : tickets) {
int match = 0;
for (int n : ticket) {
if (winNums.contains(n)) match++;
}
System.out.print(mark++ + " ");
print(ticket);
System.out.println(" => " + match + "개 일치");
}
scan.close();
}
private static Set<Integer> makeLotto() {
Set<Integer> nums = new TreeSet<>();
Random r = new Random();
while (nums.size() < 6) {
nums.add(r.nextInt(45) + 1);
}
return nums;
}
private static void print(Set<Integer> nums) {
int cnt = 0;
for (int n : nums) {
System.out.print(n);
if (++cnt < nums.size()) System.out.print(",");
}
System.out.println();
}
}
import java.util.Scanner;
public class Tax {
// 구간 경계, 세율, 누진공제 배열(각 index가 동일 구간)
static int[] section = {12_000_000, 46_000_000, 88_000_000, 150_000_000, 300_000_000, 500_000_000, 1_000_000_000};
static double[] rate = {0.06, 0.15, 0.24, 0.35, 0.38, 0.40, 0.42, 0.45};
static int[] minus = {0, 1_080_000, 5_220_000, 14_900_000, 19_400_000, 25_400_000, 35_400_000, 65_400_000};
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.println("[과세금액 계산 프로그램]");
System.out.print("연소득을 입력해 주세요.:");
int income = scan.nextInt();
int[] sectionAmount = new int[8];
double[] taxEach = new double[8];
int left = income;
int start = 0;
// 구간별 계산
for (int i = 0; i < section.length; i++) {
int thisRange = section[i] - start;
if (left > thisRange) {
sectionAmount[i] = thisRange;
left -= thisRange;
start = section[i];
} else {
sectionAmount[i] = left;
left = 0;
break;
}
}
if (left > 0) sectionAmount[section.length] = left;
// 각 구간 세금 및 총 세금
double totalTax = 0;
for (int i = 0; i < sectionAmount.length; i++) {
taxEach[i] = sectionAmount[i] * rate[i];
if (sectionAmount[i] > 0)
System.out.printf("%d * %.0f%% = %.0f\n", sectionAmount[i], rate[i] * 100, taxEach[i]);
totalTax += taxEach[i];
}
System.out.printf("\n[세율에 의한 세금]:\t\t%.0f\n", totalTax);
// 누진공제 계산
int idx = 0;
int[] cut = {12_000_000, 46_000_000, 88_000_000, 150_000_000, 300_000_000, 500_000_000, 1_000_000_000};
while (idx < cut.length && income > cut[idx]) idx++;
double minusTax = income * rate[idx] - minus[idx];
System.out.printf("[누진공제 계산에 의한 세금]:\t%.0f\n", minusTax);
scan.close();
}
}
일단 메모만...