본문 바로가기

TIPS

Java 멀티스레드(Thread) 기초 가이드

728x90
반응형
SMALL

 

이번 포스팅에서는 Java에서 스레드를 활용하는 방법을 배워보고

멀티스레드 프로그래밍을 이해하고, 실제 코드를 통해 실행 흐름과 성능 최적화 방법을 익혀보려 한다

 


 

1. 멀티스레드란?

 

싱글스레드 vs 멀티스레드

  • 싱글스레드: 한 번에 하나의 작업만 실행 (ex. StringBuilder)
  • 멀티스레드: 여러 작업을 동시에 실행 (ex. StringBuffer)

 

멀티스레드의 장점

  • CPU의 여러 코어를 활용하여 성능 최적화 가능
  • 입출력(IO) 작업을 비동기 처리하여 응답 속도 개선

 

멀티스레드의 단점

  • 동기화 문제 발생 가능 (synchronized 필요)
  • 자원 관리가 어려워질 수 있음 (ExecutorService 활용)

 


2. 예제 코드 분석

 

🏆 기본적인 멀티스레드 실행

 

public class ThreadExample {
    public static void main(String[] args) {
        /***********************************************************************/
        /***************************** 스레드 연습 ********************************/
        /***********************************************************************/
        
        System.out.println("작업 1 시작");
        Thread thread1 = new Thread(new Task());
        thread1.start(); // 새로운 스레드 실행

        System.out.println("작업 2 시작");
        Thread thread2 = new Thread(new Task());
        thread2.start(); // 또 다른 스레드 실행
        
        // 멀티스레드 최적화 코어 개수 설정
        int threadsForCPU = Runtime.getRuntime().availableProcessors() + 1;	// CPU 연산이 많은 경우
        int threadsForIO = (Runtime.getRuntime().availableProcessors() * 2) + 1;	// I/O 작업이 많은 경우
        System.out.println("CPU 작업용 스레드 수: " + threadsForCPU);
        System.out.println("I/O 작업용 스레드 수: " + threadsForIO);
        
        // ExecutorService를 사용한 멀티스레드 처리 (스레드 풀 3개)
        ExecutorService executor = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 작업 수행 중...");
            });
        }
        executor.shutdown(); // 모든 작업 요청 후 종료
        
        System.out.println("모든 작업 요청 완료");
    }

    // Runnable 인터페이스를 활용한 스레드 작업 정의
    static class Task implements Runnable {
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " 작업 중... " + i);
                try {
                    Thread.sleep(1000); // 1초 대기(작업 하나 하는데 걸리는 시간)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
 

 


 

3. 실행 결과 예상

 

작업 1 시작
작업 2 시작
CPU 작업용 스레드 수: 9
I/O 작업용 스레드 수: 17
모든 작업 요청 완료
Thread-0 작업 중... 0
Thread-1 작업 중... 0
Thread-0 작업 중... 1
Thread-1 작업 중... 1
...
pool-1-thread-1 작업 수행 중...
pool-1-thread-2 작업 수행 중...
 

🔹 Thread-0, Thread-1이 동시에 실행

🔹 ExecutorService를 활용한 스레드 풀에서 작업 수행


4. Thread와 ExecutorService 차이점

 

 
방식
특징
사용 예
Thread
직접 생성 및 관리
간단한 스레드 실행
Runnable
Thread보다 가벼움, run() 메서드만 구현
멀티스레드 기본 작업
ExecutorService
스레드 풀을 사용하여 관리 효율적
대규모 병렬 처리, 안정적 관리

 

💡 멀티스레드 환경에서 직접 Thread를 생성하는 것보다 ExecutorService를 사용하는 것이 권장됨!

(스레드 개수를 제한하여 성능을 관리할 수 있기 때문)

 

 


 

5. ExecutorService 활용법

 

 

✅ 1) 고정된 개수의 스레드 풀

ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(new Task());
executor.shutdown();
 

 

2) 캐시된 스레드 풀 (필요할 때 생성)

 

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(new Task());
executor.shutdown();
 

 

3) 단일 스레드 실행 (순차 처리)

 

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(new Task());
executor.shutdown();
 

 


 

 

6. 멀티스레드 꿀팁

 

1) 스레드 동기화 (synchronized)

 

class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
}
 

💡 여러 스레드가 동시에 접근할 때 데이터 충돌을 방지

 


2) Future를 활용한 비동기 작업 처리

 

ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> {
    Thread.sleep(1000);
    return 42;
});
System.out.println(future.get()); // 1초 후 결과 출력
executor.shutdown();
 

💡 비동기 작업을 실행하고 결과를 나중에 가져올 수 있음!

 


3) CountDownLatch를 활용한 스레드 동기화

 

CountDownLatch latch = new CountDownLatch(3);
ExecutorService executor = Executors.newFixedThreadPool(3);

for (int i = 0; i < 3; i++) {
    executor.execute(() -> {
        System.out.println(Thread.currentThread().getName() + " 작업 완료");
        latch.countDown(); // 하나의 작업 완료
    });
}

latch.await(); // 모든 작업이 끝날 때까지 대기
System.out.println("모든 작업 완료!");
executor.shutdown();
 

 

💡 여러 개의 스레드가 작업을 마칠 때까지 대기할 수 있음

 


7. 마무리

📌 정리

✅ Thread, Runnable, ExecutorService를 활용하여 멀티스레드 프로그래밍 가능

✅ ExecutorService를 활용하면 스레드 관리를 더 효율적으로 할 수 있음

✅ synchronized, Future, CountDownLatch 등을 활용하여 동기화 문제 해결 가능

 

스레드 하나 잘쓰면 비용과 시간을 줄일 수 있기에 실무에서 아주 유용하고 자주 쓰이게 된다

고로 이후 포스팅에도 스레드에 관한 내용을 자주 다뤄보려고 한다!

 

 

 

 

학스의 개발일지

일상과 코딩 그 사이 어딘가에있는 블로그.. 블로거이자 빅데이터개발자 학스 입니다 JAVA, jQuery, PostgreSQL, MySQL, HIVE, Hadoop 더 많은 정보는 깃허브 주소 https://github.com/hacs2772 를 방문해주세요

hacs2772.tistory.com

 

 

 

오류나 궁금하신점은
아래 댓글로 알려주시면 감사하겠습니다.

 

 

 

 

 

 

728x90
반응형
LIST