병렬처리의 필요성:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
public class DataProcessor {
public static void main(String[] args) {
// 대용량 데이터셋 생성
List<Integer> dataSet = new ArrayList<>();
for (int i = 0; i < 100; i++) {
dataSet.add(i);
}
// ForkJoinPool 객체 생성
ForkJoinPool forkJoinPool = new ForkJoinPool();
// ForkJoinPool에 병렬 작업 제출
forkJoinPool.submit(() -> {
// 병렬 스트림을 사용하여 각 데이터 요소를 처리
dataSet.parallelStream().forEach(DataProcessor::processData);
}).join(); // 작업이 완료될 때까지 대기
}
private static void processData(int data) {
// 시간이 오래 걸리는 작업을 시뮬레이션
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 처리된 데이터 출력
System.out.println("Processed data: " + data);
}
}
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinPool
객체를 생성한다. 이 객체는 병렬 작업을 관리하고 스케줄링 한다.forkJoinPool.submit(() -> {...}).join();
ForkJoinPool
**에 병렬 작업을 제출하고, 작업이 완료될 때까지 대기한다..join()
메서드를 통해 모든 병렬 작업이 완료될 때까지 메인 스레드가 대기하게 된다.dataSet.parallelStream().forEach(DataProcessor::processData);
parallelStream()
**을 사용하여 데이터셋의 각 요소를 병렬로 처리한다.forEach(DataProcessor::processData)
부분에서 processData
메서드가 각 데이터 요소에 대해 병렬로 호출된다.동시 처리(병렬) 방식
dataSet.parallelStream().forEach(DataProcessor::processData);
코드를 실행하면, 데이터셋의 각 요소가 병렬로 처리된다. 즉, **ForkJoinPool
**이 관리하는 여러 스레드가 각각의 데이터 요소를 독립적으로 처리하게 된다. 예를 들어, 데이터셋에 1, 2, 3, ..., 100까지의 숫자가 있다면, 이 숫자들은 병렬로 처리될 것이다.forEach(DataProcessor::processData)
**를 통해 processData
메서드가 각 데이터 요소에 대해 병렬로 호출되며, 이는 **ForkJoinPool
**에 의해 관리된다.스레드 동기화
스레드 동기화는 여러 스레드가 공유 자원에 동시에 접근할 때 발생할 수 있는 문제를 해결하기 위한 기술이다. 이를 위해 Java에서는 synchronized
키워드와 Lock
인터페이스를 제공한다.
실제 상황 예시
적합한 상황
synchronized 키워드
synchronized
**로 선언하면, 해당 부분에 한 번에 하나의 스레드만 접근할 수 있다.예시코드
public class SynchronizedExample {
private int count = 0; // 공유 자원
// synchronized 키워드를 사용하여 메서드 동기화
public synchronized void increment() {
count++; // 임계 영역
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value of count: " + example.count);
}
}
count
변수를 0으로 초기화하고, increment()
메서드에 synchronized
키워드를 붙여 동기화를 설정한다.main
메서드에서 두 개의 스레드(t1
, t2
)를 생성한다. 이 스레드들은 increment()
메서드를 각각 1000번 호출하도록 설정된다.t1
과 t2
스레드를 실행시킨다. 이제 두 스레드는 count
를 증가시키기 위해 경쟁한다.t1
이 먼저 increment()
메서드에 접근하면, synchronized
블록에 진입한다. 이때 count
를 1 증가시킨다.t2
는 synchronized
블록이 해제될 때까지 대기한다.t1
이 synchronized
블록을 빠져나오면, 이제 t2
가 진입할 수 있다. t2
도 count
를 1 증가시킨다.t1
과 t2
가 총 2000번 count
를 증가시키게 된다. 따라서 count
의 최종 값은 2000이 된다.synchronized
키워드를 사용하면, 여러 스레드가 동시에 접근해도 count
값은 정확하게 계산된다.Lock 인터페이스
java.util.concurrent.locks
패키지에서 제공한다. Lock
인터페이스를 구현한 클래스를 사용하면 더 세밀한 동기화 제어가 가능하다.예시코드
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock(); // Lock 인터페이스의 구현체
private int count = 0; // 공유 자원
public void increment() {
lock.lock(); // 임계 영역에 진입하기 전에 lock을 획득
try {
count++; // 임계 영역
} finally {
lock.unlock(); // 임계 영역을 빠져나올 때 lock을 해제
}
}
public static void main(String[] args) {
LockExample example = new LockExample();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
example.increment();
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value of count: " + example.count);
}
}
Lock
객체를 만들고, count
변수를 0으로 초기화 한다.main
함수에서 두 스레드(t1
, t2
)를 만들어 increment()
함수를 각각 1000번 호출하도록 설정한다.t1
과 t2
를 실행시킨다. 이제 두 스레드는 count
를 증가시키려고 경쟁한다.t1
이 먼저 도착해서 lock.lock()
을 호출하면, 락을 획득하고 count
를 증가시킨다.t2
는 락이 해제될 때까지 기다린다.t1
이 작업을 마치고 lock.unlock()
을 호출하여 락을 해제하면, 이제 t2
가 락을 획득하고 count
를 증가시킨다.t1
과 t2
가 각각 1000번씩 count
를 증가시키게 된다. 결국 count
의 최종 값은 2000이 된다.Lock
을 사용하면, 여러 스레드가 동시에 같은 변수나 자원에 접근해도 안전하게 관리할 수 있다.Mutual Exclusion의 약자로, 한 번에 하나의 스레드만 공유 자원에 접근할 수 있도록 하는 동기화 메커니즘이다.
왜 사용하는가: 데이터 무결성을 유지하기 위해, 공유 자원을 한 번에 하나의 스레드만 수정할 수 있도록 제한한다.
실제 상황 예시:
적합한 상황
Java에서의 구현:
synchronized
키워드나 Lock
인터페이스를 통해 뮤텍스를 구현할 수 있다.