13장. 스레드 & 네트워크(기초)
프로세스와 스레드
프로세스(Process)란?
프로세스는 실행 중인 프로그램입니다. 각 프로세스는 독립적인 메모리 공간을 가집니다.
프로세스의 특징
- 독립적인 메모리 공간: 각 프로세스는 별도의 메모리 영역을 가짐
- 프로세스 간 통신 어려움: 프로세스 간 데이터 공유가 어려움
- 컨텍스트 스위칭 비용 큼: 프로세스 전환 시 오버헤드가 큼
- 독립 실행: 한 프로세스의 오류가 다른 프로세스에 영향 없음
스레드(Thread)란?
스레드는 프로세스 내에서 실행되는 실행 단위입니다. 하나의 프로세스는 여러 스레드를 가질 수 있습니다.
스레드의 특징
- 공유 메모리: 같은 프로세스 내 스레드들이 메모리를 공유
- 경량: 프로세스보다 생성/전환 비용이 적음
- 동시 실행: 여러 스레드가 동시에 실행 가능
- 의존성: 한 스레드의 오류가 전체 프로세스에 영향
프로세스 vs 스레드
| 구분 | 프로세스 | 스레드 |
|---|---|---|
| 메모리 | 독립적인 메모리 공간 | 공유 메모리 공간 |
| 생성 비용 | 높음 | 낮음 |
| 통신 | 어려움 (IPC 필요) | 쉬움 (공유 메모리) |
| 독립성 | 높음 | 낮음 |
| 사용 시기 | 독립적인 작업 | 병렬 처리 |
멀티스레딩의 장점
- 응답성 향상: UI 스레드와 작업 스레드 분리
- 자원 활용: 멀티코어 CPU 활용
- 효율성: I/O 대기 시간 활용
- 동시성: 여러 작업 동시 처리
스레드 생성 방법
방법 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(); // 현재 스레드에서 실행 (새 스레드 생성 안 됨)
스레드 상태
스레드는 여러 상태를 가집니다:
- NEW: 생성되었지만 시작되지 않음
- RUNNABLE: 실행 가능한 상태
- BLOCKED: 모니터 락 대기
- WAITING: 다른 스레드의 작업 대기
- TIMED_WAITING: 시간 제한 대기
- 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
네트워크 통신 과정
- 서버: ServerSocket 생성 및 대기
- 클라이언트: Socket으로 서버에 연결
- 서버: 연결 수락 (accept)
- 양방향 통신: 데이터 송수신
- 연결 종료: 소켓 닫기
스레드와 네트워크 종합 예제
멀티스레드 서버
여러 클라이언트를 동시에 처리하는 서버입니다.
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();
}
}
}
연습 문제
스레드 생성
- Thread 클래스를 상속받아 1부터 10까지 출력하는 스레드를 만드세요.
Runnable 구현
- Runnable 인터페이스를 구현하여 "Hello"를 5번 출력하는 스레드를 만드세요.
동기화
- 공유 카운터를 동기화하여 여러 스레드가 안전하게 증가시키도록 하세요.
간단한 서버
- 클라이언트로부터 메시지를 받아서 응답하는 서버를 만드세요.
간단한 클라이언트
- 서버에 연결하여 메시지를 보내고 응답을 받는 클라이언트를 만드세요.
멀티스레드 서버
- 여러 클라이언트를 동시에 처리할 수 있는 서버를 만드세요.
다음 장 예고
다음 장에서는 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 |