IT이야기

Java에서 가장 자주 발생하는 동시성 문제는 무엇입니까?

cyworld 2022. 6. 16. 22:20
반응형

Java에서 가장 자주 발생하는 동시성 문제는 무엇입니까?

이것은 Java의 일반적인 동시성 문제에 대한 일종의 의견조사입니다.예를 들어 전형적인 교착 상태 또는 경합 상태 또는 Swing의 EDT 스레드 버그가 있을 수 있습니다.가능한 다양한 문제뿐만 아니라 가장 일반적인 문제에도 관심이 있습니다.따라서 자바 동시성 버그에 대한 구체적인 답변을 댓글당 1개씩 남겨주시고, 마주친 버그가 있으면 투표해 주세요.

지금까지 가장 고통스러웠던 동시성 문제는 두 의 오픈 소스 라이브러리가 다음과 같은 작업을 했을 때 발생했습니다.

private static final String LOCK = "LOCK";  // use matching strings 
                                            // in two different libraries

public doSomestuff() {
   synchronized(LOCK) {
       this.work();
   }
}

언뜻 보기에 이것은 매우 단순한 동기화 예처럼 보입니다.단, 문자열은 Java에 내장되어 있기 때문에 리터럴 문자열은"LOCK" 로 판명되다java.lang.String은 서로 되어 있는데 나쁘다.결과는 분명히 나쁘다.

지금까지 본 가장 일반적인 동시성 문제는 한 스레드에 의해 작성된 필드가 다른 스레드에 의해 확실하게 표시되지 않는다는 것을 깨닫지 못하는 것입니다.일반적인 응용 프로그램:

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    while(!stop) {
      doSomeWork();
    }
  }

  public void setStop() {
    this.stop = true;
  }
}

stop이 휘발성이 없는 한, 또는setStop ★★★★★★★★★★★★★★★★★」run동기화되지 않습니다.동작할 수 있는 것은 아닙니다.이 실수는 99.999%에서는 실제로 문제가 되지 않기 때문에 특히 악랄합니다.리더 스레드는 결국 변화를 볼 수 있기 때문입니다.그러나 그가 그것을 얼마나 빨리 알아차렸는지는 알 수 없습니다.

한 가지 전형적인 문제는 동기화 중인 개체를 동기화하면서 변경하는 것입니다.

synchronized(foo) {
  foo = ...
}

그러면 다른 동시 스레드가 다른 개체에서 동기화되고 이 블록은 사용자가 기대하는 상호 제외 기능을 제공하지 않습니다.

일반적인 문제는 여러 스레드의 Calendar 및 SimpleDateFormat과 같은 클래스를 동기화하지 않고 사용하는 것입니다.이러한 클래스는 스레드 세이프가 아니기 때문에 멀티 스레드접근은 최종적으로 일관성이 없는 상태에서 이상한 문제를 일으킵니다.

에서 반환된 개체에서 올바르게 동기화되지 않음Collections.synchronizedXXX()「 」 「 」 。

Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

...

if(!map.containsKey("foo"))
    map.put("foo", "bar");

틀렸어.한 번의 조작에도 불구하고synchronized , " " " " " "contains ★★★★★★★★★★★★★★★★★」put하다을 하다

synchronized(map) {
    if(!map.containsKey("foo"))
        map.put("foo", "bar");
}

또,를 ConcurrentMap★★★★

map.putIfAbsent("foo", "bar");

잠금을 더블 체크.대체로.

제가 BEA에서 일할 때 문제를 배우기 시작한 패러다임은 사람들이 싱글톤을 다음과 같이 점검한다는 것입니다.

public Class MySingleton {
  private static MySingleton s_instance;
  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) { s_instance = new MySingleton(); }
    }
    return s_instance;
  }
}

다른 스레드가 동기화된 블록에 들어가 s_instance가 null이 아니기 때문에 이 방법은 작동하지 않습니다.따라서 자연스런 변화는 다음과 같습니다.

  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) {
        if(s_instance == null) s_instance = new MySingleton();
      }
    }
    return s_instance;
  }

Java Memory Model은 지원하지 않기 때문에 이것도 동작하지 않습니다.s_instance를 동작시키기 위해서는 volatile로 선언해야 합니다.그 후에도 동작하는 것은 Java 5뿐입니다.

Java Memory Model의 복잡함에 익숙하지 않은 사람들은 항상 이것을 망친다.

아마 당신이 원하는 것은 아닐지도 모르지만, 제가 가장 자주 접한 동시성과 관련된 문제(일반적인 단일 스레드 코드에서 발생하는 문제일 수 있습니다)는

java.util.ConcurrentModificationException

다음과 같은 이유로 인해 발생합니다.

List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }

동기화된 컬렉션이 실제보다 더 많은 보호를 제공한다고 생각하고 콜 사이에 잠금을 유지하는 것을 잊기 쉽습니다.나는 이 실수를 몇 번 본 적이 있다.

 List<String> l = Collections.synchronizedList(new ArrayList<String>());
 String[] s = l.toArray(new String[l.size()]);

를 들어,두 " " " " " " " " " " " " " "toArray() ★★★★★★★★★★★★★★★★★」size()그로는 스레드 「스루드 세이프」는, 「스루드 세이프」입니다.size()로 평가됩니다.toArray()리스트의 잠금은 이들2개의 콜 사이에 유지되지 않습니다.

목록에서 항목을 동시에 삭제하는 다른 스레드와 함께 이 코드를 실행하면 조만간 새로운 스레드가 생성됩니다.String[]반환된 값은 목록 내의 모든 요소를 유지하는 데 필요한 값보다 크고 꼬리 부분에 늘 값이 있습니다.리스트에의 2개의 메서드콜이 1줄의 코드로 이루어지기 때문에, 이것은 어떤 식으로든 원자적인 조작이라고 생각하기 쉽지만, 실제로는 그렇지 않습니다.

제가 일하는 곳에서 가장 흔히 볼 수 있는 버그는 프로그래머가 EDT에서 서버 호출과 같은 장시간 작업을 수행하고 GUI를 몇 초 동안 잠그고 앱을 반응시키지 않는 것입니다.

루프에서 wait()(또는 Condition.await())을 잊어버리고 대기 상태가 실제로 참인지 확인합니다.이것이 없으면 스플리어스 wait() 웨이크업으로 인한 버그가 발생합니다.표준 사용법은 다음과 같습니다.

 synchronized (obj) {
     while (<condition does not hold>) {
         obj.wait();
     }
     // do stuff based on condition being true
 }

또 다른 일반적인 버그는 불량한 예외 처리입니다.백그라운드 스레드가 예외를 발생시킬 때 적절하게 처리하지 않으면 스택트레이스가 전혀 표시되지 않을 수 있습니다.또는 예외를 처리하지 못했기 때문에 백그라운드 태스크가 실행을 중지하고 다시 시작되지 않을 수도 있습니다.

Brian 을 들을 된 Brian Goetz는.getter된 개인 setter는 갱신된 값을 반환하는 것을 보증하지 않습니다.읽기 및 쓰기 모두에서 동기화된 블록에 의해 변수가 보호되는 경우에만 변수의 최신 값이 보장됩니다.

public class SomeClass{
    private Integer thing = 1;

    public synchronized void setThing(Integer thing)
        this.thing = thing;
    }

    /**
     * This may return 1 forever and ever no matter what is set
     * because the read is not synched
     */
    public Integer getThing(){
        return thing;  
    }
}

싱글 스레드 코드를 쓰고 있다고 생각되지만, 가변 통계(싱글톤 포함)를 사용하고 있다.분명히 그것들은 스레드 간에 공유될 것이다.이런 일은 의외로 자주 일어난다.

동기화된 블록 내에서 임의의 메서드콜을 발신해서는 안 됩니다.

Dave Ray는 첫 번째 답변에서 이 점에 대해 언급했고, 사실 동기화된 방식에서 청취자에게 호출하는 방식도 교착상태에 빠졌습니다.보다 일반적인 교훈은 동기화된 블록 내에서 메서드 호출을 "자연으로" 해서는 안 된다는 것입니다.콜이 장시간 실행되는지, 교착 상태가 될지는 알 수 없습니다.

이 경우, 그리고 일반적으로 솔루션은 동기화된 블록의 범위를 줄여 코드의 중요한 프라이빗 섹션만 보호하는 것이었습니다.

또, 동기 블록외의 리스너 컬렉션에 액세스 하고 있었기 때문에, Copy-on-Write Collection으로 변경했습니다.아니면 단순히 컬렉션의 방어용 복사본을 만들 수도 있습니다.요점은 일반적으로 알 수 없는 개체 모음에 안전하게 액세스할 수 있는 다른 방법이 있다는 것입니다.

최근에 마주친 동시성 관련 버그는 컨스트럭터에서 ExecutorService를 생성했지만 해당 개체가 더 이상 참조되지 않았을 때 ExecutorService를 종료하지 않았습니다.따라서 몇 주 동안 수천 개의 스레드가 누출되어 결국 시스템이 크래시되었습니다.(기술적으로는 크래시는 발생하지 않았지만 계속 작동하는 동안 제대로 작동하지 않았습니다.)

엄밀히 말하면, 이것은 동시성 문제가 아니라 java.util.concurrency 라이브러리의 사용에 관한 문제라고 생각합니다.

특히 맵에 대한 불균형 동기화는 상당히 일반적인 문제인 것 같습니다.많은 사람들이 동기화가 맵(ConcurrentMap이 아니라 HashMap이라고 함)에 배치되고 동기화가 되지 않는 것으로 충분하다고 생각합니다.그러나 이로 인해 재해시 중에 무한 루프가 발생할 수 있습니다.

그러나 읽기 및 쓰기 공유 상태가 있는 곳에서도 동일한 문제(부분 동기화)가 발생할 수 있습니다.

각 요청에 의해 설정되는 가변 필드가 있을 때 Servlet에서 동시성 문제가 발생했습니다.단, 모든 요구에 대해 서블릿인스턴스는 1개뿐이기 때문에 단일 사용자 환경에서 완벽하게 동작하지만 여러 사용자가 서블릿을 요구하면 예측할 수 없는 결과가 발생합니다.

public class MyServlet implements Servlet{
    private Object something;

    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException{
        this.something = request.getAttribute("something");
        doSomething();
    }

    private void doSomething(){
        this.something ...
    }
}

버그는 아니지만, 가장 큰 죄악은 다른 사람에게 사용할 라이브러리를 제공하지만 어떤 클래스/메서드가 스레드 세이프한지, 어떤 클래스/메서드가 단일 스레드에서만 호출되어야 하는지 등을 명시하지 않는 것입니다.

더 많은 사람들이 Goetz의 책에 설명된 동시성 주석(@ThreadSafe, @GuardedBy 등)을 사용해야 합니다.

나의 가장 큰 문제는 항상 교착 상태였고, 특히 잠금 장치를 잡고 있는 청취자 때문에 발생했다.이 경우 두 스레드 사이에 역잠금이 생기기 쉽습니다.제 경우, 하나의 스레드에서 실행되는 시뮬레이션과 UI 스레드에서 실행되는 시뮬레이션의 시각화 사이입니다.

편집: 두 번째 파트를 다른 답변으로 이동.

클래스 생성자 내에서 스레드를 시작하는 것은 문제가 있습니다.클래스가 확장되면 서브클래스 생성자가 실행되기 에 스레드를 시작할 수 있습니다.

공유 데이터 구조의 가변 클래스

Thread1:
    Person p = new Person("John");
    sharedMap.put("Key", p);
    assert(p.getName().equals("John");  // sometimes passes, sometimes fails

Thread2:
    Person p = sharedMap.get("Key");
    p.setName("Alfonso");

이 경우 코드는 이 단순화된 예보다 훨씬 복잡합니다.버그를 복제, 검출, 수정하는 것은 어렵습니다.어떤 클래스는 불변으로, 어떤 데이터 구조는 불변 객체만 보유한다고 표시할 수 있다면 피할 수 있을 것입니다.

문자열 리터럴에 의해 정의된 문자열 리터럴 또는 상수로 동기화하는 것은 (잠재적으로) 문제가 됩니다.문자열 리터럴이 내부 삽입되어 같은 문자열 리터럴을 사용하여 JVM 내의 다른 사용자가 공유하기 때문입니다.이 문제가 애플리케이션 서버 및 기타 "컨테이너" 시나리오에서 발생했음을 알고 있습니다.

예:

private static final String SOMETHING = "foo";

synchronized(SOMETHING) {
   //
}

이 경우 "foo" 문자열을 사용하여 잠금을 설정하는 모든 사용자가 동일한 잠금을 공유하고 있습니다.

향후 Java의 주요 문제는 컨스트럭터의 가시성 보증(부족)이 될 것이라고 생각합니다.예를 들어 다음 클래스를 만드는 경우

class MyClass {
    public int a = 1;
}

그런 다음 다른 스레드인 MyClass.a에서 MyClass 속성 a를 읽으면 JavaVM의 구현 및 분위기에 따라 0 또는 1이 될 수 있습니다.오늘날 'a'가 1일 확률은 매우 높다.그러나 향후 NUMA 시스템에서는 이와 다를 수 있습니다.많은 사람들이 이 사실을 알지 못하며 초기화 단계에서 멀티스레딩에 신경 쓸 필요가 없다고 생각합니다.

제가 자주 저지르는 가장 멍청한 실수는 오브젝트 상에서 notify() 또는 wait()를 호출하기 전에 동기화를 잊어버리는 것입니다.

로컬 "new Object()"를 뮤텍스로 사용합니다.

synchronized (new Object())
{
    System.out.println("sdfs");
}

이건 쓸모없어.

또 다른 일반적인 '통화' 문제는 전혀 필요하지 않은 경우 동기화된 코드를 사용하는 것입니다.예를 들어, 나는 여전히 프로그래머들이StringBuffer또는 심지어java.util.Vector(메서드 로컬 변수).

잠금이 보호되지만 일반적으로 연속적으로 액세스하는 여러 개체입니다.다른 순서로 다른 코드로 잠금을 취득하여 교착 상태가 되는 경우가 몇 번 있었습니다.

그 사실을 깨닫지 못하고 있다this내적 계급은 아니다this외부 계급의.일반적으로는 익명의 내부 클래스에서Runnable가 모든 이기 때문입니다.Object체크는 .usenet, Brian Goetz'z Java Concurency in Practice.

는 BGGA가 때문에 이 를 겪지 이치노thisclosure))를 위해this을 이용하다non-to-non-to를하는 경우this오브젝트를 잠그면 이 문제 등을 회피할 수 있습니다.

잠금을 위한 정적 변수와 같은 전역 개체 사용.

경합으로 인해 성능이 매우 저하됩니다.

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★java.util.concurrent제가 일상적으로 접한 가장 흔한 문제는 스레드 스러싱(thread-thrashing)입니다. 즉, 스레드를 사용하여 동시성을 확보하지만 너무 많은 스레드 스러싱을 발생시키는 애플리케이션입니다.

언급URL : https://stackoverflow.com/questions/461896/what-is-the-most-frequent-concurrency-issue-youve-encountered-in-java

반응형