TIL(2020-02-28)

roach·2021년 2월 28일
0

ConcurrentModificationException ?

  • 해당 컬렉션 객체의 일관성에 대해 비 정상적인 환경으로 될 수 있을 경우 해당 Exception 이 발생함.
  • Effective Java 에서 나온 내용은 이제 Observable 이란 코드 기반으로 이루어지는데,

Effective Java 예제

  • 이제 main에서 ObservableSet 를 생성후 element 를 add 할때 원소가 Set 에 추가되면서 이제 addNotifyElement 구독자들에게 원소가 추가됬다는 사실을 알려주기 위해서 이제 callback 함수인 added 가 발생하는데, 근데 여기서 그 람다로 정의된 함수를 보면 아래와 같이 되어 있습니다.
set.addObserver(new SetObserver<>() {
		public void added(ObservableSet<E> set, E element){
				print(element); // 귀찮아서 이렇게 적었습니다.
				if(e == 23) 
					s.removeObserver(this);
		}
});

참고 SetObservers

@FunctionalInterface
public interface SetObserver<E> {
    //Call this function when ObservableSet added element
    void added(ObservableSet<E> set, E element);
}
  • 그니까 오류가 나는 이유를 위와 같은 상황에 빗대어 봤을때 이제 added 는 콜백함수이다. 근데 지금 하나의 스레드 자체는 observers 에 lock 을 걸고 순회중인데, 갑자기 외부에서 해당 observer 에 접근이 가능하게 된것이다. 그니까 현재 notifyElementAdded 가 block synchronized 구조 인데
public void notifyElementAdded(E element) {
        synchronized (observers) {
            for (SetObserver<E> observer : observers) {
                observer.added(this, element);
            }
        }
    }

저런 구조 자체가 added 라는 callback 함수의 내부까지 synchronized 가 잡히지 않기 때문입니다. 실제로 진행해보면 이걸 Debugging 으로 하나하나씩 잡아보면, 처음에 add 에 들어오고 조건문을 거쳐 notifyElementAdded 로 가게 되고, 그 이후 observers 에 lock 을 걸고 observers 를 순회하게 되는데 이때 observer 의 added 메소드로 가게됩니다. (아직 끝나기전) 그래서 지금 메소드 스택구조를 간단하게 그려보면

----------------------
|       remove       |
----------------------
|   removeObservers  | 
----------------------
|        added       |
----------------------
| notifyElementAdded | => observers 에 lock 을 걸어주었음
----------------------
|         add        |
----------------------

인데 지금 added 에서 remove 를 호출하여 notifyElementAdded 에서 작업하고 있는 observers 에 대한 순회 도중 변경이 일어났음 따라서 ConcurrentModificationException 발생하고 종료됨.

발생이유 상세하게

우리의 경우 List 이므로 for-loop 는 Iterator 로 작동하는데 Iter 클래스는 AbstractList(or List 등등 Collection 구조에 따라다를것임) 는 처음에 modCount 변수를 자신의 지역변수인 expectedModCount 에 할당하는데 이는 modCount 에 해당 Collection 에 관한 변경사항에 따른 count 를 기록하는데 add / remove 등등 다수의 메소드에서 기록됨. 그래서 next() 를 하는데 기존 자신이 참조하고 있는 Collection 과의 변경사항에 대해 Checkt 를 하는데 이때 다르면 ConcurrentModificationException 을 발생시키는데 지금과 같은 경우는 앞에서 add 를 해줘서 modCount++ 을 해준 상태(현재 상태 1)일텐데, 마지막에 remove 가 작동하여 modCount 를 ++ (2)해줍니다. 하지만 이미 exptectModCount 는 1 이므로 다음 next() 를 하는과정에서 ConcurrentModificationException 이 발생합니다. 이거 이해안가시면 당일날 디버깅으로 보여드리겠습니다. 직접보고싶으시면 [ArrayList.java](http://arraylist.java) 에 956 번째 줄 967 번째 줄 Break Point 로 잡고 확인하시면 됩니다.

간단 지식

Enhanced for loop 는 Itreator 를 랩핑하여 사용하는 것이다.

ArrayList.java 안의 private class Itr implements Iterator 을 계속해서 호출해 오는데 우리가 처음에 add 를 해서 expecteModCount 한 값이 들어가고, 그 다음에 이제 remove 가 콜백되어 modCount++ 이 되어 오류가 나는것이다 . 음 Enhanced for loop 도 아래와 상당히 흡사하게 돌아가거나 (같을 것이므로 아래 코드를 예시로 들면) 처음에 modCount 가 들어가는데 이때는 add 가 되어 1 이 들어가있을텐데 그 도중에 remove 가 발생되어 2가 될테고, 결국 마지막 if (modCount != expectedModCount) 에 걸려서 ConcurrentModificationException 을 발생시킨다.

public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        final Object[] es = elementData;
        final int size = this.size;
        for (int i = 0; modCount == expectedModCount && i < size; i++)
            action.accept(elementAt(es, i));
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

Concurrent Collection java

  • 그래서 설마 Concurrent Hash Map 은 이 modCount 를 비교하는 로직을 없애고 다른 방식을 채용한 건가? 하고 확인해보니 modCount 가 진짜로 없었다~! 이제 약간은 아 이래서 생긴거구나 라고 감이 오기 시작한것 같다. 보다 보니까 Concurrency Level 이란게 존재한다고 한다.

읽기 좋은 글

Java ConcurrentHashMap

DB

데이터베이스 개론

  • 데이터베이스 첫걸음 완독(개인적으로 2회독 할만한 책은 아니라고 생각됨, 개론은 2회독 가능하다고 생각)
  • 요즘 공부를 많이 못한거 같음.. 데이터베이스 개론 트랜잭션 이제 곧 준비중.

외장 하드에 도커 설정

  • 오늘 드디어 선물받은 외장하드에 이제 환경을 구축해야지 하고 있는데, MAC 은 NTFS 인가 그거 인식하려면 복잡하길래 그냥 2만원 주고, 유료프로그램 하나 샀다. 항상 느끼는 거지만 오래 쓸거면 돈주고 사는게 좋은것 같다. 괜히 무료 썼다가 내 컴 이상해지면 어떡해.. 여튼 도커는 아래와 같은 명령어를 넣었다.
docker run -d -p 3307:3306 -v /Volumes/roach/docker/test:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=1234 --name testingdb mysql:8.0.17 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

Sample Data 넣기

Mysql 에서 Sample Data 를 제공하는데 Docker 안에서 해당 파일을 git 으로 다운받자

Sample Data Link

다운 받았으면 test_db 라는 디렉토리가 하나 생길 것 이다.

mysql -uroot -p<password> -t employee.sql

이런식으로 하면 무슨 뭐뭐뭐 뜨면서 막 업데이트 된다. 아 참고로 요즘 도커는 볼륨도 디폴트로 어딘가 지정해주는것 같긴하다? 옛날 버전은 날라갔었는데 근데 난 외장하드에 도커 환경을 구축하기 때문에 따로 볼륨 path 를 지정해주었다.

여튼 저 명령어를 치면 정상적으로 추가되어 있다~ DB 실습은 이렇게 해보자!

그리고 엄청 큰 샘플 데이터들도 있는데 그런거 받아서 인덱스 공부해보면 빠르게 감을 익힐 수 있다.

알고리즘 테스트

  • 알고리즘 테스트 공부는 계속하고 있는데 스터디를 구할까 고민중이다.. 근데 현재하고있는 스터디가 두개라 하나가 종료되면 어디 자리나는 곳 구해볼까 고민중이다. 부담만 안간다면 해볼텐데

코드스쿼드

  • 다음주 부터 스프링을 들어가는데 어떤 수업일지 상당히 기대된다~!
  • 스프링 미리 하기보다는 일단 자바를 조금 더 보려고 한다
profile
모든 기술에는 고민을

0개의 댓글