병렬처리의 필요성:
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 인터페이스를 통해 뮤텍스를 구현할 수 있다.