Java Day13

YDC·2025년 6월 23일

우테코8기

목록 보기
13/23

Day 12 복습

static void vs void의 차이

구분설명예시
static void클래스 자체에서 호출 가능한 메서드
객체를 만들지 않아도 호출 가능
public static void main(String[] args)
Math.max(3, 5)
void인스턴스(객체)를 생성해야 호출 가능
각 객체의 상태에 따라 동작
TodoService ts = new TodoService();
ts.printAll();

학습 목표

1.백준 3문제

  • 15552 빠른 A+B
  • 11021 A+B-7
  • 11022 A+b-8

2.cs공부 (프로세스 동기화)

  • 핵심 개념
  • Java 예제 실습

백준 3문제

1.15552 빠른 A+B

InputStreamReader: 바이트 → 문자로 변환
StringTokenizer(line): 공백 기준으로 문자열 나누기
nextToken() 순서대로 하나씩 꺼냄

BufferWriter란?

Java에서 기본 System.out.println()은 출력할 때마다 콘솔에 즉시 출력
반복문 안에서 수천 번 호출되면 시간 초과로 이어질 수 있음 그래서 버퍼에 모아뒀다가 한 번에 출력하는 방식
줄바꿈은 직접 \n 써야함

BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));

bw.write("출력할 내용");
bw.newLine(); // 줄바꿈 또는 \n

bw.flush(); // 버퍼 비우기 (강제 출력)
bw.close(); // 자원 정리

메서드 설명

flush() 버퍼에 쌓인 데이터를 강제로 출력. 출력은 하지만 스트림은 닫지 않음
close() 버퍼 출력 + 스트림 종료 (더 이상 쓸 수 없음)

왜OutputStreamWriter가 들어가냐?

System.out은 바이트 스트림
BufferedWriter는 문자 스트림
중간에 OutputStreamWriter로 연결해줘야 함

버퍼(Buffer)란?

데이터를 임시로 저장해두는 중간 저장 공간
CPU ↔ 메모리 ↔ 입출력 장치 사이에서 속도 차이를 줄이기 위해 존재

br.readLine()은 예외 발생 가능 → throws IOException 필요

방식작동 원리속도버퍼 사용 여부
Scanner / System.out.println()입력/출력 시 즉시 처리느림❌ 없음
BufferedReader / BufferedWriter데이터를 모았다가 한 번에 처리빠름✅ 있음

문제

본격적으로 for문 문제를 풀기 전에 주의해야 할 점이 있다. 입출력 방식이 느리면 여러 줄을 입력받거나 출력할 때 시간초과가 날 수 있다는 점이다.
Java를 사용하고 있다면, Scanner와 System.out.println 대신 BufferedReader와 BufferedWriter를 사용할 수 있다. BufferedWriter.flush는 맨 마지막에 한 번만 하면 된다.

입력

첫 줄에 테스트케이스의 개수 T가 주어진다. T는 최대 1,000,000이다. 다음 T줄에는 각각 두 정수 A와 B가 주어진다. A와 B는 1 이상, 1,000 이하이다.

출력

각 테스트케이스마다 A+B를 한 줄에 하나씩 순서대로 출력한다.

예제 입력 1

5
1 1
12 34
5 500
40 60
1000 1000

예제 출력 1

2
46
505
100
2000

풀이

package loop;

import java.io.*;
import java.util.StringTokenizer;

public class BOJ_15552_FastAPlusB {
    public static void main(String[] args)throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        long t = Long.parseLong(br.readLine());
        for (int i = 0; i < t; i++) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            bw.write((a+b)+"\n");
        }
        bw.flush();
        bw.close();
    }
}

기본 Scanner가 아닌 BufferedReader 사용 경험 빠른 입력 처리 → 온라인 저지 환경의 I/O 속도 제한 이해

2.11021 A+B-7

문제

두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다.
각 테스트 케이스는 한 줄로 이루어져 있으며, 각 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

각 테스트 케이스마다 "Case #x: "를 출력한 다음, A+B를 출력한다. 테스트 케이스 번호는 1부터 시작한다.

예제 입력 1

5
1 1
2 3
3 4
9 8
5 2

예제 출력 1

Case #1: 2
Case #2: 5
Case #3: 7
Case #4: 17
Case #5: 7

풀이

package loop;
import java.util.Scanner;

public class BOJ_11021_FastAPlusB2 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        for (int i = 0; i < t; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            System.out.println("Case #"+(i+1)+": "+(a + b));
        }
        sc.close();
    }
}

A+B의 스캐너 판 문자열 출력만 복잡해짐

3.11022 A+B-8

문제

두 정수 A와 B를 입력받은 다음, A+B를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 T가 주어진다.
각 테스트 케이스는 한 줄로 이루어져 있으며, 각 줄에 A와 B가 주어진다. (0 < A, B < 10)

출력

각 테스트 케이스마다 "Case #x: A + B = C" 형식으로 출력한다. x는 테스트 케이스 번호이고 1부터 시작하며, C는 A+B이다.

예제 입력 1

5
1 1
2 3
3 4
9 8
5 2

예제 출력 1

Case #1: 1 + 1 = 2
Case #2: 2 + 3 = 5
Case #3: 3 + 4 = 7
Case #4: 9 + 8 = 17
Case #5: 5 + 2 = 7

풀이

package loop;
import java.util.Scanner;

public class BOJ_11022_FastAPlusB3 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        for (int i = 0; i < t; i++) {
            int a = sc.nextInt();
            int b = sc.nextInt();
            System.out.println("Case #"+(i+1)+": "+ a + " + " + b + " = " + (a + b));
        }
        sc.close();
    }
}

A+B-7 보다 출혁하는 문자열이 더 복잡해짐

Cs공부

1.프로세스 동기화

프로세스 동기화란?

여러 프로세스 또는 스레드가 동시에 공유 자원에 접근할때,
예상치 못한 충돌이나 데이터 오류를 방지하기위해 접근 순서를 조정하는것

왜 필요한가?

멀티태스킹 환경에서 여러개의 프로세스 또는 스레드가 같은 메모리 공간(공유자원)을 건드릴 수 있음

예시

프로세스 A: 잔액 읽기 → 1000
프로세스 B: 잔액 읽기 → 1000

A: 1000 - 500 = 500 → 저장
B: 1000 - 300 = 700 → 저장 ←❌ 결과는 700, A의 500이 덮임!
데이터의 일관성 파괴

핵심 개념

1.임계 영역(Critical Section)

둘 이상의 프로세스*(또는 스레드)가 접근하면 안되는 코드 영역
공유 변수조작,파일 쓰기,은행 잔액 처리등
동기화의 핵심 대상

2.상호배제(Mutual Exclusion)

동시에 둘 이상이 임계 영역에 접근하지 못하게 하는 제어 기법
하나가 들어가있으면 하나는 기다려야함
Java에선 synchronized,Lock등으로 구현
대표 알고리즘 Peterson, Dekker, Bakery

3.진행조건(Progress)

임계영역에 아무도 없다면 들어가려는 프로세스중 하나는 반드시 진입해야함
특정 프로세스가 진입하지 못하는 상황을 막는 조건
프로세스간 양보/선택 알고리즘이 공정하게 작동해야함

4.유한 대기(Bounded Wating)

어떤 프로세스도 무한히 기다리게해선 안됨
기다리면 언젠간 들어가야함
특정 프로세스가 계속 우선순위를 가져버리면 유한대기 조건 위반

5.교착 상태(Deadlock)

서로 자원을 기다리다가 아무것도 못하는 상태
A는B가 점유한 자원 필요, B는A의 자원을 기다림
자원 회수 불가능 + 상호 대기 발생 -> 시스템 멈춤

6.세마포어(semiprhore)

자원 접근을 제한하는 정수형 변수
p() acquire 자원 감소
v() release 자원 증가
0이면 기다리고 양수면 들어감

7.뮤텍스(Mutex)

상호 배제를 구현하는 간단한 도구 (하나만 접근 허용)
Java에선 synchronized,ReentrantLock등으로 구현
스레드가 자원 점유 끝나면 반드시 반납

8.모니터(Monitor)

동기화된 코드와 데이터를 포괄하는 고급 개념
Java에선 객체단위로 동기화 가능
Sychronized method/block 모니터 진입 퇴장처럼 동작
하나의 모니터에 동시에 하나만 들어올 수 있음

2.Java 예제 실습

1.임계영역+상호 배제 실습

Synchronized vs ReentrantLock

항목 설명
synchronized 간단하게 사용 가능하지만, 자동 잠금/해제만 제공
ReentrantLock 수동으로 lock/unlock, 더 많은 기능 제공 (tryLock, timeout 등)

기능synchronizedReentrantLock
기본 락/언락자동수동 (lock() / unlock()) 직접 호출
락 상태 확인❌ 불가isLocked(), isHeldByCurrentThread()
타임아웃 락❌ 불가tryLock(timeout) 지원
조건 변수❌ 없음Condition 객체로 세밀한 제어 가능
공정성 설정❌ 없음new ReentrantLock(true)로 FIFO 설정 가능

Thread.currentThread().getName() 현재 작동중인 쓰레드의 이름을 가져옴
currentTread() - 현재 작동중인 쓰레드
.getName() - 이름을 가져옴

Thread.sleep(100) -> 일부러 쓰레드에 멈춤을 줌 (동기화 없을때 오류 야기)

Runnable 스레드가 할일을 정의하는 인터페이스

상호 배재가 없기때문에 스레드 a와 b가 동시에 임계영역에 들어옴

잔액에서 700원을 배서 300원밖에 없는데 또 700을 빼야 함 오류 발생

코드
class Account {
    private int balance = 1000;

    public void withdraw(int amount) {
        if (balance >= amount) {
            System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 출금 완료. 잔액: " + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 출금 실패. 잔액 부족");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Account account = new Account();

        Runnable task = () -> account.withdraw(700);

        Thread t1 = new Thread(task, "🧵스레드 A");
        Thread t2 = new Thread(task, "🧵스레드 B");

        t1.start();
        t2.start();
    }
}
출력

🧵스레드 A 출금 시도: 700
🧵스레드 B 출금 시도: 700
🧵스레드 A 출금 완료. 잔액: 300
🧵스레드 B 출금 완료. 잔액: -400

Synchronized로 동기화 적용

임계영역을 보호해줌 한번에 1개의 스레드만 들어올 수 있어서

700원이 빠진 300원으로 스레드 b 진입 잔액 부족 -

코드
class SyncAccount {
    private int balance = 1000;

    public synchronized void withdraw(int amount) {
        if (balance >= amount) {
            System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
            try { Thread.sleep(100); } catch (InterruptedException e) {}
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 출금 완료. 잔액: " + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 출금 실패. 잔액 부족");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        SyncAccount account = new SyncAccount();

        Runnable task = () -> account.withdraw(700);

        Thread t1 = new Thread(task, "🧵스레드 A");
        Thread t2 = new Thread(task, "🧵스레드 B");

        t1.start();
        t2.start();
    }
}
출력

🧵스레드 A 출금 시도: 700
🧵스레드 A 출금 완료. 잔액: 300
🧵스레드 B 출금 실패. 잔액 부족

2.ReentrantLock 동기화

락을 이용해서 메소드를 쓰고있으면 사용 불가능 끝나면 락 해제
Final을 사용해서 락 객체는 한번 만들면 변경 불가능

코드

import java.util.concurrent.locks.ReentrantLock;

class LockAccount {
    private int balance = 1000;
    private final ReentrantLock lock = new ReentrantLock(); // 🔒 락 객체 생성

    public void withdraw(int amount) {
        lock.lock(); // 🔐 락 획득
        try {
            if (balance >= amount) {
                System.out.println(Thread.currentThread().getName() + " 출금 시도: " + amount);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                balance -= amount;
                System.out.println(Thread.currentThread().getName() + " 출금 완료. 잔액: " + balance);
            } else {
                System.out.println(Thread.currentThread().getName() + " 출금 실패. 잔액 부족");
            }
        } finally {
            lock.unlock(); // 🔓 락 반드시 해제 (예외 상황 방지)
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        LockAccount account = new LockAccount();

        Runnable task = () -> account.withdraw(700);

        Thread t1 = new Thread(task, "🔐 스레드 A");
        Thread t2 = new Thread(task, "🔐 스레드 B");

        t1.start();
        t2.start();
    }
}

3.wait()/notify() 동기화

Puduce가 안되있으면 생산 하고 소비자를 깨움
소비가 안되있으면 생산을 멈추고 소비자를 깨움
서로 깨우고 재우고를 반복함

코드

class Buffer {
    private int data;
    private boolean hasData = false;

    public synchronized void produce(int value) {
        while (hasData) {
            try {
                wait(); // 버퍼가 차있으면 생산자는 대기
            } catch (InterruptedException e) {}
        }
        data = value;
        hasData = true;
        System.out.println("🔼 생산: " + data);
        notify(); // 소비자 1명 깨움
    }

    public synchronized void consume() {
        while (!hasData) {
            try {
                wait(); // 버퍼가 비었으면 소비자는 대기
            } catch (InterruptedException e) {}
        }
        System.out.println("🔽 소비: " + data);
        hasData = false;
        notify(); // 생산자 1명 깨움
    }
}

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();

        // 생산자 스레드
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                buffer.produce(i);
                try { Thread.sleep(200); } catch (InterruptedException e) {}
            }
        });

        // 소비자 스레드
        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                buffer.consume();
                try { Thread.sleep(300); } catch (InterruptedException e) {}
            }
        });

        producer.start();
        consumer.start();
    }
}

리뷰

Cs공부라는게 이렇게 어려운 거였군요 개념만 이해하는게 아니라 실습을 추가했는데
예시 코드에도 모르는게 잔뜩있고 핵심 개념들이 어떻게 작용하는지 알아야되고 알고리즘 …
난이도가 어렵다는거는 많이 발전하고 있다는 거겠죠? 일단 머리가 깨질거같습니다 …
내일은 동기화 실습 4단계 notifyAll을 쓸건데 queue?
이런게 나오니가 머리 아프더라고요..
내일 하겠습니다…
내일은 5단계 cs 알고리즘 학습까지 하고 java 제네릭 메서드/클래스 직접 정의하고 적용
Optional, Map, Set 등 컬렉션 고급 활용 실습 해보겠습니다 될까..?
아 모레 … 내일은 와이프 생일이라 데이트 입니다
음 그래도 발전하고 있어 멋있다 나!

profile
초심자

0개의 댓글