상세 컨텐츠

본문 제목

java "Exception in thread" sychronized method problem, 자바언어에서 동기화의 어려움 관련

JAVA

by AlrepondTech 2020. 9. 20. 02:06

본문

반응형

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

 

error:

 

Exception in thread "Timer-0" java.util.ConcurrentModificationException

        at java.util.HashMap$HashIterator.nextNode(Unknown Source)

        at java.util.HashMap$KeyIterator.next(Unknown Source)

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://so-blog.net/2015/01/04/collection_with_multi_thread/

 

 

여러개의 인스턴스를 관리하는 인터페이스나 클래스를 총칭하여 컬렉션 이라고 한다.

java.util.List 인터페이스, java.util.ArrayList 클래스 등등..

자바의 컬렉션은 대부분 thread-safety 하지 않다. 따라서 여러개의 쓰레드들이 동시에 컬렉션에 접근하는 경우에는 사용하고자 하는 컬렉션이 thread-safety 한지 아닌지 API 레퍼런스를 체크해볼 필요가 있다.

다음과 같이 세 개의 예제를 살펴본다.

  • 1) thread-safety 하지 않은 java.util.ArrayList 클래스
  • 2) Collections.synchronizedList 메소드에 의한 동기화
  • 3) cow (copy-on-write) 를 사용한 java.util.concurrent.CopyOnWriteArrayList 클래스

1) thread-safety 하지 않은 java.util.ArrayList 클래스

import java.util.List;
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {

        List<integer> list = new ArrayList<integer>();    // ArrayList는 thread-safety 하지 않음
        new WriterThread(list).start();        // WriterThread에서는 ArrayList를 write함
        new ReaderThread(list).start();        // ReaderThread에서는 ArrayList를 read함
        // ArrayList 인스턴스에 대해 read/write가 동시에 일어나므로 익셉션 발생
    }
}
import java.util.List;

public class ReaderThread extends Thread {
    private final List<integer> list;

    public ReaderThread(List<integer> list) {
        super("ReaderThread");
        this.list = list;
    }

    public void run() {
        while (true) {
            for (int n : list) {        // iterator를 이용하여 list를 read
                System.out.println(n);
            }
        }
    }
}
import java.util.List;

public class WriterThread extends Thread {
    private final List<integer> list;

    public WriterThread(List<integer> list) {
        super("WriterThread");
        this.list = list;
    }

    public void run() {
        for (int i = 0; true; i++) {
            list.add(i);     // list에 write
            list.remove(0);  // list에 write
        }
    }
}

ArrayList 클래스는 thread-safety 하지 않으므로 여러개의 쓰레드에서 동시에 읽고 쓰는 것은 안전하지 않다. ArrayList 클래스와 그 iterator는 안전성이 상실되었음을 검출하면 ConcurrentModificationException이라는 예외를 발생시킨다. 이것은 '수정이 동시에 이루어졌다'는 사실을 나타내는 런타임 익셉션이다. 또한 이 예제에서는 NullPointerException이 발생할 수 있다.

 

2) Collections.synchronizedList 메소드에 의한 동기화

java.util.ArrayList 클래스는 thread-safety 하지 않지만, Collections.synchronizedList 메소드를 이용하여 동기화하면 thread-satefy 인스턴스를 확보할 수 있다.

import java.util.List;
import java.util.ArrayList;
import java.util.Collections;

public class Main {
    public static void main(String[] args) {

        // ArrayList의 인스턴스를 synchronizedList() 메소드를 통하여 변수 list에 보관
        final List<integer> list = Collections.synchronizedList( new ArrayList<integer>() );

        new WriterThread(list).start();        // WriterThread에서는 ArrayList를 write함.  
        new ReaderThread(list).start();        // ReaderThread에서는 ArrayList를 read함
    }
}

ArrayList의 인스턴스를 Collections.synchronizedList 메소드를 통하여 변수 list에 보관한다.

import java.util.List;

public class ReaderThread extends Thread {

    private final List<integer> list;

    public ReaderThread(List<integer> list) {
        super("ReaderThread");
        this.list = list;
    }

    public void run() {
        while (true) {
            synchronized (list) {           // synchronized 블록 처리
                for (int n : list) {        // iterator를 이용하여 list를 read
                    System.out.println(n);
                }
            }
        }
    }
}

ReaderThread 클래스의 run() 메소드에서, iterator를 이용해 ArrayList 인스턴스에 read 하는 부분을 synchronized 블록으로 감싸준다. WriterThread 클래스의 run() 메소드에서 ArrayList 인스턴스에 write 하는 부분은 synchronized 블록으로 감싸줄 필요가 없다. 여기에서는 add, remove 메소드를 명시적으로 호출하기 때문이다.

이렇게 변경하면 ConcurrentModificationException, NullPointerException이 발생하지 않는다.

 

3) copy-on-write를 사용한 java.util.concurrent.CopyOnWriteArrayList 클래스

java.util.concurrent.CopyOnWriteArrayList 클래스는 thread-safety 하다. 이 클래스는 Collections.synchronizedList 메소드를 이용하여 동기화 시키는 위의 예제 2와는 달리, copy-on-write를 이용하여 충돌을 막는다.

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Main {
    public static void main(String[] args) {

        final List<integer> list = new CopyOnWriteArrayList<integer>();

        new WriterThread(list).start();
        new ReaderThread(list).start();

    }
}

 

copy-on-write는 'write 할 때 copy 한다'는 의미. 컬렉션에 대하여 write를 할 때마다, 내부에 확보된 배열을 통째로 복사한다. 이렇게 통째로 복사를 하면 iterator를 사용하여 element들을 순서대로 읽어가는 도중에 element가 변경될 염려가 없다. 따라서 CopyOnWriteArrayList 클래스와 그 iterator가 ConcurrentModificationException을 발생시키는 일은 절대 없다. 단 write를 할 때마다 배열을 통째로 copy 하므로, write가 잦은 경우 성능이 저하될 수 있다. write가 적고 read가 빈번한 경우에 좋다.


References: "Java 언어로 배우는 디자인 패턴 입문 - 멀티쓰레드편" Introduction 02

 

 

 

=================================

=================================

=================================

 

 

출처: http://okky.kr/article/279692

 


"멀티쓰레드 개발은 언어 무관하게 무지 어렵다. 세계 최고 개발자의 할아버지가 와도 어렵다." 

 

 

요즘 "폴리글랏 프로그래밍" 이 유행하고있습니다. 여러개의 언어를 적재적소에 사용해서 생산성 및 품질

을 올리자 뭐 이쯤되겠지요. 설사 주력언어 이외에 다른언어를 전혀 사용하지 않더라도, 다른 언어를

공부하는것은 주력언어에 에 대한 이해의 폭을 상당히 넓혀주므로 틈틈히 다른 언어를 공부하는건 굉장히

바람직한 일이 될것입니다. 따라서 " 7 가지 언어를 7주에 마스터"  같은 책 (http://www.yes24.com/24/goods/17568694?scode=032&OzSrank=1 도 인기가 있는것이겠구요.

 

하지만 언어를 배운다는것은 정말 힘든일이라고 생각되는게,  지금 쓰고있는 "자바언어에서 동기화의

어려움" 를 읽어보면 아시겠지만, 해당 언어에 대한 경험이 많지 않다면 실수하기 쉬운 문제가 도처에

도사리고 있습니다. 그 이슈를 실전에서 대처하려면 ,실수에 의한 경험도 필요하고, 가끔은 소스의 내부를

철저히 조사해봐야하는 수고를 해야하는데, 언어를 배우는것도 힘든데 저런부분까지 신경쓰려면 고난의

행군은 각오해야할거 같습니다. 

 

필자도 경력의 대부분을 C++  언어와 함께 하였기때문에 , 최근에 자바,파이썬,자바스크립트등으로

개발할때 많은 실수를 하게되는데 그때마다  제대로된 제품하나 만들기가 정말 너무 어렵구나..

남들이 쉽게 생각할지는 몰라도 소프트웨어 개발이라는게 정말 빡이 세구나 하는걸 많이 느끼게 됩니다.

 

자 아래에 써내려갈 야그들은 아마 자바고수님들이라면 다 알고 계실 오래된 내용일거 같습니다. 

하지만 다시 이렇게 정리해 보는것은 저 처럼 자바에 익숙치 않거나 , 초중급분들에겐 충분히 가치있는 내용이

될거 같아서 중복이겠지만 다시 되풀이해 보는시간을 가지려합니다.  

그리고 아래 Actor / Akka  게시글과 연관성도 있을듯 하네요. (http://okky.kr/article/279263

 


 

1. Collections.synchronizedList 이야기 

 

보통 우리는 Vector 대신해서 ArrayList 를 사용하라는 말을 듣곤합니다.  Vector 는 동기화되어진 함수로 가득차있기때문에 싱글쓰레드 프로그램에서 효율적이지 않다라는거지요. 

따라서 멀티쓰레드 프로그램을 짤때 ArrayList 를 사용할 경우 , 쓰레드문제에 대해서 신경을 써줘야하는데 , 선택할 2가지방법은 ArrayList 의 함수를 사용할때마다 적절한 동기화를 직접처리해주거나 Collections.synchronizedList 과 같은 함수를 사용하는 방법이 있습니다.

 

Collections.synchronizedList 사용법은 다음과 같습니다.

List list = Collections.synchronizedList(new ArrayList());

 

일단 아 저렇게 쓰면 쓰레드문제는 이제 신경안써도 되겠군~ 아싸~~!!! 하는 순간에 뒤통수 제대로 맞는거지요. 저렇게 되면 일단 함수하나를 사용할때는 락이 걸려서 중복호출이 되지 않겠지만, 여러함수가 호출될때 문제가 생길수있습니다.

 

다음과 같이

final List<String> list = Collections.synchronizedList(new ArrayList<String>());

final int nThreads = 2;

ExecutorService es = Executors.newFixedThreadPool(nThreads);

for (int i = 0; i < nThreads; i++) {

    es.execute(new Runnable() {

        public void run() {

            while(true) {

                try {

                    list.clear();

                    list.add("888");

                    list.remove(0);

                } catch(IndexOutOfBoundsException ioobe) {

                    ioobe.printStackTrace();

                }

            }

        }

    });

}

 

위의 코드를 실행하면 , Thread A 가 remove(0) 을 하는 순간에 Thread B 가 clear() 를 한다면 ,, 꽝~~

remove 할것이 없는데 remove 를 하려니 문제가 생길수밖에요.  이럴땐 

 synchronized (list) {

    list.clear();

    list.add("888");

    list.remove(0);

}

이렇게 함수들을 묶어서 동기화를 시켜줘야합니다. 

따라서 단지 Collections.synchronizedList  를 쓴다고해서 동기화 문제를 회피할수있지 않다는 얘기입니다.

 

 

2. Threadsafe Iteration & ConcurrentModificationException 이야기 

 

컬렉션 객체를 사용할때 우리는 동기화에 대해서 굉장히 불분명할때가 많은데요. 그리고 예상치 못한 문제가 생기기도 합니다. 얼마전에 생긴문제인데요. 

final List<String> list = new ArrayList<String>();

list.add("A");

list.add("B");

list.add("C");

for(String s : list) {

    if(s.equals("A")) {

        list.remove(s);

    }

}

위의 코드를 실행시키면 어떻게 될까요??  

ConcurrentModificationException 예외가 발생합니다. 정확히는 remove 끝내고 다시 위로 올라간후에

list 로 부터 다음것을 할당받는순간에 발생합니다.  (내부에서 next() 함수 호출될때) 

 

솔직히 저런 코드에서 이런 예외가 발생할수있다는걸 예상하는건  어렵습니다. 너무나 뻔해보이는

코드이고 설마 내부에서 해결해주지 않았겠어 하는 마음때문인데요. 저게 왜 예외가 발생하는지 알려면

소스 내부를 살펴봐야합니다.

 

내부를 살펴보겠습니다.!!

modCount 를 주의깊게 살펴봐주세요 !!!

 

ArrayList 의 add 함수 입니다.

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} private void ensureExplicitCapacity(int minCapacity) { modCount++; .... }

modCount 를 하나 증가시키고 있습니다.  위의 소스에서 add 를 3번하니깐 3이 되었겠네요. size 는 3 이 됩니다.

 

다음은  list.remove(s); 함수를 살펴보겠습니다.

private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

역시나 modCount 를 하나 증가시키고 있습니다. 이때 modeCount 는 4가 됩니다.  size 는 2로 줄어듭니다.

 

modCount가 늘어나고 size  가 줄어들기전인 처음 for  문이 시작되는 상황으로 돌아가서   for(String s : list)   부분을  살펴봅시다.

list 에서 s 객체를 가져오기 위해서  처음에는 Iterator 객체를 생성합니다.

이때 Iterator 객체는 list 객체의 값 (modeCount) 를 할당받습니다.

public Iterator<E> iterator() {
    return new Itr();
}
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

 

이렇게  두 값은 3이 됨을 알수있습니다. CURSOR = 0  이 됩니다.

 

다음으로는  hasNext 를 호출하여 순회가능한지 확인합니다.

public boolean hasNext() {
return cursor != size;
}

 

size 는 3일것이고 CURSOR = 0  이기때문에 순회가능합니다.

이후에 modCount 가 4 가되었고. 다시 위(for  문 시작) 로 올라가봅시다.

자 대망의 next() 함수입니다. 순회가능하기때문에 해당차례의 객체를 얻기위하여 next() 를 호출합니다.

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

 

자 여기 checkForComodification() 함수를 잘보시면 modCount != expectedModCount 다르면 예외~ 뿅!!

위에 remove 함수쪽을 다시 올라가서 보시면 remove 하면서 modeCount 는 4가 되었고, size 는 2가 

되었는데, Iter 객체의 expectedModCount  는 여전히 3입니다. 먼가 수정이 이루어질때 이 변수는 변하지

않는겁니다. 그래서 예외가 발생하는거지요.  여기서  눈치챈분도 있겠지만 remove 뿐만아니라 add 해도 

예외가 발생합니다. 먼가 수정한것이기때문입니다.

 

그래서 저런 수정이 있을경우 대신해서 다음과 같이 작성합니다.

for(Iterator<String> iter = list.iterator(); iter.hasNext();) {

    String s = iter.next();

    if(s.equals("Test1")) {

        iter.remove();

    }

}

 

예외가 발생하지 않습니다.  이유는 다음 코드와 같이

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();

    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

iter 객체의 remove 에서는 expectedModCount = modCount; 를 같게끔 변경해주기때문입니다.

 

자 여기까지는 싱글쓰레드 얘기였습니다. 

이제 여기에 두개의 쓰레드가 동시에 컬렉션을 건드린다고 생각해봅시다...끔찍합니다. 그래서 akka 니 무상태 지향 코딩이니 같은것들이 나오는건가 봅니다.

설사 아래와 같이 foreach 를 쓰지않고 iterator 를 쓴다고 해도 멀티쓰레드상에서는 컬렉션의 아이템 수정이 가해지면 헬이됩니다.  여러모로 ConcurrentModificationException  예외는 멀티쓰레드 프로그래밍에서 중요한 예외가 될것입니다. 

 

 

3. Volatile  실패 이야기 

가끔 ConcurrentModificationException   예외는 의도치 않게 행동을 합니다.

public E next() {
    if (modCount != expectedModCount)
      throw new ConcurrentModificationException();
      .....
}

 

 

위의 소스를 보면 내부 상태가 불일치 할때 ConcurrentModificationException  를 던지기로 되있는데요.

황당하게 어떤때는  예외를 안던지는 경우가 있습니다.  

A의 쓰레드가 next 를 호출하고 있고, B 쓰레드는 modCount 를 수정하고있습니다. 이때 위에 살펴본것과

마찬가지로 예외가 발생해야하는데 예외가 발생하지 않습니다. B 쓰레드가 수정한 modeCount 가 A 쓰레드

의 메모리공간에서는 변경되지 않았기때문인데요. 이때 떠오르는 생각이 , 최신의 변수만을 적용하고

싶을때 변수에 붙히는 키워드가 멀까요? 

그렇습니다. Volatile 인데요. (http://tutorials.jenkov.com/java-concurrency/volatile.html 참고) 

Volatile 이 붙어있는지 확인했더니. 없습니다. 조슈아 블로흐(이펙티브 자바 저자 & 자바 아키텍트) 가 정신을

딴데 팔고 API 를 개발했던것일까요?  그건 아닙니다.  volatile 은 동기화에 그닥 도움을 줄수없습니다. 

updateness 는 보장해도 atomicity 는 보장하지 않습니다.Volatile 을 붙혀놔도  operator (++) 가 atomic

하지 않습니다.

 

4. 그래서 어떻게 하면 되냐 OTL 

 이때 우리의 구원자 Dug Lea 가 나타나주셨습니다. akka 라이브러리를 만든사람인데요. 그 분은 

자바에 concurrent collection API 를 추가해 주셨습니다. (꽤 오래전 얘기죠?)  공부해야합니다. :-) 

http://javarevisited.blogspot.kr/2013/02/concurrent-collections-from-jdk-56-java-example-tutorial.html

 

 

5. 주요 컬렉션 이야기

이 게시물이 너무 길어지기때문에 간단하게 정리합니다.

 

CopyOnWriteArrayList 

CopyOnWrite 가 말해주는것처럼 read (select) 시는 아무런 동기화 문제가 없기때문에 놔두고 

변경이 일어날경우 객체를 clone 해서 다루자는 전략입니다. 따라서 읽기행위가 많이 일어나는 

곳에서 사용하기 좋습니다. 위의 예제에서도 ArrayList 를 이거로 바꾸면 예외가 발생하지 않습니다.

 

BlockingQueue 

보통 생산자 - 소비자 패턴에서 활용되는 큐로 많이 사용된다. 사실 이야기는 이 큐는 멀티쓰레드환경에서 

대표할만한 컬렉션이라는 것이다. 전에 Actor / Akka 문서에 말한 큐같은것들이 대부분 이것으로 이루어져있다. 

소비자가 꺼내어 사용할동안 생산자는 멈춰있고, 생산자가 넣을동안 소비자는 멈춰있어야한다.

서로 쟁탈하면 선반은 망가질것이다.

 

ConcurrentHashMap

ConcurrentHashMap은 Map의 일부에만 Lock을 걸기때문에 HashTable과 synchronized Map 보다 

효율적인게 특징이다.

 

 

참고 

http://rayfd.me/2007/11/11/when-a-synchronized-class-isnt-threadsafe/

https://www.ibm.com/developerworks/community/blogs/738b7897-cd38-4f24-9f05-48dd69116837/entry/synchronized_collections_vs_concurrent_collections7?lang=en

자바병렬프로그래밍 http://www.yes24.com/24/goods/3015162?scode=032&OzSrank=1

 

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040201&docId=67094557&qb=Q29sbGVjdGlvbnMuc3luY2hyb25pemVk&enc=utf8§ion=kin&rank=1&search_sort=0&spq=0&pid=S/hTOloRR1Rssvju/XGsssssssR-487402&sid=5H8inb7UnT7Mu6BGzafHjA%3D%3D

 

collection에서의 동기화 방법

 

collection에서의 동기화 방법이 있다고 들었는데..

 

예를 들어 ArrayList에서는

List AccountList = Collections.synchronizedList(new ArrayList());

이렇게 하라고 들었는데 단순히 이렇게 하면 해당 리스트에 대해 동기화가 되는지 궁금합니다...

그런데 자바 API문서를 보면

 synchronized(list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }


이부분도 있는데...동기화 하는데 어떤 기능을 하는지 알고 싶습니다..

제가 보기에는 단순히 블럭안에만 동기화 시키는거 같은데요...

 

그리고 간단히 ArrayList에대해 동기화 예제좀 만들어 주세요..

 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

Collections의 synchronizedList메서드는

받은 List 객체가 instanceof RandomAccess 일 때

SynchronizedRandomAccessList 객체로 변환해서 반환하고

아닐 때는 SynchronizedList객체로 변환해서 반환합니다.

 

SynchronizedRandomAccessList 나 SynchronizedList는

SynchronizedList를 상속하는 클래스인데

SynchronizedList는 SynchronizedCollection를 상속하는 클래스입니다.

SynchronizedList는 생성시 부모인 SynchronizedCollection의 생성자를 호출하고

이 생성자는 mutax = this;라는 코드를 담고 있습니다.

mutax는 Object형 변수입니다.

 

이걸 말씀 드리는 이유는

SynchronizedList 클래스의

모든 메서드가 mutax를 통해

동기화되기 때문입니다.

 

자세히는 잘 모르겠으나 제 생각에 API 문서에 나오기를

"synchronizedList메서드로 부터 반환된 객체를 iterating 할 때는

좋게 말로할 때 니가 직접 동기화해야된다."(완역 ^^;)

라고 하는 것을 보면 iterator와 관련된 부분에 한해서

동기화가 따로 필요한 것으로 보입니다.

 

결론적으로,

Collections.synchronizedList메서드의 인수로 넣은 List는

동기화된 List가 되어 반환되는데

그 List를 iterating 할 때만

따로 사용자가 동기화를 해주어야 한다~ 라는 것으로 보입니다.

 

해당 글은 지식스폰서가 활동 기간 (04년~08년 6월 종료)중에 작성한 글 입니다.

  • 지식스폰서가 작성한 답변은 본문 내 자기소개 및 출처란의 실명, 상호명, URL표시를 허용합니다.
  • 출처란에 표시된 정보가 지식iN 운영원칙에 위배되는 음란성, 불법성, 청소년 유해사이트 등으로 변질된 경우는 허용이 되지 않습니다.

지식스폰서란

 

 

 

 

=================================

=================================

=================================

 

 

 

 

출처: https://community.oracle.com/thread/1142625

 

Hi experts,

I was trying to use Timer to schedule a regular task. The code is as follows. When I ran the code, I got java.util.ConcurrentModificationException occasionally. It indicates that the the keys are changed when enumerating the keyset of registeredListeners in the run method. I did put the synchronized block but it seems not working as I expected. Advices needed.

Thanks

 public static class BasicSystemTimer extends TimerTask implements SystemTimer {

        protected static Logger logger = Logger.getLogger(BasicSystemTimer.class);

        @Override
        public void run() {
            totalTick += interval;

            // Only go through the listeners when there is any listener ready to go
            if (totalTick == nextNotificationTick) {
                // Calculate earliest next schedule time
                long minTickToGo = Long.MAX_VALUE;
                synchronized (this) {
                    Set<Long> keys = registeredListeners.keySet();
                    for (Long key : keys) {
                        long tickToGo = totalTick % key.longValue();
                        if (tickToGo == 0) {
                            ArrayList<TimeNotificationListener> listenGroup = registeredListeners.get(key);
                            for (int i = listenGroup.size() - 1; i >= 0; i--) {
                                TimeNotificationListener listener = listenGroup.get(i);
                                listener.timeTicked(totalTick);

                                if (!listener.isRecursive()) {
                                    listenGroup.remove(listener);
                                }
                            }
                        }

                        tickToGo = tickToGo == 0 ? key.longValue() : tickToGo;

                        if (minTickToGo > tickToGo) {
                            minTickToGo = tickToGo;
                        }
                    }
                }
                nextNotificationTick += minTickToGo;
            }

            logger.info("Current tick = " + totalTick);
        }
        protected HashMap<Long, ArrayList<TimeNotificationListener>> registeredListeners = new HashMap<Long, ArrayList<TimeNotificationListener>>();
        protected int speed = 1;
        protected long totalTick = 0;
        protected Timer timer = new Timer();
        protected long interval = 1;

        /**
         * Get the value of speed
         *
         * @return the value of speed
         */
        public int getSpeed() {
            return speed;
        }

        /**
         * Set the value of speed
         *
         * @param speed new value of speed
         */
        public void setSpeed(int speed) {
            this.speed = speed > 100 ? 100 : speed;
        }

        public long getInterval() {
            return interval;
        }

        public void setInterval(long interval) {
            this.interval = interval;
        }
        private long nextNotificationTick = Long.MAX_VALUE;

        public void registerNotificate(TimeNotificationListener listener) {
            Long key = new Long(listener.getDelay());
            ArrayList<TimeNotificationListener> listenGroup = null;

            synchronized (this) {
                if (registeredListeners.containsKey(key)) {
                    listenGroup = registeredListeners.get(key);
                } else {
                    listenGroup = new ArrayList<TimeNotificationListener>();
                    registeredListeners.put(key, listenGroup);

                    if (key.longValue() < nextNotificationTick && key.longValue() > totalTick) {
                        nextNotificationTick = key.longValue();
                    }
                }
                listenGroup.add(listener);
            }
        }

        public void start() {
            logger.info("Simulation is about to start...");
            long period = 100 / speed;
            timer.scheduleAtFixedRate(this, 100, period);
        }

        public void stop() {
            logger.info("Simulation is about to stop...");
            timer.cancel();
        }
    }

 

  • 294 보기
  • 태그:

평균 사용자 등급: 평가 없음 (0 등급)

평균 사용자 등급

평가 없음

(0 등급)

listenGroup.remove(listener);
    • Was removing from the collection that you were iterating over. 

      Can you post the exact stacktrace?
    • 4. Re: sychronized method problem.. do also note that your code can fail if e.g. listener.timeTicked(totalTick) invokes something that invokes registerNotificate on the same thread.

      Kaj
    • 791266 2010. 9. 20 오후 1:57 (791266에 대한 응답)
    • 5. Re: sychronized method problemException in thread "Timer-0" java.util.ConcurrentModificationException          java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)          java.util.HashMap$KeyIterator.next(HashMap.java:828)        strategysim.util.SimpleFactory$BasicSystemTimer.run(SimpleFactory.java:77)        java.util.TimerThread.mainLoop(Timer.java:512)        java.util.TimerThread.run(Timer.java:462)SimpleFactory.java:77 is the line "for (Long key : keys)". 

      I added a log before this line. The program didn't crash after calling this.listenGroup.remove(listener);Edited by: tmdfan on 21/09/2010 00:05
    • 843790 2010. 9. 20 오후 2:07 (791266에 대한 응답)
    • 6. Re: sychronized method problem".. do also note that your code can fail if e.g. listener.timeTicked(totalTick) invokes something that invokes registerNotificate on the same thread."

      Sorry, I don't quite understand. Could you please explain? Thanks.
    • 843790 2010. 9. 20 오후 2:02 (791266에 대한 응답)
    • 7. Re: sychronized method problemThis can happen.

      1) Your thread, let's call it thread A, acquires the the lock in BasicSystemTimer.run()

      2) You iterate over the registeredListeners.keySet()

      3) You then invoke listener.timeTicked(totalTick);

      I don't know what timeTicked does, but this will happen if it invokes registerNotificate with a new listener.

      4) The thread (that still is thread A if you haven't done a thread switch) sees that it already has a lock on this

      5) registeredListeners.put(key, listenGroup); is invoked, and that is the part that is causing your concurrent modification exception in the iteration in 2)
    • 791266 2010. 9. 20 오후 2:12 (843790에 대한 응답)
    • 8. Re: sychronized method problemWow. That's exactly what the problem is. I was a bit naive about the concurrency issue. Thanks a lot.
    • 843790 2010. 9. 20 오후 2:16 (791266에 대한 응답)
    • 9. Re: sychronized method problem
    • 791266 2010. 9. 20 오후 2:20 (843790에 대한 응답)
tmdfan wrote:
Wow. That's exactly what the problem is. I was a bit naive about the concurrency issue. Thanks a lot.
  • It's a quite common mistake :)

 

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://suein1209.tistory.com/454

 

java.util.ConcurrentModificationException

 

요런 에러가 있다.

이 에러는 동기화 관련된 에러이다.

그러니깐 현재 데이터에 대해서 무결성이 입증이 되지 않을때 발생을 한다.

다음과 같은 예제가 있다.

 

public class Test {

	public static void main(String[] args) {

		List<Integer> k = new ArrayList<Integer>();
		
		for(int i=0; i<10; i++)
			k.add(i);
		
		for (Integer integer : k) {
			System.out.println(integer);
			
			if(integer == 6){
				System.out.println("같은건 있네요");
				k.remove(integer);
			}
		}
		
		System.out.println("");
		
		for (Integer integer : k) {
			System.out.println(integer);
		}
	}
}

 

결과

0

1

2

3

4

5

6

같은건 있네요

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.AbstractList$Itr.checkForComodification(Unknown Source)

at java.util.AbstractList$Itr.next(Unknown Source)

at Nove14.Test.main(Test.java:15)

 

실행하면 요런 에러가 난다.

 

이유는 저 k.remove(integer); 때문에 발생했다.

 

for - each 문으로 k 리스트를 참조 하고 있는데 도중에 지워주게 되면 for - each 문으로 참조하는 k 리스트가 깨지게

 

된다. 이렇게 되면 병렬처리 관련 에러가 발생하게 되는것이다.

 

근데 위의 예제에서 재미있는건... ArrayList 는 동기화 처리가 안돼 있는 것으로 알려져 있는데

 

에러가 발생하는거 보니 for - each 문에서 사용되는 인덱스 처리에서 문제가 발생하는 것 같다.

 

 

자~ 그럼 이번에는 이 상황이 MAP 에서는 어떨때 일어나는지 확인하자

public class IterTest {
	
	Map<Integer, String> testMap1;
	Map<Integer, String> testMap2;
	
	public void removeFromKeyObj() {

		Iterator<Integer> itKey = testMap1.keySet().iterator();

		while (itKey.hasNext()) {

			itKey.next();
			itKey.remove();

		}
		
		System.out.println(" TestMap1 Size : "+testMap1.size());
	}
	
	public void removeFromKeyFromLoop() {

		Iterator<Integer> itKey = testMap2.keySet().iterator();

		while (itKey.hasNext()) {
			
			Integer index = itKey.next();
			testMap2.remove(index);

		}
		
		System.out.println(" TestMap2 Size : "+testMap2.size());
	}
	
	public void initTable() {
		
		this.testMap1 = new HashMap<Integer, String>();
		this.testMap2 = new HashMap<Integer, String>();
		

		for (int i = 0; i < 100; i++) {
			testMap1.put(i, i+"우헤");
			testMap2.put(i+100, (i+100)+"우헤");
		}
		
	}
	
	public static void main(String[] args) {
		
		IterTest test = new IterTest();
		test.initTable();
		test.removeFromKeyObj();
		test.removeFromKeyFromLoop();
	}

}

 

removeFromKeyObj 메소드와 removeFromKeyFromLoop 메소드를 실행했을때 어느것이

동기화 에러를 발생시킬까요~

 

 TestMap1 Size : 0

Exception in thread "main" java.util.ConcurrentModificationException

at java.util.HashMap$HashIterator.nextEntry(Unknown Source)

at java.util.HashMap$KeyIterator.next(Unknown Source)

at nioTest.IterTest.removeFromKeyFromLoop(IterTest.java:32)

at nioTest.IterTest.main(IterTest.java:58)

 

 

 

 

=================================

=================================

=================================

 

 

출처: https://coderanch.com/t/233932/threads/java/deal-Concurrent-Modification-Exception

How to deal with Concurrent Modification Exception

 

It's a combination of things - it's because you're using the Iterator at the same time another thread is modifying the list, and there's apparently no synchronization (or perhaps, insufficient or incorrect synchronization) to prevent these events

 from occurring near-simultaneously.

One solution is to synchronize all access to the List, using the list itself as the monitor:

?

synchronized (list) {
    for (Iterator it = list.iterator(); it.hashNext(); ) {
        Foo f = (Foo) it.next();
        // do what you need with f
    }
}


Whatever other threads are modifying the list, they need to be synchronized too, also on the list. E.g.:

?

syncronized (list) {
    list.add(new Foo());
}


or

?

syncronized (list) {
    list.remove(oldFoo);
}


Now this may be undesirable if the list is long and performance is critical, because if it takes a long time to iterate through

 the list, other threads are also blocked while waiting for this to complete. There are various alternatives. One is to use aCopyOnWriteArrayList. This is good if writes are fairly infrequent. You may also be able to revise your code in a way that

 it is not necessary to iterate an entire list. You may be able to use other data structures instead, such as ConcurrentHashMap orConcurrentLinkedQueue. It's hard to say what would be most suitable here without more details

 on what you're doing.

"I'm not back." - Bill Harding, Twister

 

Yes, that should be correct. If you're seeing this exception, then probably one thread is not done initializing the list 

before another thread checks it. Even if common sense is telling you it really should be done. Threads can do some 

strange things.

One approach is something like this:

?

class MyClass {
    private final List myList = makeList();
  
    private static list makeList() {
        List list = new ArrayList();
        // do what you need to initialize this
        return Collections.unmodifiableList(list);
    }
  
    public List getList() {
       return myList;
    }
}


Note that when you initialize the list, no other thread can possibly know about it yet. "list" is just a local variable,

 while "myList" does not yet know anything about the new list. Only after it's initialized does myList get the 

reference to the list. The fact that myList is declared final here is actually important for subtle reasons - 

otherwise the compiler or JIT may perform some weird optimizations that you only notice when multiple

 threads are involved. And then only rarely - but when they happen, they're a real pain to track down. 

If making the variable final is not an option for you, there are other strategies. Maybe make it volatile - 

but then you have to worry about whether it's null, before you do anything with it. As it is now (with final), 

that's not possible.

For more extended discussion of this and many related threading issues, I heartily recommend Java Concurrency

 in Practice, an excellent use of time and money for any multithreaded programmer (at least if they're already

 pretty comfortable with the threading topics covered in the Java Tutorial).
[ August 29, 2007: Message edited by: Jim Yingst ]

"I'm not back." - Bill Harding, Twister

 

Hello, 

I have a method which retrieves a java.util.List of objects for display on the UI and this method is always access concurrently by hundred or even thousands of threads at the same time(high concurrency), 
however if certain condition is true I need to remove some objects from that List before it is returned by that method, since this method has a very high usage I must take performance into account therefore 
I can't use synchronized block in the method, I just want some confirmations I can't use Iterator.remove in a multi-thread situation but there should be no performance difference in directly looping through the 
list using 
(for int i=0; i<List.size(); i++) {List.get(i)} 
and with 
for (final Iterator i = temp.iterator(); i.hasNext();) , 
I have attached the following code snippets 

?

public List getOrderedChildOfferProducts() {
//getDisplayableChildOfferProductInstances method retrieves a List of displayable products from the database through an ORM framework
        final List temp = new ArrayList(this.getDisplayableChildOfferProductInstances());
        final HttpSession session = request.getSession();
        final Plan currentPlan = (Plan)session.getObject("plan");
        if (currentPlan != null && !temp.isEmpty()) {
            final List products = currentPlan.getAllProducts();
            for (final Iterator i = temp.iterator(); i.hasNext();) {
              final Product product = (Product)i.next();
              if (!products.contains(product)) {
                i.remove();
              }
            }
        }
        Sort.sort(temp, "displayOrder", true);
        return Collections.unmodifiableList(temp);
}



I want to confirm here in a multithreaded situation the i.remove() would not work in the above method? 

my currently solution is as follows and would like to know if are there any advises to better improve this method for better efficiency since it is a critical method 

?

public List getOrderedChildOfferProducts() {
//getDisplayableChildOfferProductInstances method retrieves a List of displayable products from the database through an ORM framework
        final List temp = new ArrayList(this.getDisplayableChildOfferProductInstances());
        final HttpSession session = request.getSession();
        final Plan currentPlan = (Plan)session.getObject("plan");
        if (currentPlan != null && !temp.isEmpty()) {
            final List removableProducts = new ArrayList();
            final List products = currentPlan.getAllProducts();
            for (final Iterator i = temp.iterator(); i.hasNext();) {
              final Product product = (Product)i.next();
              if (!products.contains(product)) {
                removableProducts.add(op);
              }
            }
            temp.removeAll(removableProducts);
        }
        Sort.sort(temp, "displayOrder", true);
        return Collections.unmodifiableList(temp);
}

>

 

 

 

=================================

=================================

=================================

 

 

 

Hallo,
ich habe eine Server-Applikation mit zwei Threads. Der eine Thread führt
zusammengefasst Datenbankabfragen für die Clients aus, während der
andere Thread die Clients managed. Die Clients holen sich die Daten aus
den Objekten, die vom Datenbank-Thread gefüllt werden. Dies geschieht
hier in updateAgentData().

//aufgerufen vom Datenbank-Thread
private void updateAgentData() {
Iterator<Map.Entry<String, Agent>> it =
agent_map.entrySet().iterator();

Agent agent;
String agent_id = "";

while (it.hasNext()) {
Map.Entry<String, Agent> entry = it.next();

agent_id = entry.getKey();
agent = entry.getValue();

...mach was mit Objekt "agent"


Wenn sich genau zu diesem Zeitpunkt ein Client abmeldet, wird das Objekt
Agent über den Client-Thread während der Iteration gelöscht
(agent_map.remove(agent_id)). Obwohl ich die Map agent_map mit
synchronized angelegt habe,

private static Map<String, Agent> agent_map =
Collections.synchronizedMap(new HashMap<String, Agent>());

erhalte ich dennoch den Ausnahmefehler ConcurrentModificationException

Exception in thread "Timer-0" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:810)
at java.util.HashMap$EntryIterator.next(HashMap.java:851)
at java.util.HashMap$EntryIterator.next(HashMap.java:849)

Warum? Ich dachte, dass Modifikationen an einer Liste während einer
Iteration so verhindert werden?

 

 

--------------------------------------------------------------------------

 

Wie sollte das in der Praxis sinnvoll bewerkstelligt werden?

| It is imperative that the user manually synchronize on the returned
| map when iterating over any of its collection views:
|
Map m = Collections.synchronizedMap(new HashMap());
| ...
| Set s = m.keySet(); // Needn't be in synchronized block
| ...
| synchronized(m) { // Synchronizing on m, not s!
| Iterator i = s.iterator(); // Must be in synchronized block
| while (i.hasNext())
| foo(i.next());
| }
[API-Doc zu Collections#synchronizedMap()]

Die bessere Loesung waere hier wohl ein interner Iterator - in Java mit
brauchbarer Syntax voraussichtlich um das Jahr 2058 zu haben. :/

Viele Gruesse,
Patrick

 

 

 

 

 

=================================

=================================

=================================

 

 

출처: https://coderanch.com/t/537334/threads/java/ConcurrentModificationException-synchronized-method

 

 

Hi, 

I'm iterating an Arraylist inside a synchronized method, and I've set the Arraylist synchronized inside the method, but I still got the error ConcurrentModificationException. Why this is happening? 

?

private synchronized TaskInProgress findTaskFromList(
            Collection<TaskInProgress> tips,
            TaskTrackerStatus ttStatus,
            int numUniqueHosts,
            boolean removeFailedTip) {


        synchronized(tips) {
            Iterator<TaskInProgress> iter = tips.iterator();


            while (iter.hasNext()) {
                TaskInProgress tip = iter.next();
                        }
               }
}



?

java.io.IOException: java.util.ConcurrentModificationException
        at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
        at java.util.AbstractList$Itr.next(AbstractList.java:343)
        at org.apache.hadoop.mapred.JobInProgress.findTaskFromList(JobInProgress.java:1930)
        at org.apache.hadoop.mapred.JobInProgress.findNewMapTask(JobInProgress.java:2143)
        at org.apache.hadoop.mapred.JobInProgress.obtainNewLocalMapTask(JobInProgress.java:1268)
        at org.apache.hadoop.mapred.JobQueueTaskScheduler.assignTasks(JobQueueTaskScheduler.java:163)
        at org.apache.hadoop.mapred.JobTracker.heartbeat(JobTracker.java:2576)
        at sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:508)
        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:959)
        at org.apache.hadoop.ipc.Server$Handler$1.run(Server.java:955)
        at java.security.AccessController.doPrivileged(Native Method)
        at javax.security.auth.Subject.doAs(Subject.java:396)
        at org.apache.hadoop.ipc.Server$Handler.run(Server.java:953)

Thanks,

 


Even if the problem is caused by access from multiple threads, simply using a synchronized ArrayList (presumably from Collections.synchronizedList()) or Vector is unlikely to solve the problem - at least not by itself. These classes do not provide synchronization at the right level to solve most problems, including this one. Specifically when you iterate through the list, you need additional synchronization. From the API for Collections.synchronizedList(): 

It is imperative that the user manually synchronize on the returned list when iterating over it: 

?

List list = Collections.synchronizedList(new ArrayList());
    ...
synchronized(list) {
    Iterator i = list.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
}


Failure to follow this advice may result in non-deterministic behavior.


And frankly there are plenty of other times that you need additional synchronization to get proper thread-safe behavior from a synchronizedList() or Vector. These classes cannot generally be trusted to automatically provide thread safety - the user still needs to understand synchronization and use it judiciously.

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://effectiveprogramming.tistory.com/entry/Decorator-%ED%8C%A8%ED%84%B4synchronizedList%EC%9D%98-%EA%B5%AC%ED%98%84-%ED%8C%A8%ED%84%B4

 

 

Decorator 패턴(synchronizedList의 구현 패턴)

5.디자인패턴 2016.08.23 19:31

Decorator 패턴은 동일한 타입의 객체를 품고 있는 패턴이다.

Decorator 패턴은 기본적인 기능을 구현한 클래스를 인자로 받아서 추가된 기능을 구현한 객체가 이용함으로써 기능의 확장이나 변경을 수행하는 패턴이다. 이 패턴의 장점은 동적으로 기능의 추가 제거가 가능하고, 기능을 구현하는 클래스들을 분리함으로써 수정이 용이해진다는 점이다. 마치 기본 제품에 포장지나 외부 디자인을 살짝 변경해 줌으로써 새로운 기능을 부여하는 것과 같다고 해서 이 명칭이 붙었다.

 

해결하고자 하는 문제

일단 다음과 같은 데이터가 있다고 가정하자.

int data;

개발자들에게 주어진 미션은 다음과 같다.

    1. data를 멀티쓰레드 환경에서 사용할 수 있도록 구현할 것. 즉 동시성을 만족할 것.

    2. data의 사용 환경이 싱글쓰레드일 경우 성능을 우선시 할 것.(즉 동시성 만족을 위한 코드를 제거할 것)

 

이런 문제를 처음 접한 사람들은 상당한 난감함에 빠질 것이다. 데이터의 동시성 코드를 구현함과 동시에 동시성을 구현하지 않은 코드도 구현하라니. 특히나 C언어를 중심으로 공부한 사람들은 사실 이 문제가 더더욱 어렵게 느껴질 것이다. 전역 변수 문제로 데이터를 다루는 함수를 특정짓기가 어렵기 때문이다.

 

하지만 일단 객체지향으로 넘어오고 나면 개념적으로 이 문제를 해결할 수 있는 실마리가 있다. 클래스의 정의로부터 클래스는 속성(데이터)과 행위의 집합임을 알 수 있고, 속성은 정보 은닉(infomation hiding)을 통해서 외부에 노출시키지 않을 수 있다. 그리고 데이터를 다루는 것은 데이터를 포함하고 있는 클래스의 메소드들로 한정 지을 수 있다.

 

먼저 해결의 단계를 밟기 전에 Decorator 패턴을 적용하기 용이하도록 interface를 하나 정의하도록 하겠다. 물론 int data를 보호하기 위해 만들어질 클래스를 위한 인터페이스이다. 단순히 데이터를 꺼내 가고 집어 넣는 get/set 함수들이다.

interface IData{

    public void setData(int data);

    public int getData();

}

 

 

이제 위의 인터페이스를 구현하면서 int data를 선언하고 그에 필요한 행위를 정의하는 클래스를 선언한다. 이 클래스를 선언하는 순간 해결의 첫 단계를 밟게 된다.

class Data implements IData{

    private int data;

    public void setData(int data){

        this.data = data;

    }

    public int getData(){

        return data;

    }

}

 

위의 클래스를 선언함으로써 얻은 효과는 다음과 같다.

1. data를 private으로 선언함으로써 외부에서 임의로 접근하여 생기는 동시성 문제를 차단하였다.

2. data를 다루는 행위들을 모두 한 클래스 안에 모아 둠으로써 동시성 문제가 확산되는 것을 방지하였다.

 

이제 적극적으로 동시성 문제를 해결해 볼 차례다. Java 뿐 만 아니라 다른 객체지향 언어를 사용하는 사람들도 익숙한 형태로 먼저 문제를 해결해 보도록 하겠다. 현재 data를 다루는 메소드는 getData() 함수와 setData() 함수 둘 뿐이다. 그리고 여러 쓰레드에서 이들 함수를 호출할 때 동시성 문제가 발생하게 된다. 이를 방지하려면 데이터를 다루는 코드 영역을 동시에 여러 쓰레드에서 접근하지 못하도록 제한하면 된다. 즉, Lock 또는 Mutex를 이용하는 것이다.

 

동시 접근을 제한해야 할 영역은 이미 위의 두 메소드로 한정되어 있으니 적용도 역시 간단하다.

class Data implements IData{

    private int data;

    private Lock mutex = new ReentrantLock();

    public void setData(int data){

        mutex.lock();

        this.data = data;

        mutex.unlock();

    }

    public int getData(){

        mutex.lock();

        int backup = data;

        mutex.unlock();

        return backup;

    }

}

 

ReentrantLock 클래스는 Java에서 사용하는 Lock의 구체 클래스이다. 다른 언어에서 사용하는 Mutex나 Lock이라고 생각하고 보면 된다. setData()와 getData()를 보면 data를 다루는 영역이 Lock으로 잠겨 있는 것을 알 수 있다. data를 사용하는 영역은 저 두 영역 뿐이고, 두 영역이 같은 Lock 객체를 통해 잠겨 있으므로 동시에 저 영역이 접근되는 것은 차단되어 있다.

 

자 이제 Java 사용자들이 익숙한 synchronized 키워드를 이용한 접근 제한을 구현해 보도록 하겠다. 다른 언어 개발자들은 그냥 위의 코드와 아래 코드가 동일한 기능을 한다고 이해하면 되겠다.

class Data implements IData{

    private int data;

    public void setData(int data){

        synchronized(this){

            this.data = data;

        }

    }

    public int getData(){

        synchronized(this){

            return data;

        }

    }

}

 

바뀐 부분을 보면, 기존에 Lock 객체에 의해 상하로 막혀 있던 것이 synchronized 키워드를 통해 감싸져 있고, this 객체, 즉 자기 자신에 의해 동기화 되어 있음을 알 수 있다. this 객체에 의해 동기화 되어 있으므로 같은 객체 안에서 synchonized 키워드로 둘러 싸인 영역은 중복 접근이 불가능하다.

 

이것으로 일단 1번 요구사항을 만족시켰다. 하지만 2번 요구사항을 만족시키는 것은 간단해 보이지 않는다. 2번 요구사항을 좀 해석해 보자면, 동시성을 만족시키는 구현과 동시성을 만족시키지 않는 구현을 바꿔치기 하기 용이하도록 구현하라는 것이다.

 

이제 위에서 선언한 interface가 사용될 때가 되었다. 일단 동시성을 만족시키지 않는 클래스는 그대로 사용하도록 한다. 그러면 동시성을 만족하는 클래스를 만들어야 한다. 하지만 이미 구현된 동시성 코드와 그렇지 않는 코드는 중복이다. 이러면 한 클래스가 수정되었을 때 다른 클래스는 수정되지 않을 수 있다. 따라서 중복 코드는 제거 되어야 한다. 이 때 사용하는 것이 Decorator 패턴이다. 아래는 Decorator 패턴을 통해 구현한 동시성 만족 클래스이다.

class SynchronizedData implements IData{

    private IData data;

    public SynchronizedData(IData data){

        this.data = data;

    }

    public void setData(int data){

        synchronized(this){

            this.data.setData(data);

        }

    }

    public synchronized int getData(){

        synchronized(this){

            return data.getData();

        }

    }

} 

 

위의 코드는 우선 IData 인터페이스를 구현하고 있다. 따라서 Data 클래스와 외부적 관점에서는 동일한 타입이 된다. 내부에는 첫째로 IData 객체에 대한 레퍼런스를 선언해 두고 있다. 그리고 생성자를 통해서 IData 타입의 객체를 입력 받도록 되어 있다. 우리가 선언한 Data 클래스가 외부적 관점에서는 IData 타입이다. 이것을 객체로 생성하여 집어 넣을 것이다. 그리고 data를 다루는 메소드들에서는 생성자를 통해 들어온 참조 객체를 그냥 이용하기만 한다. 대신 synchronized 블럭을 이용함으로써 동시성을 만족하도록 구현 되어 있다. 이렇게 하면 동시성을 만족하면서도 중복 코드가 없어서 수정에 닫혀 있는 형태의 구현이 완성된다.

 

그러면 이제 결과물을 종합해 보자.

 

클래스 다이어그램

 

최종 결과물

interface IData{ // 동일한 타입으로 만들어 주기 위한 인터페이스

    public void setData(int data);

    public int getData();

}

 

class Data implements IData{ // 동시성을 구현하지 않은 Data 클래스

    private int data;

    public void setData(int data){

        this.data = data;

    }

    public int getData(){

        return data;

    }

}

 

class SynchronizedData implements IData{ // 동시성을 구현한 클래스(Decorator 패턴)

    private IData data;

    public SynchronizedData(IData data){

        this.data = data;

    }

    public void setData(int data){

        synchronized(this){

            this.data.setData(data);

        }

    }

    public synchronized int getData(){

        synchronized(this){

            return data.getData();

        }

    }

} 

 

이 패턴을 사용하는 코드를 보자.

public static void main(String[] args) {

    IData data = new Data(); // 동시성이 필요없을 때

    IData data = new SynchronizedData(new Data()); // 동시성이 필요할 때

 

}

 

동시성이 필요하지 않은 경우에는 그냥 new Data()를 호출해서 바로 Data 클래스를 사용하면 된다. 동시성이 필요한 경우에는 같은 코드에서 new SynchronizedData()를 호출한 후 Data 클래스를 생성해서 넣어 주기만 하면 된다. 즉, 동시성을 만족하고 안하고는 저 코드 한 줄만 수정하면 되는 문제이다.

 

사실 이 구현 방식은 특별한 것이 아니다. 이미 Java 라이브러리에서는 동시성을 이런 방식으로 구현해 두었다. 다음의 코드를 보자.

사용 방법

    List<String> list = new ArrayList<String>();

    List<String> list = Collections.synchronizedList(new ArrayList<String>());

 

Java에서는 Collection을 자주 사용하게 되는데, 사용 빈도가 높은 만큼 Collection의 동시성 문제는 상당히 해결하기 어려워 질 수 있다. 하지만 두번째 줄에 보면 우리가 구현한 내용과 유사한 코드가 보인다. 저것이 Decorator 패턴을 이용해 구현한 동시성 지원 Collection 클래스 사용 방법이다. Collections.synchronizedList와 우리의 SynchronizedData가 얼마나 유사한지 알아보기 위해 라이브러리 소스를 살펴 보도록 하겠다.

 

Collections.synchronizedList 내부 소스 코드

static class SynchronizedList<E> implements List<E> {

    final List<E> list;

    SynchronizedList(List<E> list) {

        super(list);

        this.list = list;

    }

    public E get(int index) {

        synchronized (mutex) {return list.get(index);}

    }

    public E set(int index, E element) {

        synchronized (mutex) {return list.set(index, element);}

    }

    public void add(int index, E element) {

        synchronized (mutex) {list.add(index, element);}

    }

    public E remove(int index) {

        synchronized (mutex) {return list.remove(index);}

    }

    ......

}

 

일단 잡다한 코드들은 일부 지웠다. 눈여겨 볼 것은 데이터를 다루는 메소드들이다. 보면 mutex 객체를 통해 모두 synchronized 블럭으로 막혀 있고, 그 안에서 일반 List 객체를 다루고 있는 것을 볼 수 있다. 그리고 생성자에서는 당연히 일반 List 객체를 인자로 받도록 되어 있다. 구조적으로 위에서 구현 내용과 완전히 같음을 알 수 있다.

 

 

 

=================================

=================================

=================================

 

 

 

출처: http://cafe.naver.com/cstudyjava/25376

 

 

일단 API문서를 보게되면

이 구현은 동기화 되지 않습니다. 복수의 thread가 동시에 링크 리스트에 액세스 해, 그러한 적어도 1 개(살)이 구조적으로 리스트를 변경했을 경우에는, 외부에서 동기를 잡을 필요가 있습니다. 구조적인 변경과는 1 개 이상의 요소를 추가 또는 삭제하는 모든 처리입니다. 요소의 값만을 변경하는 처리는, 구조적인 변경이 아닙니다. 일반적으로, 리스트의 동기를 잡으려면 , 리스트를 자연스럽게 캡슐화하는 객체로 동기를 잡습니다. 이런 종류의 객체가 없는 경우에는,Collections.synchronizedList 메소드를 사용해 리스트를 「랩」할 필요가 있습니다. 이것은, 리스트에의 우발적인 비동기 액세스를 막기 위해서(때문에), 작성시에 실시하는 것이 최적입니다.

List list = Collections.synchronizedList(new LinkedList(...));이런식으로 명시가 되어있는데요, 멀티쓰레드를 할경우 외부에서 동기를 잡아준다.. 즉 힙영역을 공유하니까 명시적으로 Collections.synchronizedLIst(new LikedList(...));함수로 동기화를 잡아주는 것 같네요.. 지금 계속 코딩중인데 어렵네요 ㅠㅠ 암튼 저번주에 못가서 과제 뜻을 잘 파악 못했는데유용한 메소드라면 synchronizedList()이 메소드가 아닐까 생각합니다.. 일단 발표자료도 아직 미완성이지만 지금 계속수정하고 있으니 수정되는데로 바로 다시 올리겠습니다.

[출처] [7주차과제_8주차발표] (윤성우의 프로그래밍 스터디그룹 [C언어/C++/자바(JAVA)]) |작성자 kij

 

 

 

 

=================================

=================================

=================================

 

 

출처: http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040201&docId=67094557&qb=Q29sbGVjdGlvbnMuc3luY2hyb25pemVkTGlzdCBzeW5jaHJvbml6ZWQ=&enc=utf8§ion=kin&rank=1&search_sort=0&spq=0&pid=S/k/AwoRR18ssbvbC88sssssstZ-240929&sid=cjYd9U3h9%2BlO7KFY2nHxLQ%3D%3D

 

 

collection에서의 동기화 방법

 

collection에서의 동기화 방법이 있다고 들었는데..

 

예를 들어 ArrayList에서는

List AccountList = Collections.synchronizedList(new ArrayList());

이렇게 하라고 들었는데 단순히 이렇게 하면 해당 리스트에 대해 동기화가 되는지 궁금합니다...

그런데 자바 API문서를 보면

 synchronized(list) {
      Iterator i = list.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }
이부분도 있는데...동기화 하는데 어떤 기능을 하는지 알고 싶습니다..

제가 보기에는 단순히 블럭안에만 동기화 시키는거 같은데요...

 

그리고 간단히 ArrayList에대해 동기화 예제좀 만들어 주세요..

 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

Collections의 synchronizedList메서드는

받은 List 객체가 instanceof RandomAccess 일 때

SynchronizedRandomAccessList 객체로 변환해서 반환하고

아닐 때는 SynchronizedList객체로 변환해서 반환합니다.

 

SynchronizedRandomAccessList 나 SynchronizedList는

SynchronizedList를 상속하는 클래스인데

SynchronizedList는 SynchronizedCollection를 상속하는 클래스입니다.

SynchronizedList는 생성시 부모인 SynchronizedCollection의 생성자를 호출하고

이 생성자는 mutax = this;라는 코드를 담고 있습니다.

mutax는 Object형 변수입니다.

 

이걸 말씀 드리는 이유는

SynchronizedList 클래스의

모든 메서드가 mutax를 통해

동기화되기 때문입니다.

 

자세히는 잘 모르겠으나 제 생각에 API 문서에 나오기를

"synchronizedList메서드로 부터 반환된 객체를 iterating 할 때는

좋게 말로할 때 니가 직접 동기화해야된다."(완역 ^^;)

라고 하는 것을 보면 iterator와 관련된 부분에 한해서

동기화가 따로 필요한 것으로 보입니다.

 

결론적으로,

Collections.synchronizedList메서드의 인수로 넣은 List는

동기화된 List가 되어 반환되는데

그 List를 iterating 할 때만

따로 사용자가 동기화를 해주어야 한다~ 라는 것으로 보입니다.

 

해당 글은 지식스폰서가 활동 기간 (04년~08년 6월 종료)중에 작성한 글 입니다.

  • 지식스폰서가 작성한 답변은 본문 내 자기소개 및 출처란의 실명, 상호명, URL표시를 허용합니다.
  • 출처란에 표시된 정보가 지식iN 운영원칙에 위배되는 음란성, 불법성, 청소년 유해사이트 등으로 변질된 경우는 허용이 되지 않습니다.

지식스폰서란

 

 

 

 

=================================

=================================

=================================

 

 

출처ㅣ http://kin.naver.com/qna/detail.nhn?d1id=1&dirId=1040201&docId=240889379&qb=Q29sbGVjdGlvbnMuc3luY2hyb25pemVkTGlzdCBzeW5jaHJvbml6ZWQ=&enc=utf8§ion=kin&rank=3&search_sort=0&spq=0&pid=S/k/AwoRR18ssbvbC88sssssstZ-240929&sid=cjYd9U3h9%2BlO7KFY2nHxLQ%3D%3D

 

 

'이것이 자바다' 책에서  동기화된 컬렉션 부분이 나왓으나 예제가 없어 구글에서 예제를 찾았습니다.

 

그런데 무엇을 의미하는지 잘 모르겟어서 코드 해석을 부탁드립니다 알려주시면 고맙겠습니다.

 

import java.util.ArrayList; 
import java.util.Collections; 
import java.util.Iterator; 
import java.util.List; 
public class SynchroTest01 { 
   public static void main(String[] args) { 
      List<String> syncList = Collections.synchronizedList(new ArrayList<String>()); 
      syncList.add("one"); 
      syncList.add("two"); 
      syncList.add("three"); 
      synchronized (syncList){ 
         Iterator<String> iterator = syncList.iterator(); 
         while(iterator.hasNext()){ 
            System.out.println("item : "+iterator.next()); 
         } 
      } 
   }//end main 
}//end class 

 

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

컬렉션은..

Collection이란 같은 타입의 참조값을 여러개 저장하기 위한 자바 라이브러리 입니다.
배열 보다 편리하다고 생각하시면 됩니다.

 

ArrayList 의 경우 List 가 쌓여 있는데 중간의 Object를 수정, 삭제 하는 속도가 가장 빠르다고는 합니다.

그리고 thread를 safe하지 않는데 속도 때문이라고 합니다.

 

// 컬렉션 리스트 생성

List<String> syncList = Collections.synchronizedList(new ArrayList<String>());

 

         syncList.add("one");

         syncList.add("two");

         syncList.add("three");

 

         // 위에서 생성된 객체를 동기화 시켜줍니다.

         synchronized (syncList){

 

            // 해당 컬렉션에서 Iterator 통해 값을 가져옵니다.

            Iterator<String> iterator = syncList.iterator();

 

            // iterator 에 담은 값을 순차적으로 hasNext()가 true을 리턴하는 동안 즉, Iterator는

          //  syncList의 어떤 항목의 위치를 가리키고 있는데, 이때 앞으로 하나 이동해서

          // 가리킬 항목이 존재하는동안 while 루프문을 반복합니다.

            while(iterator.hasNext()){

               System.out.println("item : "+iterator.next());

            }

}

 

위에 소스를 실행하면 결과가

 

item : one
item : two
item : three

 

나옵니다.

간단하게 얘기하면 syncList 를 Iterator 통해 담고, 그걸 순차적으로 순환시켜 출력한 겁니다.

 

 

 

=================================

=================================

=================================

 

 

 

 

반응형


관련글 더보기

댓글 영역