동시성과 병렬성을 이해하는것을 목표로 합니다.
작업을 처리나 계산하는 과정에서 동시성과 병렬성은 언뜻보면 비슷하게 보인다.
그렇다면 구체적으로 어떤부분이 다를까?
동시성과 병렬성?
오라클 문서에는 병렬성은 두 개 이상의 스레드가 동시에 실행되는것을 의미하며
동시성은 두 개 이상의 스레드가 진행중일 때 라고 차이를 둔다.
뭐가 다른지는 잘모르겠으니 좀 더 찾아보자.
병렬성과 동시성은 모두 여러 작업을 동시에 수행하기 위해 사용된다.
동시성은 싱글 코어를 이용하여 여러 작업을 동시에 처리하는 것처럼 보이게(Context Switching) 처리하는것이고
병렬성은 멀티 코어를 이용하여 실제로 여러 작업을 동시에 처리하는것을 의미한다.
따라서 두 프로세스 모두 여러 작업을 동시에 처리하지만, 동시성 처리는 정확한 동시 작업(Concurrently)을 보장하지 않고
병렬처리는 실제 멀티 코어에서 여러 작업이 수행될 경우 동시 작업(Simultaneously)을 보장한다.
아래 이미지를 참고할 수 있다.
자바에서 병렬처리
자바 8 이후부터 지원하는 Stream Api를 사용하면 손쉽게 병렬처리를 할 수 있다.
* 메인 클래스
public class Main {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
List<Integer> box = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println("Part 1");
System.out.println("────────────────────────────────────────────");
long startTime1 = System.currentTimeMillis();
// 순차 처리
for (Integer value : box) {
String threadName = Thread.currentThread().getName();
LocalDateTime currentTime = LocalDateTime.now();
System.out.printf(currentTime.format(formatter) + " -> Thread Name: %s, Stream Value: %s\n", threadName, value);
}
long endTime1 = System.currentTimeMillis();
System.out.println("────────────────────────────────────────────");
System.out.println("Elapsed Time (Part 1): " + (endTime1 - startTime1) + " milliseconds");
System.out.println();
System.out.println("Part 2");
System.out.println("────────────────────────────────────────────");
long startTime2 = System.currentTimeMillis();
// 병렬 처리
box.parallelStream().forEach(value -> {
String threadName = Thread.currentThread().getName();
LocalDateTime currentTime = LocalDateTime.now();
System.out.printf(currentTime.format(formatter) + " -> Thread Name: %s, Stream Value: %s\n", threadName, value);
// 시간 확인을 위해 2초간 sleep 함
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long endTime2 = System.currentTimeMillis();
System.out.println("────────────────────────────────────────────");
System.out.println("Elapsed Time (Part 2): " + (endTime2 - startTime2) + " milliseconds");
}
}
* 실행 결과
- 순차로 처리했을 때는 메인 스레드로 모두 처리했고 병렬 처리 시 ForkJoinPool worker라는 스레드 이름이 생긴게 확인된다.
- 병렬처리 시 순차로 처리하지 않았기 때문에 숫자가 순차적으로 호출되지 않는다.
자바에서 동시성 처리
* 여러 스레드를 동시에 작업하며 컨텍스트 스위칭을 하는 과정에서 스레드들간의 공유 자원 간 생기는 문제들에 대해서 안정성을 보장해주어야한다.
이를 스레드 세이프(thread safe)라고 한다.
* 스레드 안정성이 깨지는 상황
public class Main {
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
List<Integer> box = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int counter = 0;
box.parallelStream().forEach(value -> {
counter += value + 1;
System.out.println(counter);
});
- 위 코드의 counter라는 변수에 담긴 값은 병렬 처리 과정에서 동시 업데이트에 대한 경쟁이 발생하게 된다.
- 그 결과 값이 중복되거나 예상치 못한 결과가 반환될 수 있다.
예를들어 스레드 A와 B가 value가 각각 5, 10인 값을 가지고 있는 상황에서,
counter값 계산을 동시에 처리했을때, 최종값이 6과 11이 나타나게 된다.
이를 해결하기 위한 방법
1. Concurrent 패키지 (Atomic)
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
List<Integer> box = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
AtomicInteger counter = new AtomicInteger();
box.parallelStream().forEach(value -> {
counter.addAndGet(value + 1);
System.out.println(counter.get());
});
* 단일 변수에 대한 스레드 세이프 프로그래밍을 지원하는 패키지 클래스이며 AtomicInteger, AtomicBoolean등 사용이 가능하다.
* addAndGet, get 등의 함수를 통해 값을 수정이나 꺼낼 수 있다.
2. 불변객체 사용
* 불변 객체는 락을 걸 필요가 없다. 내부적인 상태가 변하지 않으니 여러 스레드에서 동시에 참조해도 동시성 이슈가 발생하지 않는다는 장점이 있다. 즉, 불변 객체는 언제라도 스레드 안전하다.
* 불변 객체는 생성자로 모든 상태 값을 생성할 때 세팅하고, 객체의 상태를 변화시킬 수 있는 부분을 모두 제거해야 다. 가장 간단한 방법은 세터(setter)를 만들지 않는 것이다. 또한 내부 상태가 변하지 않도록 모든 변수를 final로 선언하면 된다.
referenced by (https://medium.com/@deepshig/concurrency-vs-parallelism-4a99abe9efb8, https://vagabond95.me/posts/concurrency_vs_parallelism/,
https://devfunny.tistory.com/810, https://velog.io/@mooh2jj/%EB%A9%80%ED%8B%B0-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88,
http://cris.joongbu.ac.kr/course/java/api/java/util/concurrent/atomic/package-summary.html,
https://deveric.tistory.com/104)
'공부' 카테고리의 다른 글
팩토리 메소드 패턴이란? (0) | 2024.06.06 |
---|---|
Practical Testing: 실용적인 테스트 가이드 강의를 마치며 (0) | 2024.03.06 |
백지에서 따라가며 배우는 젠킨스 & EC2 (4) (0) | 2024.01.14 |
백지에서 따라가며 배우는 젠킨스 & EC2 (3) (0) | 2024.01.14 |
백지에서 따라가며 배우는 젠킨스 & EC2 (2) (0) | 2024.01.14 |