TIL - 20251029

juni·2025년 10월 28일

TIL

목록 보기
167/316

1029 Java 중급으로의 도약: 스레드와 입출력(I/O)


✅ 1. 프로세스와 스레드 (Process and Thread)

  • 프로세스 (Process): 운영체제(OS)로부터 자원(메모리, CPU)을 할당받아 실행 중인 프로그램의 단위. 각 프로세스는 독립적인 메모리 공간을 가집니다. (e.g., Chrome 브라우저, IntelliJ IDEA)

  • 스레드 (Thread): 프로세스 내에서 작업을 수행하는 실행 흐름의 단위. 하나의 프로세스는 하나 이상의 스레드를 가질 수 있으며, 같은 프로세스 내의 스레드들은 메모리 자원을 공유합니다.

  • 멀티태스킹 (Multi-tasking): 여러 개의 프로세스가 동시에 실행되는 것.

  • 멀티스레딩 (Multi-threading): 하나의 프로세스 내에서 여러 개의 스레드가 동시에 실행되는 것.

    • 장점: 자원을 공유하므로 컨텍스트 스위칭(Context Switching) 비용이 적고, 응답성이 향상됩니다. (e.g., 채팅 프로그램에서 메시지를 보내는 스레드와 받는 스레드를 분리)
    • 단점: 자원을 공유하기 때문에, 여러 스레드가 동시에 같은 데이터에 접근할 때 동기화(Synchronization) 문제가 발생할 수 있습니다.

✅ 2. Java에서 스레드 생성하기

  • Java에서 새로운 실행 흐름(스레드)을 만드는 방법은 크게 두 가지입니다.

➕ 2-1. Thread 클래스 상속

  1. java.lang.Thread 클래스를 상속받는 새로운 클래스를 만듭니다.

  2. run() 메서드를 오버라이딩하여, 해당 스레드가 수행할 작업을 작성합니다.

  3. start() 메서드를 호출하여 스레드를 실행합니다. (run()을 직접 호출하면 안 됨!)

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("새로운 스레드가 실행됩니다.");
        }
    }
    
    // 실행
    MyThread t = new MyThread();
    t.start(); // OS에 스레드 생성을 요청하고, run() 메서드가 실행됨

➕ 2-2. Runnable 인터페이스 구현 (권장)

  1. java.lang.Runnable 인터페이스를 구현하는 클래스를 만듭니다.

  2. run() 메서드를 구현하여 스레드가 수행할 작업을 작성합니다.

  3. Thread 클래스의 생성자에 Runnable 구현 객체를 전달하여 Thread 객체를 생성합니다.

  4. start() 메서드를 호출하여 스레드를 실행합니다.

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable을 이용한 스레드가 실행됩니다.");
        }
    }
    
    // 실행
    Runnable r = new MyRunnable();
    Thread t = new Thread(r);
    t.start();
    
    // 람다 표현식으로 더 간결하게
    new Thread(() -> System.out.println("람다를 이용한 스레드")).start();
  • Runnable 구현이 더 권장되는가?: Java는 다중 상속을 지원하지 않으므로, Thread 클래스를 상속하면 다른 클래스를 상속할 수 없게 됩니다. Runnable 인터페이스를 구현하면 이러한 제약에서 자유로워져 더 유연한 설계가 가능합니다.

✅ 3. 스레드 동기화 (Thread Synchronization)

  • 임계 영역 (Critical Section): 여러 스레드가 동시에 접근해서는 안 되는 공유 자원(데이터)에 접근하는 코드 블록.
  • 동기화: 임계 영역에 오직 하나의 스레드만 접근할 수 있도록 제어하여, 데이터의 무결성을 보장하는 메커니즘입니다.

synchronized 키워드

  • Java에서 가장 간단하게 동기화를 구현하는 방법입니다.
  1. synchronized 메서드: 메서드 전체를 임계 영역으로 지정합니다. 이 메서드는 한 번에 하나의 스레드만 실행할 수 있습니다.

  2. synchronized 블록: 특정 객체(lock)에 대한 잠금(lock)을 획득한 스레드만 해당 코드 블록을 실행할 수 있도록 합니다. 메서드 전체가 아닌, 꼭 필요한 부분만 동기화하여 성능을 높일 수 있습니다.

    public class BankAccount {
        private int balance;
        private final Object lock = new Object();
    
        // synchronized 블록을 이용한 동기화
        public void deposit(int amount) {
            synchronized (lock) {
                balance += amount;
            }
        }
    }

✅ 4. Java I/O (Input/Output)

  • I/O란 프로그램이 외부(파일, 네트워크, 키보드 등)로부터 데이터를 읽고(Input), 외부로 데이터를 쓰는(Output) 모든 작업을 의미합니다.
  • Java의 I/O는 스트림(Stream)이라는 개념을 기반으로 합니다. 스트림은 데이터가 흐르는 단방향 통로와 같습니다.

➕ 스트림의 종류

구분바이트 스트림 (Byte Stream)문자 스트림 (Character Stream)
단위1 byte2 bytes (char)
최상위 클래스InputStream / OutputStreamReader / Writer
주요 용도이미지, 동영상, 실행 파일 등 모든 종류의 바이너리 데이터텍스트 데이터 (문자 인코딩 자동 처리)
하위 클래스 예시FileInputStream, FileOutputStreamFileReader, FileWriter

➕ 보조 스트림 (Decorator Stream)

  • 기본 스트림(e.g., FileInputStream)에 추가적인 기능을 덧붙여주는 스트림입니다. (데코레이터 패턴)
  • BufferedInputStream / BufferedReader: 내부에 버퍼(buffer)를 두어, 데이터를 한 번에 많이 읽어와 I/O 성능을 크게 향상시킵니다.
  • InputStreamReader / OutputStreamWriter: 바이트 스트림을 문자 스트림으로, 또는 그 반대로 변환해주는 "다리" 역할을 합니다.

➕ 파일 읽기 예시 (try-with-resources)

  • Java 7부터 도입된 try-with-resources 구문을 사용하면, finally 블록에서 close()를 명시적으로 호출하지 않아도 스트림이 자동으로 닫혀 코드가 매우 간결해집니다.

    // 파일에서 한 줄씩 텍스트 읽기
    String filePath = "myFile.txt";
    
    try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }

📌 요약

  • 멀티스레딩은 하나의 프로세스 내에서 여러 작업을 동시에 처리하여 응답성을 높이는 기술이며, 스레드 생성은 Runnable 인터페이스 구현 방식이 권장됩니다.
  • 여러 스레드가 공유 자원에 접근할 때 발생하는 데이터 충돌을 막기 위해, synchronized 키워드를 사용하여 동기화 처리를 해야 합니다.
  • Java의 I/O스트림이라는 단방향 통로를 통해 이루어지며, 데이터 종류에 따라 바이트 스트림문자 스트림으로 나뉩니다.
  • 보조 스트림(BufferedReader 등)을 사용하여 I/O 성능을 향상시킬 수 있으며, try-with-resources 구문을 통해 리소스를 안전하고 간결하게 관리해야 합니다.

0개의 댓글