이것은 Java의 일반적인 동시성 문제에 대한 일종의 설문 조사입니다. Swing의 전형적인 교착 상태 또는 경쟁 조건 또는 EDT 스레딩 버그가 그 예입니다. 가능한 광범위한 문제와 가장 일반적인 문제에 관심이 있습니다. 따라서 댓글 당 Java 동시성 버그에 대한 특정 답변을 하나 남겨두고 발생한 버그가 있으면 투표하십시오.
이것은 Java의 일반적인 동시성 문제에 대한 일종의 설문 조사입니다. Swing의 전형적인 교착 상태 또는 경쟁 조건 또는 EDT 스레딩 버그가 그 예입니다. 가능한 광범위한 문제와 가장 일반적인 문제에 관심이 있습니다. 따라서 댓글 당 Java 동시성 버그에 대한 특정 답변을 하나 남겨두고 발생한 버그가 있으면 투표하십시오.
답변:
내가 본 가장 일반적인 동시성 문제는 하나의 스레드로 작성된 필드가 다른 스레드로 표시 되지 않는다는 것을 인식 하지 못한다 는 것 입니다. 이것의 일반적인 응용 프로그램 :
class MyThread extends Thread {
private boolean stop = false;
public void run() {
while(!stop) {
doSomeWork();
}
}
public void setStop() {
this.stop = true;
}
}
만큼 정지로하지 휘발성 이나 setStop
하고 run
있지 않습니다 동기화 이 작동하도록 보장 할 수 없습니다. 이 실수는 99.999 %에서 특히 끔찍합니다. 독자 스레드가 결국 변화를 볼 수 있기 때문에 실제로는 문제가되지 않습니다. 그러나 우리는 그가 얼마나 빨리 그것을 보았는지 모릅니다.
내 # 1 가장 고통스러운 때 동시성 문제는 지금까지 발생한 두 개의 서로 다른 오픈 소스 라이브러리는 다음과 같이 뭔가를했다 :
private static final String LOCK = "LOCK"; // use matching strings
// in two different libraries
public doSomestuff() {
synchronized(LOCK) {
this.work();
}
}
언뜻보기에 이것은 아주 간단한 동기화 예제처럼 보입니다. 하나; Strings는 Java 로 인턴 되기 때문에 리터럴 문자열 "LOCK"
은 java.lang.String
서로 완전히 분리되어 선언되었지만 동일한 인스턴스로 나타납니다 . 결과는 분명히 나쁩니다.
하나의 고전적인 문제는 동기화하는 객체를 동기화하는 동안 변경하는 것입니다.
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 메모리 모델이 지원하지 않기 때문에 작동하지 않습니다. s_instance를 휘발성으로 선언해야 작동하며 Java 5에서만 작동합니다.
Java 메모리 모델의 복잡성에 익숙하지 않은 사람들은 이것을 항상 혼란스럽게 한다 .
아마도 정확히 당신이 요구하는 것은 아니지만, 내가 직면 한 가장 빈번한 동시성 관련 문제는 (아마도 일반적인 단일 스레드 코드에서 발생하기 때문에)
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()
and size()
메소드는 모두 자체적으로 스레드로부터 안전하지만 size()
는와 별도로 평가되며 toArray()
List의 잠금은이 두 호출 사이에 유지되지 않습니다.
목록에서 항목을 동시에 제거하는 다른 스레드와 함께이 코드를 실행하면 조만간 목록의 String[]
모든 요소를 보유하는 데 필요한 것보다 크고 새로 꼬리 값이 있는 새 리턴이 발생합니다. List에 대한 두 개의 메소드 호출이 단일 코드 행에서 발생하기 때문에 이것은 다소 원자 적 인 작업이지만 그렇지는 않다고 생각하기 쉽습니다.
또 다른 일반적인 버그는 잘못된 예외 처리입니다. 백그라운드 스레드가 예외를 처리 할 때 올바르게 처리하지 않으면 스택 추적이 전혀 표시되지 않을 수 있습니다. 또는 예외 처리에 실패하여 백그라운드 작업 실행이 중지되고 다시 시작되지 않을 수 있습니다.
나는 비를 동기화 몰랐어요 브라이언 게츠 I와 클래스를했다 때까지 getter
동기화를 통해 돌연변이 private 필드의 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는 첫 번째 답변에서 이것을 만졌으며 실제로 동기화 된 메소드 내에서 리스너의 메소드를 호출하는 것과 관련하여 교착 상태가 발생했습니다. 더 일반적인 교훈은 동기화 된 블록 내에서 메소드 호출을 "실제로"해서는 안된다는 것입니다. 호출이 오래 실행되거나 교착 상태가 발생하는지 여부는 알 수 없습니다.
이 경우 일반적으로 해결 방법은 중요한 개인 코드 섹션을 보호하기 위해 동기화 된 블록의 범위를 줄이는 것 입니다.
또한 동기화 된 블록 외부의 리스너 컬렉션에 액세스하고 있었으므로 쓰기시 복사 컬렉션으로 변경했습니다. 또는 단순히 컬렉션의 방어 사본을 만들 수도 있습니다. 요점은 일반적으로 알 수없는 객체의 컬렉션에 안전하게 액세스 할 수있는 대안이 있다는 것입니다.
불균형 동기화, 특히지도에 대한 동기화는 상당히 일반적인 문제인 것 같습니다. 많은 사람들은 동기화를 통해 Map (ConcurrentMap이 아니라 HashMap이라고 함)에 동기화하고 가져 오기를 동기화하지 않는 것으로 충분하다고 생각합니다. 그러나 이것은 다시 해시하는 동안 무한 루프로 이어질 수 있습니다.
그러나 읽기 및 쓰기와 공유 상태가있는 모든 위치에서 동일한 문제 (부분 동기화)가 발생할 수 있습니다.
각 요청에 의해 설정되는 변경 가능한 필드가있을 때 서블릿에 동시성 문제가 발생했습니다. 그러나 모든 요청에 대해 하나의 서블릿 인스턴스 만 있으므로 단일 사용자 환경에서 완벽하게 작동했지만 둘 이상의 사용자가 서블릿을 요청하면 예기치 않은 결과가 발생했습니다.
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 ...
}
}
내 가장 큰 문제는 항상 교착 상태였습니다. 특히 자물쇠를 들고 발사 된 청취자 때문에 발생합니다. 이 경우 두 스레드 사이에서 반전 잠금을 얻는 것이 정말 쉽습니다. 필자의 경우 하나의 스레드에서 실행되는 시뮬레이션과 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 머신에서는 다를 수 있습니다. 많은 사람들이이를 인식하지 못하고 초기화 단계에서 멀티 스레딩에 신경 쓸 필요가 없다고 생각합니다.
로컬 "new Object ()"를 뮤텍스로 사용.
synchronized (new Object())
{
System.out.println("sdfs");
}
이것은 쓸모가 없습니다.
this
내부 클래스의 내부 클래스가 this
외부 클래스의 클래스가 아님을 인식 하지 못합니다 일반적으로 구현하는 익명의 내부 클래스에서 Runnable
. 근본적인 문제는 동기화가 모든 것의 일부이기 때문에 Object
정적 유형 검사가 사실상 없다는 것입니다. 나는 이것을 유즈넷에서 적어도 두 번 보았으며 Brian Goetz의 Java Concurrency in Practice에도 나타납니다.
BGGA 클로저는 클로저가 없기 때문에이 문제를 겪지 않습니다 this
( this
외부 클래스 참조). 비 this
객체를 잠금으로 사용하면 이 문제와 다른 문제가 해결됩니다.
솔직히? 의 출현 이전에, java.util.concurrent
내가 일상적으로 겪었던 가장 일반적인 문제는 "스레딩 스레 싱 (thread-thrashing)"이라고했습니다.