멀티쓰레드 환경에서 공유 리소스에 대한 동시성 제어가 필요한 예제를 알아본다.
sychronized 등을 통한 동시성제어가 아니라 AtomicXXX 클래스(AtomicIntger)를 통해 동시성제어를 구현해본다.
1.멀티쓰레드 환경에서 item 리소스(공유변수)에 동시에 접근할 때
멀티쓰레드환경에서 2개 이상의 쓰레드가 item 변수에 동시에 접근하여 증가, 감소를 실행할 때 예제를 통해 상황을 구현한다. increase()를 1000번 실행시키고 decrease()를 1000번 실행시키면 item값은 0이 되어야 한다. 그러나 멀티쓰레드 환경에서는 결과가 다르게 나올 수 있기 때문에 동시성 제어가 필요하다.
일반적으로 synchronized 키워드를 메소드에 붙여주거나 임계영역(Critical Section) 블록에만 붙여주는 방법이 있다.(임계영역에만 붙여주는게 쓰레드간의 효율적인 처리를 위해 더 추천된다)
2.동시성제어를 하지 않은 경우
동시성 제어를 따로 하지 않은 경우의 예시코드다.
package org.example;
public class Main17 {
public static void main(String[] args) throws InterruptedException {
//item을 증가시키는 쓰레드 생성, item을 감소시키는 쓰레드 생성
Item item = new Item();
IncrementThread incrementThread = new IncrementThread(item);
DecrementThread decrementThread = new DecrementThread(item);
//쓰레드들 실행
incrementThread.start();
decrementThread.start();
//실행순서 보장 처리
incrementThread.join();
decrementThread.join();
System.out.println("Updated Item :" + item.get());
}
public static class IncrementThread extends Thread {
private Item item;
public IncrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.increment();
}
}
}
public static class DecrementThread extends Thread {
private Item item;
public DecrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.decrement();
}
}
}
public static class Item {
private Integer item=0;
public void increment() {
item++;
}
public void decrement() {
item--;
}
public Integer get() {
return item;
}
}
}
위와 같이 동시성제어가 따로 안된 상태에서 main()을 호출하면 결과값은 0이 아니라 이상한 값이 나온다.
참고 : run()으로 실행하면 쓰레드를 따로 안만들고 단순히 코드를 실행하는 역할만 하기 때문에 start()를 사용하여 쓰레드를 위한 새로운 메모리영역을 만들어서 실행시켜줘야 함
Updated Item :-627
3.동시성제어를 한 경우(synchronized 키워드 사용)
synchronized 키워드를 경쟁상태(race condition)가 발생할 메소드에 붙여준다.
package org.example;
public class Main17 {
public static void main(String[] args) throws InterruptedException {
//item을 증가시키는 쓰레드 생성, item을 감소시키는 쓰레드 생성
Item item = new Item();
IncrementThread incrementThread = new IncrementThread(item);
DecrementThread decrementThread = new DecrementThread(item);
//쓰레드들 실행
incrementThread.start();
decrementThread.start();
//실행순서 보장 처리
incrementThread.join();
decrementThread.join();
System.out.println("Updated Item :" + item.get());
}
public static class IncrementThread extends Thread {
private Item item;
public IncrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.increment();
}
}
}
public static class DecrementThread extends Thread {
private Item item;
public DecrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.decrement();
}
}
}
public static class Item {
private Integer item=0;
public synchronized void increment() {
item++;
}
public synchronized void decrement() {
item--;
}
public Integer get() {
return item;
}
}
}
결과값은 다음과 같다.
Updated Item :0
4.동시성제어를 한 경우 (AtomicInteger 사용)
AtomicInteger를 사용하면 synchronized 키워드 없이 동시성제어가 가능하다.
package org.example;
import java.util.concurrent.atomic.AtomicInteger;
public class Main17 {
public static void main(String[] args) throws InterruptedException {
//item을 증가시키는 쓰레드 생성, item을 감소시키는 쓰레드 생성
Item item = new Item();
IncrementThread incrementThread = new IncrementThread(item);
DecrementThread decrementThread = new DecrementThread(item);
//쓰레드들 실행
incrementThread.start();
decrementThread.start();
//실행순서 보장 처리
incrementThread.join();
decrementThread.join();
System.out.println("Updated Item :" + item.get());
}
public static class IncrementThread extends Thread {
private Item item;
public IncrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.increment();
}
}
}
public static class DecrementThread extends Thread {
private Item item;
public DecrementThread(Item item) {
this.item = item;
}
@Override
public void run() {
for (int i=0; i<1000; i++) {
this.item.decrement();
}
}
}
public static class Item {
private AtomicInteger item = new AtomicInteger(0);
public void increment() {
item.incrementAndGet();
}
public void decrement() {
item.decrementAndGet();
}
public Integer get() {
return item.get();
}
}
}
결과값은 다음과 같다.
Updated Item :0
멀티쓰레드 환경에서 AtomicInteger 성능이 더 좋고, 싱글쓰레드 환경에서는 그냥 Integer를 쓰는 것이 더 낫다.
AtomicXXX는 시리즈가 많다. AtomicLong, AtomicBoolean등 다양하다.
[참고]
Java 멀티스레딩, 병행성 및 성능최적화 - 전문가 되기(Udemy)
'개발자 일지 > Java' 카테고리의 다른 글
[java] 동일성, 동등성 개념 및 equals, hashCode (0) | 2024.10.16 |
---|---|
[Java] Logback, SLF4J 기초, 사용 이유, 환경 설정 및 테스트 (0) | 2024.10.04 |
[에러]java.lang.Integer cannot be cast to java.lang.String 해결방법 (2) | 2022.05.27 |
[ibatis]SELECT 결과가 이상할 때(remapResults 사용하기) (1) | 2021.04.23 |
[디자인패턴]빌더 패턴(Builder Pattern) (0) | 2021.03.21 |
깊은 복사(Deep Copy)와 얕은 복사(Shallow Copy)의 이해 (0) | 2021.03.15 |
[디자인패턴]싱글톤 패턴(Singleton Pattern) (0) | 2021.03.08 |
[디자인패턴]팩토리 메소드 패턴(Factory Method Pattern) (0) | 2021.03.07 |