본문 바로가기
개발자 일지/Java

멀티쓰레드환경,동시성제어 | AtomicInteger 활용하기

by 네빌링 2023. 10. 14.
반응형

멀티쓰레드 환경에서 공유 리소스에 대한 동시성 제어가 필요한 예제를 알아본다.

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)

반응형