[JAVA]자바 스터디 4주차 정리

bbbbbhyun·2025년 1월 22일
0

Thread

Thread란 무엇인가?

  • 스레드(Thread)는 프로세스 내에서 실행되는 가장 작은 단위의 실행 흐름
  • 하나의 프로세스는 기본적으로 단일 스레드(메인 스레드)로 시작하지만, 여러 스레드를 생성하여 병렬로 작업을 처리할 수 있음
  • Java에서는 스레드를 통해 멀티태스킹을 구현하여 효율적인 CPU 사용과 빠른 응답성을 제공

JAVA Thread 특징

  1. 경량 프로세스
  • 스레드는 프로세스의 메모리 공간을 공유하며, 자체적으로 독립적인 실행
  1. 병렬 처리
  • 여러 작업을 동시에 실행하여 프로그램의 성능과 효율성을 증가
  1. 공유 자원 관리
  • 스레드는 같은 메모리 공간을 공유하므로, 동기화와 같은 자원 관리가 필요

JAVA Thread 구현 예시및 비교

  1. Thread 클래스 상속
  • Thread 클래스를 상속받아 run() 메서드를 오버라이드
  • 객체를 생성하고 start() 메서드를 호출하여 스레드를 실행
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start(); // 스레드 실행
    }
}
  1. Runnable 인터페이스 구현
  • Runnable 인터페이스를 구현하여 run() 메서드를 정의
  • Thread 객체에 Runnable을 전달하여 스레드를 실행
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Thread is running");
    }
}

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start(); // 스레드 실행
    }
}
  1. Thread VS Runnable
구분 Thread Runnable
클래스 설계 다른 클래스를 상속할 수 없음 다른 클래스를 자유롭게 상속할 수 있음
코드 재사용성 스레드 실행 로직과 비즈니스 로직이 섞이므로 재사용성이 낮음 비즈니스 로직과 스레드 실행을 분리할 수 있어 재사용성이 높음
객체 공유 스레드별로 독립된 객체를 생성 동일한 Runnable 객체를 여러 스레드에서 공유
구현 방식 Thread 클래스의 run() 메서드를 오버라이드 Runnable 인터페이스의 run() 메서드를 구현
유연성 단순한 작업이나 독립적인 스레드 작업에 적합 공유 리소스 관리나 복잡한 로직이 필요한 경우 적합

동시성 프로그래밍에서 발생할 수 있는 문제

멀티스레드 환경에서 동시성 이슈로 가시성 문제와 원자성 문제 발생

가시성 문제

  • 멀티스레드 환경에서 각 스레드가 공유자원에 대해 같은 상태를 바라보지 못하는 문제

  • 각 스레드는 CPU에서 실행되며, 주 메모리(Main Memory)가 아닌 CPU 캐시(Cache)에서 변수를 읽고 쓰는 경우가 많음

  • CPU는 성능 최적화를 위해 로컬 캐시를 사용하므로, 주 메모리와 캐시 간 동기화 시점에 차이가 발생

  • 이로 인해, 한 스레드가 변수 값을 변경해도 다른 스레드가 이를 최신 상태로 보장받지 못할 가능성 발생

원자성 문제

  • 여러 스레드가 동시에 데이터를 수정하려고 할 때 데이터의 일관성을 보장하지 못하는 문제

해결 방법및 비교

  1. Synchronized
  • 특정 코드 블록이나 메서드를 하나의 스레드만 접근 가능하도록 만듬
  • 다른 스레드가 해당 블록을 실행 중이면 대기
class Counter {
    private int count = 0;

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

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

public class Main {
    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("Final Count: " + counter.getCount());
    }
}
  1. Volatile
  • 가시성 문제를 해결하기 위해 사용되며, 변수 값을 CPU 캐시가 아닌 메인 메모리에서 읽고 씀
class Flag {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    public boolean isRunning() {
        return running;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Flag flag = new Flag();

        Thread t1 = new Thread(() -> {
            while (flag.isRunning()) {
                // 작업 수행
            }
            System.out.println("Stopped");
        });

        t1.start();

        Thread.sleep(1000); // 1초 후 플래그 변경
        flag.stop();

        t1.join();
    }
}
  1. Atomic Classes
  • java.util.concurrent.atomic 패키지는 CAS기반으로 동기화를 제공
    CAS(Compare-And-Swap)란 동시성 제어를 위한 알고리즘으로, 다중 스레드 환경에서 락 없이 안전하게 데이터 갱신을 수행할 수 있는 방식
    이는 주로 비관적 락(Pessimistic Lock)을 대신하는 낙관적 락(Optimistic Lock)의 구현 방식으로 사용

  • 대표적인 클래스: AtomicInteger, AtomicLong, AtomicReference.

import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

public class Main {
    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("Final Count: " + counter.getCount());
    }
}
  1. Concurrent Collections
  • java.util.concurrent 패키지의 컬렉션 클래스는 스레드 안전하게 설계
  • ConcurrentHashMap, CopyOnWriteArrayList , ConcurrentLinkedQueue의 클래스를 가짐
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        Thread t1 = new Thread(() -> map.put("Key1", 1));
        Thread t2 = new Thread(() -> map.put("Key2", 2));

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

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

        System.out.println("Map: " + map);
    }
}
  1. ReentrantLock
  • java.util.concurrent.locks.ReentrantLock은 Lock 인터페이스를 구현하여 동기화를 제공
  • Synchronized보다 세밀한 제어가 가능하며, 조건 대기/신호(Condition)도 사용 가능
import java.util.concurrent.locks.ReentrantLock;

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

    public void increment() {
        lock.lock(); // Lock 획득
        try {
            count++;
        } finally {
            lock.unlock(); // Lock 해제
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    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("Final Count: " + counter.getCount());
    }
}

비교

방법 특징 장점 단점
synchronized 기본적인 동기화 제공 간단하고 직관적 성능 저하
Volatile 키워드 가시성 문제 해결 간단한 가시성 문제에 적합 복잡한 동기화에는 적합하지 않음
Atomic 클래스 CAS 기반 동기화 빠르고 간단한 작업에 적합 복잡한 로직에는 적합하지 않음
Concurrent Collections 스레드 안전한 컬렉션 클래스 제공 고성능 동시성 제어 특정 경우 복잡도 증가
ReentrantLock 명시적 Lock/Unlock 지원 더 세밀한 동기화 가능 코드 복잡성 증가

참고

https://velog.io/@mooh2jj/멀티-스레드의-동시성-이슈

https://velog.io/@junr_65535/CSstudy-Synchronized-키워드에-대해-알아보자

https://drg2524.tistory.com/195

https://velog.io/@eunsiver/프로세스-동기화

https://f-lab.kr/insight/multithreading-in-java

https://sharonprogress.tistory.com/187

profile
BackEnd develope

0개의 댓글

관련 채용 정보