Categories: 미분류

Java 멀티스레딩과 동시성(Concurrency) 프로그래밍 심화과정에 대해 알아보기

Java는 멀티스레딩(Multi-threading)과 동시성(Concurrency) 프로그래밍을 지원합니다. 그리고 대규모 애플리케이션의 성능을 향상 시킬 수 있습니다. 멀티스레딩을 활용하면 여러 작업을 병렬로 수행하여 응답성을 개선하고, CPU 사용률을 높일 수 있습니다.

이번 포스팅에서는 Java의 멀티스레딩 기법과 동시성 프로그래밍의 핵심 개념, 그리고 실무에서 활용할 수 있는 최적화 기법을 알려 드리겠습니다.


1. 멀티스레딩과 동시성의 차이

  • 멀티스레딩(Multi-threading): 하나의 프로세스 내에서 여러 개의 Thread가 실행되며, 각 Thred는 독립적으로 작업을 수행합니다.
  • 동시성(Concurrency): 여러 작업이 동시에 실행되는 개념으로, 멀티코어 CPU 환경에서 효율적인 자원 활용을 목표로 합니다.

Java에서는 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현하여 멀티스레딩을 구현이 가능합니다.


2. Java에서의 멀티스레딩 구현

1) Thread 클래스 상속

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();
    }
}

2) Runnable 인터페이스 구현

class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

Runnable을 사용하면 다중 상속 문제를 피할 수 있으며, 더 유연한 코드 구성도 가능하며, 활용도가 높습니다.


3. ExecutorService를 이용한 Thread 관리

Java에서는 ExecutorService를 사용하여 Thread를 효율적으로 관리할 수 있습니다.

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

public class ExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " is executing");
            });
        }

        executor.shutdown();
    }
}
  • newFixedThreadPool(n): 고정된 개수의 Thread 풀을 생성 합니다.
  • newCachedThreadPool(): 필요에 따라 동적으로 Thread를 생성합니다.
  • newSingleThreadExecutor(): 단일 Thread를 사용하여 순차적으로 작업을 처리를 합니다.

4. 동기화(Synchronization)와 경쟁 상태 해결

멀티스레딩 환경에서는 여러 Thread가 공유 자원에 동시에 접근하면 경쟁 상태(Race Condition) 문제가 발생할 수 있으며, 이를 해결하기 위해 동기화 기법을 사용합니다.

1) synchronized 키워드 사용

class SharedResource {
    synchronized void printMessage(String message) {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + message);
        }
    }
}

public class SyncExample {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        
        Thread t1 = new Thread(() -> resource.printMessage("Hello"));
        Thread t2 = new Thread(() -> resource.printMessage("World"));
        
        t1.start();
        t2.start();
    }
}

2) ReentrantLock 사용

synchronized의 단점을 보완하기 위해 ReentrantLock을 사용할 수 있습니다.

import java.util.concurrent.locks.ReentrantLock;

class SharedResource {
    private final ReentrantLock lock = new ReentrantLock();
    
    void printMessage(String message) {
        lock.lock();
        try {
            for (int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + message);
            }
        } finally {
            lock.unlock();
        }
    }
}

5. 동시성 프로그래밍을 위한 고급 기법

1) CompletableFuture 활용

비동기 작업을 쉽게 관리할 수 있도록 CompletableFuture를 사용할 수 있습니다.

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(s -> s + " World")
                .thenAccept(System.out::println);
    }
}

2) Atomic 변수 활용 (CAS 알고리즘)

멀티스레드 환경에서 synchronized 없이 동기화를 수행할 수 있습니다.

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private static final AtomicInteger counter = new AtomicInteger(0);
    
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                System.out.println(counter.incrementAndGet());
            }).start();
        }
    }
}

6. 멀티스레딩 및 동시성 프로그래밍의 모범 사례

  1. 공유 자원 최소화: 불필요한 공유 상태를 줄이고 ThreadLocal을 활용할 수 있습니다.
  2. 스레드 풀 활용: ExecutorService를 사용하여 스레드 생성 비용 절감할 수 있습니다.
  3. 동기화 최소화: synchronized 대신 ConcurrentHashMap, Atomic 클래스를 활용할 수 있습니다.
  4. 데드락(Deadlock) 방지: 락 순서를 정하고 tryLock()을 활용하여 교착 상태 방지할 수 있습니다.

결론

Java의 멀티스레딩과 동시성 프로그래밍은 성능을 극대화할 수 있는 강력한 Tool입니다. Thread, ExecutorService, CompletableFuture 등을 활용하게 된다면 효율적인 병렬 처리를 구현할 수 있습니다. 최적화된 멀티스레드 애플리케이션을 한번 Test 해보시길 바랍니다. 감사합니다.

 

루루파파

Recent Posts

2025년 디딤돌 전세 대출로 전세금 2억 원 마련한 후기 (금리, 한도, 계산기 활용)

디딤돌 전세 대출을 받은 실제 경험 바탕으로 포스팅을 작성합니다. 디딤돌 대출은 생애최초, 신혼부부 대출로 나뉘어져…

3주 ago

Spring Security를 활용한 인증 및 권한 관리에 대해 알아보기

현대 웹 애플리케이션에서는 보안이 중요한 요소이며, Spring Security는 강력한 인증 및 권한 관리 기능을 제공합니다.…

3주 ago

Java에서의 로깅 및 모니터링 전략에 대해 알아보기

애플리케이션 개발에서 로깅(logging)과 모니터링(monitoring)은 필수적인 요소입니다. Java 애플리케이션이 실행되는 동안 발생하는 이벤트를 기록하고, 성능 및…

4주 ago

Java에서의 API 설계 가이드 중 RESTful API vs GraphQL API에 대해 알아보기

API(Application Programming Interface)는 애플리케이션 간 데이터를 주고받기 위한 인터페이스로, Java에서는 주로 RESTful API와 GraphQL API를…

4주 ago

Java에서의 디자인 패턴 심화 중 GoF 패턴 분석에 대해 알아보기

디자인 패턴(Design Patterns)은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위한 일반적인 해결책을 제공합니다. 특히, GoF(Gang…

4주 ago

Java 애너테이션(Annotation)과 커스텀 애너테이션 활용법에 대해 알아보기

Java의 애너테이션(Annotation)은 메타데이터를 코드에 추가하는 기능을 제공합니다. 이를 활용하면 코드의 가독성을 높이고, 프레임워크에서 런타임 처리를…

4주 ago