동기화(Vector) vs 효율성(List) (ft. JAVA)

크리링·2024년 8월 7일
0

궁금증

목록 보기
1/2
post-thumbnail

자바에서 Stack은 왜 효율적이지 않을까?
호기심에서 호기심을 해결하는 과정에서의 공부를 정리해보고자 합니다.

일상적으로 가장 많이 쓰는 List와 코드를 비교해보자

Stack의 코드

List의 코드

두 코드를 비교하면 **Stack**에만 synchronized가 선언이 되어있다.
동기화 선언이 효율을 떨어뜨리게 만드는걸까?
먼저 동기화에 대해 짚고 넘어가자




Synchronized

자바에서 synchronized 키워드는
특정 메서드나 블록이 한번에 하나의 스레드만 실행될 수 있도록 보장해준다.
-> 주로 공유 자원에 대한 동시 접근을 제어하여 데이터의 일관성 유지에 사용된다.

동작 순서

  1. synchronized 키워드를 사용하면 해당 메소드나 블록의 모니터를 획득한다.
    • 스레드가 모니터를 획득하면 다른 스레드는 해당 모니터가 해제될때까지 대기해야 한다.
  2. 모니터를 획득한 스레드는 동기화된 메소드나 블록을 실행
  3. 메소드나 블록의 실행이 완료되면 모니터가 해제되고, 대기 중인 다른 스레드 중 하나가 모니터를 획득

예제로 보면

	public static class Synchronized {
		private int counter = 0;

		public synchronized void increment() {
			counter++;
		}

		public synchronized int getCounter() {
			return counter;
		}
	}

	public static class UnSynchronized {
		private int counter = 0;
		public void increment() {
			counter++;
		}

		public int getCounter() {
			return counter;
		}
	}

선언된 메소드를 만들고

	@Test
	@DisplayName("동기화 선언된 메소드 동시 접근")
	public void testSynchronized() {
		Synchronized example = new Synchronized();

		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 1000; i++) {
				example.increment();
			}
		});

		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 1000; i++) {
				example.increment();
			}
		});

		t1.start();
		t2.start();

		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		assertThat(example.getCounter()).isEqualTo(2000);
	}

위의 두개의 synchronized된 increment() 메소드를 동시에 실행하는 테스트 코드를 실행하면

2000의 결과가 옳은 것을 확인할 수 있고,

	public static class UnSynchronized {
		private int counter = 0;
		public void increment() {
			counter++;
		}

		public int getCounter() {
			return counter;
		}
	}

동기화되지 않은 메소드를 선언해주고

	@Test
	@DisplayName("동기화 선언 없는 메소드 동시 접근")
	public void testUnSynchronized() {
		UnSynchronized example = new UnSynchronized();

		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 1000; i++) {
				example.increment();
			}
		});

		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 1000; i++) {
				example.increment();
			}
		});

		t1.start();
		t2.start();

		try {
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		assertThat(example.getCounter()).isNotEqualTo(2000);
	}

위의 두개의 synchronized 선언 안 된 increment() 메소드를 동시에 실행하는 테스트 코드를 실행하면

2000이 아닌 결과가 나온 것을 확인할 수 있다.



위에서 보듯이 동기화는 데이터 일관성경쟁 상태 방지와 같은 상황에서 중요하다. 예를 들어 돈과 관련된 입출금, 이전에 공부했었던 로그 그리고 채팅 메시지 처리에도 자주 사용된다고 한다.






Vector를 오히려 써야하는거 아닐까?

최근에 본 글에서 최근의 성능이 좋아서 성능보다는 유지 보수에 용이하게 코드를 작성하는 것을 더 중요시 여긴다는 글을 본 적이 있다.

성능이 좋아 속도의 별 차이가 없다보면 Synchronized를 사용하지 않는 List 보다는 오히려 사용하는 Vector를 주로 써야되는 것이 아닐까?

  1. 예를들어 Vector가 더 안전하지만 단순한 get() 기능을 쓴다고 볼 때 모든 메소드가 동기화로 lock이 걸려 오버헤드 발생할 수 있음
  2. Vector보다 List가 편리
  3. 멀티스레드를 고려해야되는 부분에서 Concurrent 컬렉션을 사용
    -> Vector보다 더 나은 성능(관련된 부분만 Lock을 걸기 때문)과 동시성 제공




참고

자바에서 Vector와 Stack 컬렉션이 쓰이지 않는 이유
[Java] Synchronized Collection vs Concurrent Collection

0개의 댓글