이 클래스가 스레드로부터 안전하지 않은 이유는 무엇입니까?


94
class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

위의 클래스가 스레드로부터 안전하지 않은 이유를 누구든지 설명 할 수 있습니까?


6
Java에 대해서는 잘 모르지만 각 메서드는 개별적으로 스레드로부터 안전 해 보이지만 동시에 메서드에 스레드를 가질 수 있습니다 . bool ( increment) 을 취하는 단일 메소드가 있다면 스레드로부터 안전 할 것입니다. 또는 잠금 개체를 사용한 경우. 제가 말했듯이 저는 Java에 대해 모릅니다. 제 의견은 C # 지식에서 비롯됩니다.
Wai Ha Lee


또한 Java를 잘 모르지만 정적 변수에 대한 액세스를 동기화하려면 synchronized정적 메서드에서만 사용해야합니다. 따라서 내 increment의견으로 는 메서드 를 제거하더라도 두 인스턴스 (동일한 인스턴스를 통해서만 동기화 된 액세스 권한이 있음)가 메서드를 동시에 호출 할 수 있기 때문에 여전히 스레드 안전하지 않습니다.
Onur

4
클래스의 인스턴스를 생성하지 않는 한 스레드로부터 안전합니다.
Benjamin Gruenbaum

1
스레드로부터 안전하지 않은 이유는 무엇입니까?
Raedwald

답변:


134

때문에 increment방법은 static그것은의 클래스 객체에 동기화됩니다 ThreadSafeClass. decrement방법은 정적하지 않고 인스턴스를 호출하는 데 사용에 동기화됩니다. 즉, 서로 다른 개체에서 동기화되므로 두 개의 서로 다른 스레드가 동시에 메서드를 실행할 수 있습니다. 이후 ++--작업이 원자하지 않은 클래스는 스레드로부터 안전하지 않습니다.

이후 또한, count이다 static,에서 수정 decrement하는 것은 동기화 인스턴스 가 다른 인스턴스에 전화를 수정할 수 있기 때문에 방법은 안전하지 않은 count방식 것을 동시에.


12
당신은 이후 추가 할 수 있습니다 countIS static인스턴스 메서드를 가진 것은 decrement()어떤 없었다하더라도, 잘못된 static increment()방법 두 스레드가 호출 할 수있는, decrement()동시에 같은 카운터를 수정하는 다른 인스턴스에.
Holger

1
그건 아마도 사용을 선호하는 좋은 이유입니다 synchronized대신, 방법에, 즉 수식을 사용하는 (심지어 전체 방법의 내용에) 일반적으로 블록을 synchronized(this) { ... }하고 synchronized(ThreadSafeClass.class) { ... }.
Bruno

++--도에,하지 원자이다volatile int . Synchronized함께 읽기 / 업데이트 / 쓰기 문제를 돌봐 ++/ --하지만 static키워드가, 음, 여기. 좋은 대답입니다!
Chris Cirefice 2015 년

다시, 동기화 된 인스턴스 메서드에서 [정적 필드]를 수정하는 것은 잘못되었습니다 . 인스턴스 메서드 내에서 정적 변수에 액세스하는 데 본질적으로 잘못된 것은 없으며 인스턴스 메서드에서 액세스하는 것도 본질적으로 잘못된 것이 없습니다 synchronized. 정적 데이터에 대한 보호를 제공하기 위해 인스턴스 메서드에서 "동기화"될 것으로 기대하지 마십시오. 여기서 유일한 문제는 첫 번째 단락에서 말한 것입니다.이 방법은 동일한 데이터를 보호하기 위해 다른 잠금을 사용하며 물론 전혀 보호를 제공하지 않습니다.
Solomon Slow

23

두 개의 동기화 된 메서드가 있지만 그중 하나는 정적이고 다른 하나는 그렇지 않습니다. 동기화 된 메서드에 액세스 할 때 유형 (정적 또는 비 정적)에 따라 다른 개체가 잠 깁니다. 정적 메서드의 경우 클래스 개체에 잠금이 설정되고 비 정적 블록의 경우 메서드를 실행하는 클래스의 인스턴스에 잠금이 설정됩니다. 두 개의 서로 다른 잠긴 개체가 있으므로 동일한 개체를 동시에 수정하는 두 개의 스레드를 가질 수 있습니다.


14

위의 클래스가 스레드로부터 안전하지 않은 이유를 누구든지 설명 할 수 있습니까?

  • increment 정적이기 때문에 동기화는 클래스 자체에서 수행됩니다.
  • decrement정적이 아닌 경우 동기화는 개체 인스턴스화에서 수행되지만 count정적이므로 아무것도 보호하지 않습니다 .

스레드 안전 카운터를 선언하기 위해이를 추가하고 싶습니다. 가장 간단한 방법은 AtomicInteger기본 int 대신 사용하는 것입니다.

java.util.concurrent.atomicpackage-info로 리디렉션하겠습니다 .


7

다른 사람들의 대답은 그 이유를 꽤 잘 설명했습니다. 요약 할 내용을 추가합니다 synchronized.

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

synchronizedfun1fun2인스턴스 개체 수준에 동기화됩니다. synchronizedon fun4은 클래스 개체 수준에서 동기화됩니다. 이는 다음을 의미합니다.

  1. 2 개의 스레드 a1.fun1()가 동시에 호출되면 후자의 호출이 차단됩니다.
  2. 스레드 1 호출 a1.fun1()과 스레드 2 a1.fun2()가 동시에 호출되면 후자의 호출이 차단됩니다.
  3. 스레드 1 호출 a1.fun1()과 스레드 2 a1.fun3()가 동시에 호출 되면 블로킹없이 두 메서드가 동시에 실행됩니다.
  4. 스레드 1 호출시 A.fun4()다른 스레드가 A.fun4()또는 A.fun5()동시에 호출하면 synchronizedon fun4이 클래스 수준 이므로 후자의 호출이 차단 됩니다.
  5. 스레드 1 호출 A.fun4(), 스레드 2 호출 a1.fun1()이 동시에 차단되지 않고 두 메서드가 동시에 실행됩니다.

6
  1. decrement서로 다른 것을 잠그고 increment있으므로 서로가 달리는 것을 방해하지 않습니다.
  2. 호출 decrement한 인스턴스에서이 호출에 다른 일에 고정되어 decrement다른 인스턴스에서,하지만 그들은 같은 일에 영향을 미치는된다.

호출에 겹치는 상기 제 1 수단 increment과하면 decrement캔슬 아웃 (올바른)를 초래할 수 증분 또는 감분.

두 번째는 decrement서로 다른 인스턴스에 대한 두 개의 중복 호출 이 이중 감소 (정확) 또는 단일 감소를 초래할 수 있음을 의미합니다 .


4

두 가지 다른 메서드, 하나는 인스턴스 수준이고 다른 하나는 클래스 수준이므로 ThreadSafe로 만들려면 두 개의 다른 개체를 잠 가야합니다.


1

다른 답변에서 설명한 것처럼 정적 메서드는 클래스 모니터를 잠그고 비 정적 메서드는 개체 모니터를 잠그기 때문에 코드는 스레드로부터 안전하지 않습니다 .increment()decrement()

이 코드 예제의 경우 synchronzed키워드 사용 없이 더 나은 솔루션이 존재 합니다. 스레드 안전성을 달성 하려면 AtomicInteger 를 사용해야 합니다 .

다음을 사용하여 스레드 안전 AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.