IT Developer/Java

Java 기초 <9. 멀티스레딩과 동시성 프로그래밍>

TEMA_ 2025. 3. 10. 09:58
반응형

자바 Java

9. 멀티스레딩과 동시성 프로그래밍

 

안녕하세요! 태마입니다.

Java 기초 강좌입니다.

 

강좌의 경우 

1. 주제 간단 정리

2. 상세 주제 정리

으로 이루어져 있습니다.

 

자바 Java

포스팅 시작하겠습니다 :)

1. 주제 간단 정리

 

1. 멀티스레딩(Multithreading) 개념

멀티스레딩하나의 프로그램 내에서 여러 개의 스레드(Thread)가 동시에 실행되는 기법입니다.
✔ 여러 작업을 동시에 처리할 수 있어 프로그램의 성능과 효율성이 향상됩니다.
스레드(Thread) 는 프로그램 내에서 실행되는 작은 작업 단위로, 멀티스레딩은 여러 스레드가 동시에 실행되는 환경을 의미합니다.

📌 멀티스레딩의 장점
자원 활용 극대화 → CPU를 여러 스레드가 번갈아가며 사용
응답성 향상 → 사용자 입력 대기 중 다른 작업을 수행할 수 있음
대기 시간 감소 → I/O 작업을 처리하는 동안 다른 작업을 동시에 수행 가능


 

2. 스레드(Thread)와 프로세스(Process)의 차이

  • 프로세스 : 실행 중인 프로그램 자체로, 각 프로세스는 독립된 메모리 공간을 가짐
  • 스레드 : 프로세스 내에서 실행되는 작은 작업 단위로, 같은 메모리 공간을 공유

스레드와 프로세스 비교

특징프로세스스레드

메모리 공간 독립적인 메모리 공간 같은 메모리 공간 공유
속도 느림 빠름
자원 독립적 프로세스 자원 공유

📌 스레드는 같은 프로세스 내에서 자원을 공유하므로 프로세스보다 빠르고 효율적


 

3. 스레드 생성 방법

Java에서는 Thread 클래스 또는 Runnable 인터페이스를 사용하여 스레드를 생성할 수 있습니다.

Thread 클래스 사용 예제

class MyThread extends Thread {
    public void run() {
        System.out.println("스레드 실행 중");
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();  // 스레드 실행
    }
}

📌 Thread 클래스는 run() 메서드를 오버라이드하여 스레드에서 실행할 코드를 작성


 

4. Runnable 인터페이스 사용 예제

Runnable 인터페이스를 구현하여 스레드를 생성할 수 있습니다.
Runnable을 사용하면 다중 상속을 지원하기 때문에 클래스 상속과 함께 사용할 수 있습니다.

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("스레드 실행 중");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable task = new MyRunnable();
        Thread thread = new Thread(task);
        thread.start();  // 스레드 실행
    }
}

📌 Runnable 인터페이스는 스레드의 작업을 정의하는 데 사용되며, Thread 클래스와 함께 실행

반응형

2. 상세 주제 정리

 

1. 동시성 프로그래밍(Concurrency)

동시성 프로그래밍은 여러 작업을 동시에 실행하는 방법입니다.
✔ **병렬 처리(Parallelism)**과 혼동할 수 있지만, 동시성은 여러 작업이 겹쳐서 진행되는 개념이며, 병렬 처리란 작업들이 진짜로 동시에 실행되는 것을 의미합니다.

동시성 프로그래밍 예제

public class ConcurrencyExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("작업 1");
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("작업 2");
            }
        });

        t1.start();  // 스레드 1 실행
        t2.start();  // 스레드 2 실행
    }
}
 

📌 동시성 프로그래밍을 사용하면 여러 작업을 동시에 처리할 수 있지만, 순서 보장이 어려움


 

2. Executor 프레임워크

Executor는 스레드 풀을 사용하여 스레드를 효율적으로 관리할 수 있는 Java의 동시성 처리 프레임워크입니다.
스레드 풀을 사용하면 스레드를 미리 생성하고 재사용하여 성능을 최적화할 수 있습니다.
ExecutorService 인터페이스를 사용하여 스레드를 제출하고 관리할 수 있습니다.

Executor 예제

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2); // 2개의 스레드 풀

        executor.submit(() -> {
            System.out.println("작업 1");
        });
        executor.submit(() -> {
            System.out.println("작업 2");
        });

        executor.shutdown();  // 스레드 풀 종료
    }
}

📌 **Executors.newFixedThreadPool(n)**은 n개의 스레드를 미리 생성하여 재사용합니다.


 

3. ExecutorService의 다양한 활용

ExecutorService는 스레드를 관리하고 다양한 작업을 처리할 수 있는 방법을 제공합니다.
submit() : 작업을 큐에 제출
invokeAll() : 여러 작업을 한 번에 실행하고 결과를 기다림
invokeAny() : 여러 작업 중 가장 먼저 완료된 작업의 결과를 반환

ExecutorService 활용 예제

import java.util.concurrent.*;

public class ExecutorServiceExample {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        Callable<String> task1 = () -> {
            Thread.sleep(1000);
            return "작업 1 완료";
        };
        Callable<String> task2 = () -> {
            Thread.sleep(500);
            return "작업 2 완료";
        };

        // 여러 작업을 동시에 처리하고 결과를 기다림
        List<Future<String>> results = executor.invokeAll(Arrays.asList(task1, task2));

        for (Future<String> result : results) {
            System.out.println(result.get());
        }

        executor.shutdown();
    }
}

📌 invokeAll()은 여러 작업을 동시에 실행하고, 모든 작업이 완료될 때까지 기다림.


 

4. 동기화(Synchronization) 및 경쟁 상태(Race Condition)

동시성 프로그래밍에서 여러 스레드가 같은 자원에 동시에 접근하면 경쟁 상태가 발생할 수 있습니다.
✔ 이를 해결하려면 **동기화(synchronization)**를 사용하여 한 번에 하나의 스레드만 자원에 접근할 수 있도록 해야 합니다.

동기화 예제

class Counter {
    private int count = 0;

    // synchronized 키워드로 메서드 동기화
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SyncExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();
        
        t1.join();
        t2.join();

        System.out.println("최종 카운트: " + counter.getCount()); // 2000 출력
    }
}

📌 synchronized 키워드를 사용하면 하나의 스레드만 해당 메서드를 실행하게 되어 경쟁 상태를 방지


지금까지 멀티스레딩과 동시성 프로그래밍에 대해 배웠어요!
👉 "이제 입출력 스트림과 파일 처리에 대해 배우고 싶다면?"
다음 회차에서 Java 입출력 스트림을 다뤄봅시다!

 

반응형