Concurrency Control

정미·2022년 7월 27일
0

Computer Science

목록 보기
43/81

동시성 이슈

멀티 스레딩

장점

  • 한 프로세스 내부의 여러 스레드들은 같은 자원을 공유하여 사용할 수 있다. → 병렬성의 향상
  • 여러 가지 일을 같은 자원을 두고 동시에 수행할 수 있다.
  • 멀티 프로세싱보다 context switching 오버헤드가 작아 메모리 리소스가 상대적으로 적다.

단점

  • 하나의 자원을 두고 경쟁 상태(race condition)가 발생한다.
  • 동시성 문제, 데드락과 같은 여러 문제점을 해결해야 한다.

스레드 안정성 Thread-safe

여러 스레드가 작동하는 환경에서 문제없이 동작하는 것

= 동시성 이슈 해결

스레드 안정성이 깨지는 예시

조회수라는 변수에 여러 스레드가 접근하여 각 스레드가 조회수의 값을 변경시키고 조회하는 경우

스레드 1, 2가 동시에 count 변수에 접근해서 같은 값을 조회하고, 각 스레드에서 조회수를 1씩 늘리고자 했다. 하지만 조회수가 102가 되지 못했다.

동시성 제어

스레드 안전성을 지키는 방법

자바 multi thread 환경에서 동시성 제어를 위한 방법을 알아보자.
대표적인 방법으로 synchronized, volatile, atomic이 있다.

1. volatile 키워드

변수를 공유하는 방법

예시

public class SharedObject {
	public volatile int count = 0;
}

자원의 가시성

  • main memory에 저장된 실제 자원의 값을 볼 수 있는 것
  • 자원의 가시성을 확보하지 못하는 경우
    • 여러 스레드가 동시에 하나의 자원에 read, write를 할 때 성능 향상을 위해 CPU 캐시에 저장하여 항상 메모리에 접근하진 않는다.
    • 해당 데이터가 메모리에 저장된 실제 데이터와 항상 일치하는지 보장할 수 없다.
  • volatile은 CPU 캐시 저장, 사용을 막는다.
    • 자원의 가시성을 책임진다.

특징

  • 각 스레드가 가진 CPU 캐시가 아닌 공유하는 메인 메모리에서 read, write를 한다.
    • 캐시 사용으로 인한 데이터 불일치가 없음

단점

  • 자원의 가시성을 확보해주지만 동시성 이슈를 해결에 충분하지 못하다.
    • 공유자원에 read를 여러번 할 경우 동기화를 통해 연산이 원자성을 이루도록 따로 설정해주어야 한다.
  • 매번 메인 메모리에 접근하기 때문에 성능 저하 발생

사용성

  • 공유자원에 대한 write 스레드는 오직 1개, read 스레드 여러 개일 경우 효과적이다.
    • 이 경우는 동시성 이슈 해결 가능
  • 32비트, 64비트 변수에 효과적이다.
    • long, double(64bit 자료형)은 32비트 값에 대한 두 개의 write 동작으로 나누어 진행한다. 쓰기 연산의 원자성이 보장되지 않는다.

2. 암시적 Lock, synchronized 키워드

synchronized block에 한 스레드가 참조하는 동안 다른 스레드는 대기

예시

  1. 메서드 lock

    • 해당 메서드에 진입하는 스레드는 단 하나만 가능하다.
    class Count {
    	private int count = 0;
    
    	public synchronized int view() {
    		return count++;
    	}
    }
  2. 변수 lock

    • 해당 변수를 단 하나의 스레드만 참조 가능하다.
    • 변수는 객체여야 한다.
    class Count {
    	private Integer count = 0;
    
    	public int view() {
    		synchronized (this.count) {
    			return count++;
    		}
    	}
    }

특징

  • 동시성을 해결하는데 가장 간단하면서 쉬운 방법
  • synchronized 블록의 인자값은 원시타입이 아닌 참조타입만 사용할 수 있다.
  • 블록 진입 전 메인 메모리와 CPU 캐시 메모리 값을 동기화한다.

단점

  • 여러 스레드가 동시에 접근할 수 없고 하나의 스레드만 메서드를 실행시킬 수 있기 때문에 병렬성이 매우 낮아진다.
  • 대규모 서비스에서 키워드를 남발하면 심각한 성능 저하가 생긴다.

3. 명시적 Lock, ReentrantLock 객체

synchronized 키워드 없이 명시적으로 ReentrantLock 생성, 사용

예시

public class Application {

	public static void main(String[] args) {
		Count count = new Count();

		for (int i = 0; i < 100; i++) {
			Thread thread = new Thread(new CountThread(count));
			thread.start();
		}
	}
}
class Count {
	private int count = 0;
	private Lock lock = new ReentrantLock();

	public int view() {
		return count++;
	}

	public Lock getLock() {
		return lock;
	}
}

class CountThread implements Runnable {
	private final Count count;

	public CountThread(Count count) {
		this.count = count;
	}

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			count.getLock().lock();
			System.out.println(count.view());
			count.getLock().unlock();
		}
	}
}

특징

  • 직접 Lock 객체를 생성해서 사용한다.
  • lock() 메서드 사용하면 다른 스레드가 lock()의 시작점에 접근하지 못하고 대기한다.
    • unlock()을 실행하면 다른 메서드가 lock을 얻는다.

사용성

  • Lock 범위를 메서드 내부에서 한정하기 어려운 경우
  • 동시에 여러 Lock을 사용하고 싶은 경우

4. 스레드 안전한 패키지

자바 1.5 버전의 java.util.concurrent 동시성 제어 유틸리티 패키지

하위 패키지

  1. java.util.concurrent
    • 동시성 제어 유틸
    • 동시성 제어 적용된 Collections 클래스들
  2. java.util.concurrent.atomic
    • Boolean, Integer, Long 및 참조타입의 동시성 제어 클래스들
  3. java.util.concurrent.locks
    • 읽기, 쓰기 관련 동시성 제어 유틸

4-1. concurrent 컬렉션

종류

  • CopyOnWriteArraySet
    • 객체 목록을 반복 조회하는 연산의 성능을 최우선으로 한 병렬 컬렉션
  • ConcurrentMap
  • ConcurrentHashMap
    • 내부적으로 해시값을 이용해 락을 분할하여 사용
    • 분할 락 → 병렬성 + 성능
  • ConcurrentLinkedQueue
  • LinkedBlockingQueue
  • ConcurrentSkipListMap, ConcurrnetSKipListSet

4-2. atomic variable

원자성을 보장하는 변수. synchronized 키워드 없이 동기화문제를 해결하기 위해 고안된 방식

예시

class Count {
	private AtomicInteger count = new AtomicInteger(0);

	public int view() {
		return count.getAndIncrement();
	}
}
public class AtomicInteger extends Number implements java.io.Serializable {

  private volatile int value; 
    
  public final int incrementAndGet() { 
	int current; 
    int next;
    do { 
	    current = get();
      next = current + 1;
    } while (!compareAndSet(current, next));
    
    return next; 
  } 
  
  public final boolean compareAndSet(int expect, int update) {
  	return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
  }	
            
}
  • AtomicInteger, AtomicLong, AtomicBoolean 등

특징

  • 각 클래스의 메서드는 내부적으로 Thread-safe하게 구조화되어있다.
  • compare-and-swap(CAS) 알고리즘 사용
    • 스레드가 가지고 있는 원래값이 현재값과 같은지 비교
    • 같으면 그냥 사용, 다르면 현재값을 받아온다.

장점

  • Nonblocking 방식
    • synchronized(다른 스레드는 아무 작업을 하지 못함)보다 효율적으로 동시성을 보장한다.
  • 멀티스레드에서 write가 가능하다.
    • volatile의 문제점 해결

5. 불변 객체

한 번 만들면 그 상태가 변하지 않는 객체

특징

  • 불변 객체는 내부적인 상태가 변하지 않으므로 동시 참조해도 동시성 이슈가 발생하지 않는다.
  • 객체 상태를 변화시킬 수 있는 부분을 모두 제거해야 한다.
  • 모든 변수를 final로 선언

출처

0개의 댓글