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(); // 스레드 실행
}
}
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(); // 스레드 실행
}
}
구분 | Thread | Runnable |
클래스 설계 | 다른 클래스를 상속할 수 없음 | 다른 클래스를 자유롭게 상속할 수 있음 |
코드 재사용성 | 스레드 실행 로직과 비즈니스 로직이 섞이므로 재사용성이 낮음 | 비즈니스 로직과 스레드 실행을 분리할 수 있어 재사용성이 높음 |
객체 공유 | 스레드별로 독립된 객체를 생성 | 동일한 Runnable 객체를 여러 스레드에서 공유 |
구현 방식 | Thread 클래스의 run() 메서드를 오버라이드 | Runnable 인터페이스의 run() 메서드를 구현 |
유연성 | 단순한 작업이나 독립적인 스레드 작업에 적합 | 공유 리소스 관리나 복잡한 로직이 필요한 경우 적합 |
멀티스레드 환경에서 동시성 이슈로 가시성 문제와 원자성 문제 발생
멀티스레드 환경에서 각 스레드가 공유자원에 대해 같은 상태를 바라보지 못하는 문제
각 스레드는 CPU에서 실행되며, 주 메모리(Main Memory)가 아닌 CPU 캐시(Cache)에서 변수를 읽고 쓰는 경우가 많음
CPU는 성능 최적화를 위해 로컬 캐시를 사용하므로, 주 메모리와 캐시 간 동기화 시점에 차이가 발생
이로 인해, 한 스레드가 변수 값을 변경해도 다른 스레드가 이를 최신 상태로 보장받지 못할 가능성 발생
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());
}
}
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();
}
}
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());
}
}
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);
}
}
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/프로세스-동기화