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과 같은 클래스를 동기화없이 사용하는 것입니다 (정적 변수로 캐싱). 이러한 클래스는 스레드로부터 안전하지 않으므로 다중 스레드 액세스는 궁극적으로 일관성이없는 상태에서 이상한 문제를 일으킬 수 있습니다.
이중 점검 잠금. 전반적으로.
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 메모리 모델의 복잡성에 익숙하지 않은 사람들은 이것을 항상 혼란스럽게 한다 .
에 의해 반환 된 객체 , 특히 반복 또는 여러 작업 중에 제대로 동기화 되지 않음 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");
Though probably not exactly what you are asking for, the most frequent concurrency-related problem I've encountered (probably because it comes up in normal single-threaded code) is a
java.util.ConcurrentModificationException
caused by things like:
List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }
The most common bug we see where I work is programmers perform long operations, like server calls, on the EDT, locking up the GUI for a few seconds and making the app unresponsive.
It can be easy to think synchronized collections grant you more protection than they actually do, and forget to hold the lock between calls. I have seen this mistake a few times:
List<String> l = Collections.synchronizedList(new ArrayList<String>());
String[] s = l.toArray(new String[l.size()]);
For example, in the second line above, the toArray()
and size()
methods are both thread safe in their own right, but the size()
is evaluated separately from the toArray()
, and the lock on the List is not held between these two calls.
If you run this code with another thread concurrently removing items from the list, sooner or later you will end up with a new String[]
returned which is larger than required to hold all the elements in the list, and has null values in the tail. It is easy to think that because the two method calls to the List occur in a single line of code this is somehow an atomic operation, but it is not.
Forgetting to wait() (or Condition.await()) in a loop, checking that the waiting condition is actually true. Without this, you run into bugs from spurious wait() wakeups. Canonical usage should be:
synchronized (obj) {
while (<condition does not hold>) {
obj.wait();
}
// do stuff based on condition being true
}
Another common bug is poor exception handling. When a background thread throws an exception, if you don't handle it properly, you might not see the stack trace at all. Or perhaps your background task stops running and never starts again because you failed to handle the exception.
Until I took a class with Brian Goetz I didn't realize that the non-synchronized getter
of a private field mutated through a synchronized setter
is never guaranteed to return the updated value. Only when a variable is protected by synchronized block on both reads AND writes will you get the guarantee of the latest value of the variable.
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;
}
}
Thinking you are writing single-threaded code, but using mutable statics (including singletons). Obviously they will be shared between threads. This happens surprisingly often.
Arbitrary method calls should not be made from within synchronized blocks.
Dave Ray touched on this in his first answer, and in fact I also encountered a deadlock also having to do with invoking methods on listeners from within a synchronized method. I think the more general lesson is that method calls should not be made "into the wild" from within a synchronized block - you have no idea if the call will be long-running, result in deadlock, or whatever.
In this case, and usually in general, the solution was to reduce the scope of the synchronized block to just protect a critical private section of code.
Also, since we were now accessing the Collection of listeners outside of a synchronized block, we changed it to be a copy-on-write Collection. Or we could have simply made a defensive copy of the Collection. The point being, there are usually alternatives to safely access a Collection of unknown objects.
The most recent Concurrency-related bug I ran into was an object that in its constructor created an ExecutorService, but when the object was no longer referenced, it had never shutdown the ExecutorService. Thus, over a period of weeks, thousands of threads leaked, eventually causing the system to crash. (Technically, it didn't crash, but it did stop functioning properly, while continuing to run.)
Technically, I suppose this isn't a concurrency problem, but it's a problem relating to use of the java.util.concurrency libraries.
Unbalanced synchronization, particularly against Maps seems to be a fairly common problem. Many people believe that synchronizing on puts to a Map (not a ConcurrentMap, but say a HashMap) and not synchronizing on gets is sufficient. This however can lead to an infinite loop during re-hash.
The same problem (partial synchronization) can occur anywhere you have shared state with reads and writes however.
I encountered a concurrency problem with Servlets, when there are mutable fields which will be setted by each request. But there is only one servlet-instance for all request, so this worked perfectly in a single user environment but when more than one user requested the servlet unpredictable results occured.
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 ...
}
}
Not exactly a bug but, the worst sin is providing a library you intend other people to use, but not stating which classes/methods are thread-safe and which ones must only be called from a single thread etc.
More people should make use of the concurrency annotations (e.g. @ThreadSafe, @GuardedBy etc) described in Goetz's book.
My biggest problem has always been deadlocks, especially caused by listeners that are fired with a lock held. In these cases, it's really easy to get inverted locking between two threads. In my case, between a simulation running in one thread and a visualization of the simulation running in the UI thread.
EDIT: Moved second part to separate answer.
Starting a thread within the constructor of a class is problematic. If the class is extended, the thread can be started before subclass' constructor is executed.
Mutable classes in shared data structures
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");
When this happens, the code is far more complex that this simplified example. Replicating, finding and fixing the bug is hard. Perhaps it could be avoided if we could mark certain classes as immutable and certain data structures as only holding immutable objects.
Synchronizing on a string literal or constant defined by a string literal is (potentially) a problem as the string literal is interned and will be shared by anyone else in the JVM using the same string literal. I know this problem has come up in application servers and other "container" scenarios.
Example:
private static final String SOMETHING = "foo";
synchronized(SOMETHING) {
//
}
In this case, anyone using the string "foo" to lock on is sharing the same lock.
I believe in the future the main problem with Java will be the (lack of) visibility guarantees for constructors. For example, if you create the following class
class MyClass {
public int a = 1;
}
and then just read the MyClass's property a from another thread, MyClass.a could be either 0 or 1, depending on the JavaVM's implementation and mood. Today the chances for 'a' being 1 are very high. But on future NUMA machines this may be different. Many people are not aware of this and believe that they don't need to care about multi-threading during the initialization phase.
The dumbest mistake I frequently make is forgetting to synchronize before calling notify() or wait() on an object.
Using a local "new Object()" as mutex.
synchronized (new Object())
{
System.out.println("sdfs");
}
This is useless.
Another common 'concurrency' issue is to use synchronized code when it is not necessary at all. For example I still see programmers using StringBuffer
or even java.util.Vector
(as method local variables).
Multiple objects that are lock protected but are commonly accessed in succession. We've run into a couple of cases where the locks are obtained by different code in different orders, resulting in deadlock.
Not realising that the this
in an inner class is not the this
of the outer class. Typically in an anonymous inner class that implements Runnable
. The root problem is that because synchronisation is part of all Object
s there is effectively no static type checking. I've seen this at least twice on usenet, and it also appears in Brian Goetz'z Java Concurrency in Practice.
BGGA closures don't suffer from this as there is no this
for the closure (this
references the outer class). If you use non-this
objects as locks then it gets around this problem and others.
Use of a global object such as a static variable for locking.
This leads to very bad performance because of contention.
Honesly? Prior to the advent of java.util.concurrent
, the most common problem I routinely ran into was what I call "thread-thrashing": Applications that use threads for concurrency, but spawn too many of them and end up thrashing.
'IT story' 카테고리의 다른 글
주석 핀에 맞게 MKMapView를 확대 하시겠습니까? (0) | 2020.05.13 |
---|---|
데이터베이스 테이블에서 클래스 생성 (0) | 2020.05.13 |
zsh를 최신 버전으로 업데이트하려면 어떻게합니까? (0) | 2020.05.13 |
ELIFECYCLE Node.js 오류는 무엇을 의미합니까? (0) | 2020.05.13 |
Linux에서 Windows 컨테이너를 호스팅 할 수 있습니까? (0) | 2020.05.13 |