본문 바로가기
  • 코딩, 허쌤이 떠먹여 줄게
BackEnd/Java

13장. 스레드 & 네트워크(기초)

by 허쌤 2026. 1. 3.

13장. 스레드 & 네트워크(기초)

프로세스와 스레드

프로세스(Process)란?

프로세스는 실행 중인 프로그램입니다. 각 프로세스는 독립적인 메모리 공간을 가집니다.

프로세스의 특징

  • 독립적인 메모리 공간: 각 프로세스는 별도의 메모리 영역을 가짐
  • 프로세스 간 통신 어려움: 프로세스 간 데이터 공유가 어려움
  • 컨텍스트 스위칭 비용 큼: 프로세스 전환 시 오버헤드가 큼
  • 독립 실행: 한 프로세스의 오류가 다른 프로세스에 영향 없음

스레드(Thread)란?

스레드는 프로세스 내에서 실행되는 실행 단위입니다. 하나의 프로세스는 여러 스레드를 가질 수 있습니다.

스레드의 특징

  • 공유 메모리: 같은 프로세스 내 스레드들이 메모리를 공유
  • 경량: 프로세스보다 생성/전환 비용이 적음
  • 동시 실행: 여러 스레드가 동시에 실행 가능
  • 의존성: 한 스레드의 오류가 전체 프로세스에 영향

프로세스 vs 스레드

구분 프로세스 스레드
메모리 독립적인 메모리 공간 공유 메모리 공간
생성 비용 높음 낮음
통신 어려움 (IPC 필요) 쉬움 (공유 메모리)
독립성 높음 낮음
사용 시기 독립적인 작업 병렬 처리

멀티스레딩의 장점

  1. 응답성 향상: UI 스레드와 작업 스레드 분리
  2. 자원 활용: 멀티코어 CPU 활용
  3. 효율성: I/O 대기 시간 활용
  4. 동시성: 여러 작업 동시 처리

스레드 생성 방법

방법 1: Thread 클래스 상속

Thread 클래스를 상속받아 run() 메서드를 오버라이딩합니다.

class MyThread extends Thread {
    @Override
    public void run() {
        // 스레드가 실행할 코드
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread: " + i);
        }
    }
}

// 사용
MyThread thread = new MyThread();
thread.start();  // 스레드 시작

방법 2: Runnable 인터페이스 구현 (권장)

Runnable 인터페이스를 구현하는 방법입니다. 더 유연하고 권장되는 방법입니다.

class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 스레드가 실행할 코드
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable: " + i);
        }
    }
}

// 사용
Thread thread = new Thread(new MyRunnable());
thread.start();

방법 3: 람다 표현식 (Java 8+)

간단한 경우 람다 표현식을 사용할 수 있습니다.

Thread thread = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
        System.out.println("Lambda: " + i);
    }
});
thread.start();

start() vs run()

  • start(): 새로운 스레드를 생성하고 run() 메서드를 실행
  • run(): 단순히 메서드를 호출 (새 스레드 생성 안 됨)
Thread thread = new Thread(() -> System.out.println("Hello"));

thread.start();  // 새 스레드에서 실행
thread.run();    // 현재 스레드에서 실행 (새 스레드 생성 안 됨)

스레드 상태

스레드는 여러 상태를 가집니다:

  1. NEW: 생성되었지만 시작되지 않음
  2. RUNNABLE: 실행 가능한 상태
  3. BLOCKED: 모니터 락 대기
  4. WAITING: 다른 스레드의 작업 대기
  5. TIMED_WAITING: 시간 제한 대기
  6. TERMINATED: 종료됨

스레드 제어 메서드

메서드 설명
start() 스레드 시작
run() 스레드가 실행할 코드
sleep(long ms) 지정된 시간만큼 대기
join() 스레드가 종료될 때까지 대기
interrupt() 스레드에 인터럽트 신호 전송
isAlive() 스레드가 살아있는지 확인

동기화 개념

동기화란?

동기화(Synchronization)는 여러 스레드가 공유 자원에 동시에 접근할 때 발생하는 문제를 해결하는 것입니다.

동기화가 필요한 이유

여러 스레드가 같은 데이터를 동시에 수정하면 데이터 불일치가 발생할 수 있습니다.

// 문제 예제: 동기화 없이 공유 변수 수정
class Counter {
    int count = 0;

    public void increment() {
        count++;  // 여러 스레드가 동시에 실행하면 문제 발생
    }
}

동기화 문제 예제

// 두 스레드가 동시에 count를 증가시키면
// 예상: count = 2
// 실제: count = 1 (데이터 손실 발생 가능)

synchronized 키워드

synchronized 키워드를 사용하여 동기화를 구현할 수 있습니다.

메서드 동기화

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

블록 동기화

class Counter {
    private int count = 0;
    private Object lock = new Object();

    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
}

동기화의 효과

  • 상호 배제: 한 번에 하나의 스레드만 실행
  • 데이터 일관성: 공유 데이터의 일관성 보장
  • 안전성: 경쟁 조건(race condition) 방지

동기화의 단점

  • 성능 저하: 스레드가 대기해야 함
  • 데드락 가능성: 서로 대기하는 상황 발생 가능
  • 복잡성 증가: 동기화 로직이 복잡해짐

네트워크 기초(Socket 개념)

네트워크 프로그래밍이란?

네트워크 프로그래밍은 네트워크를 통해 데이터를 주고받는 프로그램을 작성하는 것입니다.

Socket이란?

소켓(Socket)은 네트워크를 통해 데이터를 주고받기 위한 통신의 끝점(endpoint)입니다.

네트워크 통신 모델

클라이언트-서버 모델

┌─────────────┐         ┌─────────────┐
│  클라이언트   │ ────→   │   서버       │
│  (Client)    │         │  (Server)    │
│             │ ←────   │             │
└─────────────┘         └─────────────┘

TCP/IP 소켓

TCP/IP는 신뢰성 있는 연결 지향 통신 프로토콜입니다.

서버 소켓 (ServerSocket)

서버는 ServerSocket을 사용하여 클라이언트의 연결을 기다립니다.

import java.net.*;

// 서버 소켓 생성
ServerSocket serverSocket = new ServerSocket(8080);

// 클라이언트 연결 대기
Socket clientSocket = serverSocket.accept();

// 데이터 송수신
// ...

// 소켓 닫기
clientSocket.close();
serverSocket.close();

클라이언트 소켓 (Socket)

클라이언트는 Socket을 사용하여 서버에 연결합니다.

import java.net.*;

// 서버에 연결
Socket socket = new Socket("localhost", 8080);

// 데이터 송수신
// ...

// 소켓 닫기
socket.close();

데이터 송수신

출력 스트림 (데이터 전송)

import java.io.*;

// 소켓에서 출력 스트림 얻기
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os, true);

// 데이터 전송
pw.println("Hello, Server!");

입력 스트림 (데이터 수신)

import java.io.*;

// 소켓에서 입력 스트림 얻기
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(
    new InputStreamReader(is));

// 데이터 수신
String message = br.readLine();

간단한 서버 예제

import java.net.*;
import java.io.*;

public class SimpleServer {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("서버 시작: 포트 8080");

            Socket clientSocket = serverSocket.accept();
            System.out.println("클라이언트 연결됨");

            // 데이터 수신
            BufferedReader br = new BufferedReader(
                new InputStreamReader(clientSocket.getInputStream()));
            String message = br.readLine();
            System.out.println("받은 메시지: " + message);

            // 데이터 전송
            PrintWriter pw = new PrintWriter(
                clientSocket.getOutputStream(), true);
            pw.println("서버 응답: 메시지 받음");

            clientSocket.close();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

간단한 클라이언트 예제

import java.net.*;
import java.io.*;

public class SimpleClient {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 8080);
            System.out.println("서버에 연결됨");

            // 데이터 전송
            PrintWriter pw = new PrintWriter(
                socket.getOutputStream(), true);
            pw.println("Hello, Server!");

            // 데이터 수신
            BufferedReader br = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            String response = br.readLine();
            System.out.println("서버 응답: " + response);

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

포트(Port)란?

포트는 네트워크 통신에서 특정 프로세스를 식별하는 번호입니다.

  • 범위: 0 ~ 65535
  • 잘 알려진 포트: 0 ~ 1023 (HTTP: 80, HTTPS: 443, FTP: 21 등)
  • 사용자 포트: 1024 ~ 65535

네트워크 통신 과정

  1. 서버: ServerSocket 생성 및 대기
  2. 클라이언트: Socket으로 서버에 연결
  3. 서버: 연결 수락 (accept)
  4. 양방향 통신: 데이터 송수신
  5. 연결 종료: 소켓 닫기

스레드와 네트워크 종합 예제

멀티스레드 서버

여러 클라이언트를 동시에 처리하는 서버입니다.

import java.net.*;
import java.io.*;

public class MultiThreadServer {
    public static void main(String[] args) {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("서버 시작: 포트 8080");

            while (true) {
                Socket clientSocket = serverSocket.accept();
                System.out.println("클라이언트 연결: " + 
                    clientSocket.getInetAddress());

                // 각 클라이언트마다 새 스레드 생성
                Thread clientThread = new Thread(() -> {
                    handleClient(clientSocket);
                });
                clientThread.start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleClient(Socket socket) {
        try {
            BufferedReader br = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(
                socket.getOutputStream(), true);

            String message;
            while ((message = br.readLine()) != null) {
                System.out.println("받은 메시지: " + message);
                pw.println("에코: " + message);
            }

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

연습 문제

  1. 스레드 생성

    • Thread 클래스를 상속받아 1부터 10까지 출력하는 스레드를 만드세요.
  2. Runnable 구현

    • Runnable 인터페이스를 구현하여 "Hello"를 5번 출력하는 스레드를 만드세요.
  3. 동기화

    • 공유 카운터를 동기화하여 여러 스레드가 안전하게 증가시키도록 하세요.
  4. 간단한 서버

    • 클라이언트로부터 메시지를 받아서 응답하는 서버를 만드세요.
  5. 간단한 클라이언트

    • 서버에 연결하여 메시지를 보내고 응답을 받는 클라이언트를 만드세요.
  6. 멀티스레드 서버

    • 여러 클라이언트를 동시에 처리할 수 있는 서버를 만드세요.

다음 장 예고

다음 장에서는 Java의 고급 기능들과 실전 프로젝트를 통해 지금까지 배운 내용을 종합적으로 활용하는 방법을 학습합니다.

'BackEnd > Java' 카테고리의 다른 글

15장. 미니 프로젝트 & 실습  (0) 2026.01.04
Java와 데이터베이스 연동  (0) 2026.01.04
12장. 메모리 구조 & JVM 이해  (0) 2026.01.03
11장. 입출력(IO) & 파일 처리  (0) 2026.01.02
10장. 예외 처리  (0) 2026.01.02