-
반응형
멀티쓰레드 환경에서 공유 리소스에 대한 동시성 제어가 필요한 예제를 알아본다.
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)
반응형'BackEnd > Java' 카테고리의 다른 글
[에러]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 댓글