Synchronized Block 대신 Synchronized Method를 사용하는 것이 유리합니까?


401

예를 들어 동기화 된 블록보다 동기화 된 방법의 장점을 알려주는 사람이 있습니까?




답변:


431

누구든지 예를 들어 동기화 된 블록보다 동기화 된 방법의 장점을 말해 줄 수 있습니까? 감사.

블록보다 동기화 된 방법을 사용하면 확실한 이점이 없습니다.

아마도 유일한 것은 (그러나 나는 그것을 이점이라고 부르지 않을 것입니다) 당신은 객체 참조를 포함 할 필요가 없다는 것 this입니다.

방법:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

블록:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

보다? 전혀 이점이 없습니다.

블록 다른 객체를 잠금으로 사용할 수 있지만 메서드를 동기화하면 전체 객체가 잠길 수 있기 때문에 대부분의 유연성에서 블록 메서드보다 장점이 있습니다.

비교:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

또한 방법이 커지면 동기화 된 섹션을 계속 분리 할 수 ​​있습니다.

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
API 소비자에게 이점은 메소드 선언에 동기화 된 키워드를 사용하면 메소드가 오브젝트 인스턴스에서 동기화되고 스레드로부터 안전하다는 것을 명시 적으로 선언한다는 것입니다.
Scrubbie

59
나는 이것이 오래된 질문이라는 것을 알고 있지만 "this"로 동기화하는 것은 일부 서클에서 반 패턴으로 간주됩니다. 의도하지 않은 결과는 클래스 외부에서 누군가 "this"와 같은 객체 참조를 잠글 수 있으며 다른 스레드가 클래스 내 장벽을 통과하여 교착 상태가 발생할 가능성을 방지 할 수 있다는 것입니다. "private final Object = new Object ();"만들기 잠금 목적으로 만 사용되는 변수가 자주 사용되는 솔루션입니다. 이 문제와 직접 관련된 또 다른 질문 이 있습니다.
justin.hughey

30
"메소드를 동기화하면 전체 클래스가 잠길 수 있습니다." 이것은 정확하지 않습니다. 완전한 클래스를 잠그지 않고 완전한 인스턴스를 잠급니다. 같은 클래스의 여러 객체는 모두 자체 잠금을 유지합니다. :) 접견
codepleb

4
이것에 대한 흥미로운 점은 동기화 된 메소드를 사용하면 생성 된 바이트 코드에 1 개의 명령이 적어진다는 것입니다. 바이트 코드의 길이는 메소드가 인라인되는지 여부의 요소이므로 블록을 메소드 서명으로 이동시키는 것이 결정의 차이가 될 수 있습니다. 어쨌든 이론상. 저장되는 단일 바이트 코드 명령에 대한 디자인 결정을 기반으로하지는 않습니다. 끔찍한 아이디어처럼 보입니다. 하지만 여전히, 그것은 이다 차이. =)
corsiKa

2
@corsiKa : 둘 이상의 명령을 저장합니다. synchronized블록은 두 개의 명령어를 이용하여 구현되며, monitorenter그리고 monitorexit, 플러스 것을 보장 예외 핸들러 monitorexit에도 특별한 경우에 호출된다. synchronized메소드를 사용할 때 모두 저장됩니다 .
Holger

139

유일한 차이점은 동기화 된 블록이 동기화 할 객체를 선택할 수 있다는 것입니다. 동기화 된 메소드는 'this'(또는 동기화 된 클래스 메소드의 해당 클래스 인스턴스) 만 사용할 수 있습니다 . 예를 들어 다음은 의미 상 동일합니다.

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

후자는 모든 객체, 종종 멤버 변수 의 관련 잠금을 위해 경쟁 할 수 있기 때문에 더 유연 합니다. 또한 블록 전후에 동시에 메서드 내에서 동시 코드를 실행할 수 있기 때문에 더 세분화됩니다. 물론 동시 코드를 별도의 비 동기화 메소드로 리팩토링하여 동기화 된 메소드를 쉽게 사용할 수 있습니다. 코드를 이해하기 쉽게 만드는 방법을 사용하십시오.


후자는 foo ()의 모든 코드를 동기화 할 필요가없는 경우에도 장점이 있습니다.
Evan

1
이것은 사실이지만 "Warrior"가 "동기화 방법의 장점"이라고 물었던 것은 아닙니다.
OscarRyz

76

동기화 된 방법

장점 :

  • IDE는 동기화 된 메소드를 표시 할 수 있습니다.
  • 구문이 더 간결합니다.
  • 동기화 된 블록을 분리하여 메소드를 분리합니다.

단점 :

  • 이것과 동기화되므로 외부인도 동기화 할 수 있습니다.
  • 동기화 된 블록 외부로 코드를 이동하기가 더 어렵습니다.

동기화 된 블록

장점 :

  • 잠금에 개인 변수를 사용하여 잠금을 클래스 내부에 유지하도록 허용합니다.
  • 변수에 대한 참조를 검색하여 동기화 된 블록을 찾을 수 있습니다.

단점 :

  • 구문이 더 복잡하므로 코드를 읽기가 어렵습니다.

개인적으로 동기화가 필요한 것에 중점을 둔 클래스와 동기화 된 메소드를 사용하는 것을 선호합니다. 이러한 클래스는 가능한 한 작아야하므로 동기화를 쉽게 검토 할 수 있어야합니다. 다른 사람들은 동기화에 신경 쓸 필요가 없습니다.


"수업 내부에 머물러"라고 말하면 " 객체 내부에 머물러 "라는 말입니까, 아니면 뭔가 빠졌습니까?
OldPeculier

36

주요 차이점은 동기화 된 블록을 사용하는 경우이 이외의 다른 객체를 잠그면 훨씬 더 유연해질 수 있다는 것입니다.

메시지 대기열과 여러 메시지 생산자 및 소비자가 있다고 가정합니다. 우리는 제작자가 서로 간섭하는 것을 원하지 않지만 소비자는 제작자를 기다릴 필요없이 메시지를 검색 할 수 있어야합니다. 그래서 우리는 단지 객체를 생성합니다

Object writeLock = new Object();

그리고 지금부터 프로듀서는 새 메시지를 추가하려고 할 때마다 다음과 같이 잠급니다.

synchronized(writeLock){
  // do something
}

따라서 소비자는 여전히 읽을 수 있으며 생산자는 잠겨 있습니다.


2
귀하의 예는 비파괴 읽기로 제한됩니다. 읽기가 큐에서 메시지를 제거하면 생산자가 큐에 쓸 때 언젠가 완료되면 실패합니다.
ceving

30

동기화 된 방법

동기화 된 방법에는 두 가지 효과가 있습니다.
첫째, 하나의 스레드가 객체에 대해 동기화 된 메소드를 실행하는 경우 첫 번째 스레드가 객체와 함께 완료 될 때까지 동일한 객체 블록에 대해 동기화 된 메소드를 호출하는 다른 모든 스레드 (일시 중단).

둘째, 동기화 된 메소드가 종료되면 동일한 오브젝트에 대한 동기화 된 메소드의 후속 호출과 발생 전 관계를 자동으로 설정합니다. 이를 통해 객체 상태에 대한 변경 사항이 모든 스레드에 표시됩니다.

생성자를 동기화 할 수는 없습니다. 생성자와 동기화 된 키워드를 사용하면 구문 오류가 발생합니다. 생성자를 동기화하는 것은 의미가 없습니다. 객체를 생성하는 스레드 만 생성되는 동안 액세스 할 수 있어야하기 때문입니다.

동기화 된 진술

동기화 된 메소드와 달리, 동기화 된 명령문은 고유 잠금을 제공하는 오브젝트를 지정해야합니다. 대부분은 목록 또는 맵에 대한 액세스를 동기화하기 위해이를 사용하지만 오브젝트의 모든 메소드에 대한 액세스를 차단하고 싶지는 않습니다.

Q : 내장 잠금 및 동기화 동기화는 내장 잠금 또는 모니터 잠금이라고하는 내부 엔터티를 중심으로 구축됩니다. (API 사양은 종종이 엔티티를 단순히 "모니터"라고합니다.) 내장 잠금은 동기화의 두 측면에서 중요한 역할을합니다. 즉 객체 상태에 대한 독점 액세스를 강화하고 가시성에 필수적인 관계를 설정합니다.

모든 객체에는 관련된 고유 잠금이 있습니다. 일반적으로 객체 필드에 독점적이고 일관된 액세스가 필요한 스레드는 객체에 액세스하기 전에 객체의 본질적 잠금을 획득 한 다음 완료되면 본질적 잠금을 해제해야합니다. 스레드는 잠금을 획득하고 잠금을 해제 한 시간 사이에 고유 잠금을 소유한다고합니다. 스레드가 내장 잠금을 소유하는 한 다른 스레드는 동일한 잠금을 얻을 수 없습니다. 다른 스레드는 잠금을 획득하려고 시도 할 때 차단됩니다.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

동기화 된 방법, 블록 및 동기화없이 서로 다른 출력을 교차 점검합니다.


10
지금까지 생성자가 동기화 될 수 없다고 언급 한 것은 +1입니다 . 즉, 생성자에는 실제로 하나의 옵션, 즉 동기화 된 블록 만 있습니다.
ef2011

지시대로 코드를 테스트했지만 C는 항상 0, -2024260031 및 해시 코드를 변경하는 유일한 것입니다. 어떤 행동을보아야합니까?
저스틴 존슨


29

참고 : 정적 동기화 된 메소드 및 블록은 클래스 오브젝트에서 작동합니다.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Java 컴파일러가 소스 코드를 바이트 코드로 변환하면 동기화 된 메소드와 동기화 된 블록을 매우 다르게 처리합니다.

JVM이 동기화 된 메소드를 실행할 때 실행중인 스레드는 메소드의 method_info 구조에 ACC_SYNCHRONIZED 플래그가 설정되어 있음을 식별 한 후 자동으로 오브젝트의 잠금을 획득하고 메소드를 호출 한 후 잠금을 해제합니다. 예외가 발생하면 스레드가 자동으로 잠금을 해제합니다.

반면에 메소드 블록 동기화는 오브젝트의 잠금 및 예외 처리를 확보하기위한 JVM의 내장 지원을 생략하며 기능을 명시 적으로 바이트 코드로 작성해야합니다. 동기화 된 블록이있는 메소드의 바이트 코드를 읽으면이 기능을 관리하기위한 12 가지 이상의 추가 작업이 표시됩니다.

다음은 동기화 된 메소드와 동기화 된 블록을 생성하기위한 호출을 보여줍니다.

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

synchronizedMethodGet()메소드는 다음 바이트 코드를 생성합니다.

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

그리고 synchronizedBlockGet()메소드 의 바이트 코드는 다음과 같습니다.

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

동기화 된 방법과 블록의 한 가지 중요한 차이점은 동기화 된 블록은 일반적으로 잠금 범위를 줄입니다. 잠금 범위는 성능에 반비례하기 때문에 항상 중요한 코드 섹션 만 잠그는 것이 좋습니다. 동기화 된 블록을 사용하는 가장 좋은 예 중 하나는 전체를 잠그는 대신 싱글 톤 패턴의 이중 검사 잠금입니다.getInstance() 방법 Singleton 인스턴스를 만드는 데 사용되는 중요한 코드 섹션 만 잠급니다. 잠금은 한두 번만 필요하므로 성능이 크게 향상됩니다.

동기화 된 메소드를 사용하는 동안 정적 동기화 메소드와 비 정적 동기화 메소드를 모두 혼합하는 경우 특별한주의가 필요합니다.


1
바이트 코드 동기화 방법을 살펴보면 바이트 코드가 더 작고 단순하므로 동기화 블록이 더 빠르지 않은 이유는 무엇입니까?
eatSleepCode

@eatSleepCode 이것은 바이트 코드이며 JVM에 의해 "컴파일"됩니다. JVM은 필요에 추가합니다 monitorentermonitorexit코드를 실행하기 전에.
Philip Couling

12

가장 자주 나는 이것을 사용하여 목록이나 맵에 대한 액세스를 동기화하지만 객체의 모든 메소드에 대한 액세스를 차단하고 싶지 않습니다.

다음 코드에서 목록을 수정하는 하나의 스레드는 맵을 수정하는 스레드를 기다리는 것을 차단하지 않습니다. 메소드가 오브젝트에서 동기화 된 경우 각 메소드는 수정 사항이 충돌하지 않더라도 대기해야합니다.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

동기화 된 블록을 사용하면 여러 개의 동기화기를 가질 수 있으므로 동시에 서로 충돌하지 않는 여러 가지 작업을 동시에 수행 할 수 있습니다.


6

리플렉션 API를 사용하여 동기화 된 메소드를 확인할 수 있습니다. 이는 모델의 모든 방법이 동기화되는 등 일부 계약을 테스트하는 데 유용 할 수 있습니다 .

다음 스 니펫은 Hashtable의 모든 동기화 된 메소드를 인쇄합니다.

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

동기화 된 블록 사용시 중요 사항 : 잠금 객체로 사용하는 것을주의하십시오!

위의 user2277816의 코드 스 니펫은 문자열 리터럴에 대한 참조가 잠금 오브젝트로 사용된다는 점에서이 점을 보여줍니다. 문자열 리터럴은 Java에서 자동으로 삽입되며 문제를보기 시작해야합니다. 리터럴 "lock"에서 동기화되는 모든 코드는 동일한 잠금을 공유합니다! 이것은 완전히 관련이없는 코드 조각으로 교착 상태를 쉽게 일으킬 수 있습니다.

주의해야 할 것은 단지 String 객체가 아닙니다. 오토 박싱과 valueOf 메소드는 값에 따라 동일한 객체를 재사용 할 수 있기 때문에 박스형 프리미티브도 위험합니다.

자세한 내용은 https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused를 참조하십시오.


5

메소드 레벨에서 잠금을 사용하는 것은 너무 무례합니다. 전체 메소드를 잠금으로써 공유 자원에 액세스하지 않는 코드를 잠그는 이유는 무엇입니까? 각 객체에는 잠금이 있으므로 더미 객체를 만들어 블록 수준 동기화를 구현할 수 있습니다. 전체 레벨을 잠그지 않기 때문에 블록 레벨이 더 효율적입니다.

여기 몇 가지 예

방법 수준

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

블록 레벨

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[편집하다]

의 경우 Collection처럼 Vector그리고 Hashtable그들은 동기화하면됩니다 ArrayList또는 HashMap하지 당신이 키워드를 동기화 또는 컬렉션 동기화 방법 호출 세트 필요 :

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

유일한 차이점 : 동기화 된 블록은 동기화 된 방법과 달리 세분화 된 잠금을 허용합니다

원래 synchronized 블록이나 메소드는 메모리 불일치 오류를 피함으로써 스레드 안전 코드를 작성하는 데 사용되었습니다.

이 질문은 매우 오래되었으며 지난 7 년 동안 많은 것들이 바뀌 었습니다. 스레드 안전을 위해 새로운 프로그래밍 구성이 도입되었습니다.

synchronied블록 대신 고급 동시성 API를 사용하여 스레드 안전성을 확보 할 수 있습니다 . 이 문서 페이지 는 스레드 안전성을 달성하기위한 좋은 프로그래밍 구성을 제공합니다.

잠금 개체 는 많은 동시 응용 프로그램을 단순화하는 잠금 관용구를 지원합니다.

실행 자는 스레드를 시작하고 관리하기위한 고급 API를 정의합니다. java.util.concurrent가 제공하는 실행기 구현은 대규모 애플리케이션에 적합한 스레드 풀 관리를 제공합니다.

동시 수집을 사용하면 대규모 데이터 수집을보다 쉽게 ​​관리 할 수 ​​있으며 동기화 필요성을 크게 줄일 수 있습니다.

원자 변수 에는 동기화를 최소화하고 메모리 일관성 오류를 방지하는 기능이 있습니다.

ThreadLocalRandom (JDK 7)은 여러 스레드에서 의사 난수를 효율적으로 생성합니다.

동기화를위한 더 나은 대체 는 API 를 사용하는 ReentrantLock입니다Lock

동기화 된 메소드 및 명령문을 사용하여 액세스 된 암시 적 모니터 잠금과 동일한 기본 동작 및 의미를 갖는 재진입 상호 배제 잠금.

잠금이있는 예 :

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

java.util.concurrentjava.util.concurrent.atomic을 참조하십시오.다른 프로그래밍 구성에 대해서는 패키지도 .

이 관련 질문도 참조하십시오.

동기화 및 잠금


4

동기화 된 방법은 모든 개체를 잠그는 데 사용됩니다. 동기화 된 블록은 특정 개체를 잠그는 데 사용됩니다.


3

일반적으로 이것들은 암시 적이 객체에 대해 사용중인 객체의 모니터에 대해 명시 적이 지 않은 것과 거의 동일합니다. 때때로 간과되는 동기화 된 방법의 한 가지 단점은 "this"참조를 사용하여 동기화 할 때 동일한 객체에서 외부 객체가 잠길 가능성이 있다는 것입니다. 당신이 그것을 실행하면 그것은 매우 미묘한 버그가 될 수 있습니다. 내부 명시 적 객체 또는 다른 기존 필드에서 동기화하면 동기화를 완전히 캡슐화하여이 문제를 피할 수 있습니다.


2

이미 언급했듯이 동기화 된 기능이 "this"만 사용하는 경우 동기화 된 블록은 사용자 정의 변수를 잠금 객체로 사용할 수 있습니다. 물론 동기화해야 할 기능 영역을 조작 할 수 있습니다. 그러나 모든 사람들은 "this"를 잠금 객체로 사용하여 전체 기능을 다루는 동기화 된 기능과 블록의 차이점은 없다고 말합니다. 그것은 사실이 아니며, 두 상황에서 생성되는 바이트 코드의 차이입니다. 동기화 된 블록 사용의 경우 "this"에 대한 참조를 보유하는 로컬 변수를 할당해야합니다. 결과적으로 함수의 크기가 조금 더 커질 것입니다 (함수 수가 적은 경우에는 관련이 없음).

차이점에 대한 자세한 설명은 http://www.artima.com/insidejvm/ed2/threadsynchP.html을 참조 하십시오.


2

동기화 된 메소드의 경우 오브젝트에서 잠금이 획득됩니다. 그러나 동기화 블록을 사용하면 잠금을 획득 할 객체를 지정할 수 있습니다.

예 :

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

나는 이것이 오래된 질문이라는 것을 알고 있지만 여기에 대한 답변을 빨리 읽었을 때 누군가가 때때로 그것을 언급하는 것을 보지 못했습니다. synchronized 메서드가 잘못된 잠금 장치 .
실제로 Java 동시성 (72 페이지)에서 :

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

위의 코드는 모양 스레드 안전되는합니다. 그러나 실제로는 그렇지 않습니다. 이 경우 클래스의 인스턴스에서 잠금을 얻습니다. 그러나 해당 메소드를 사용하지 않는 다른 스레드 가 목록 을 수정할 수 있습니다. 올바른 접근 방식은

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

위의 코드는 동기화 된 블록이 완료 될 때까지 목록 을 수정 하려는 모든 스레드가 목록 을 수정 하지 못하도록 차단 합니다 .


현재이 책을 읽는 중 ... 궁금해 ... 목록이 공개 대신 비공개이고 putIfAbsent 메소드 만 가지고 있다면, 동기화 (this)로 충분합니까? 당면한 문제는 ListHelper 외부에서도 목록을 수정할 수 있기 때문입니다.
dtc

@dtc 그렇습니다. 목록이 비공개이고 클래스의 다른 곳에서 유출되지 않으면 클래스의 다른 모든 메소드를 동기화 된 것으로 수정하는 클래스의 다른 모든 메소드를 표시하는 한 충분합니다. 그러나 바로이 대신 전체 방법을 잠금 List반드시 동기화 할 필요가없는 코드의 로그가있는 경우 성능 문제가 발생할 수 있습니다
aarbor

말이 되네요 답변 해 주셔서 감사합니다! tbh, 나는이 책이 나의 지식을 넓히고 멀티 스레딩에 접근하는 방법에 매우 유용하다는 것을 알았다. 그러나 그것은 또한 나에게 완전히 새로운 혼란의 세계를 소개했다
dtc

2

실제적으로, 동기화 된 블록에 비해 동기화 된 방법의 장점은 더 바보 저항 적이라는 것입니다. 잠그는 임의의 객체를 선택할 수 없으므로 동기화 된 메소드 구문을 잘못 사용하여 문자열 리터럴 잠금 또는 스레드 아래에서 변경되는 변경 가능한 필드의 내용 잠금과 같은 어리석은 일을 할 수 없습니다.

반면에 동기화 된 메소드를 사용하면 객체에 대한 참조를 얻을 수있는 스레드가 잠금을 획득하지 못하게 할 수 없습니다.

따라서 동기화 된 메소드를 메소드에서 수정 자로 사용하는 것이 암소를 아프게하지 않도록 보호하는 데 도움이되는 반면, 개인 최종 잠금 오브젝트와 함께 동기화 된 블록을 사용하면 암소로부터 자신의 코드를 보호하는 것이 더 좋습니다.


1

Java 스펙 요약에서 : http://www.cs.cornell.edu/andru/javaspec/17.doc.html

동기화 된 명령문 (§14.17)은 객체에 대한 참조를 계산합니다. 그런 다음 해당 오브젝트에 대한 잠금 조치를 시도하고 잠금 조치가 완료 될 때까지 더 이상 진행하지 않습니다. ...

동기화 된 메소드 (§8.4.3.5)는 호출 될 때 자동으로 잠금 조치를 수행합니다. 잠금 조치가 완료 될 때까지 본문이 실행되지 않습니다. 메소드가 인스턴스 메소드경우, 메소드는 호출 된 인스턴스 (즉, 메소드 본문 실행 중이 오브젝트로 알려진 오브젝트)와 연관된 잠금을 잠급니다. 메소드가 static 인 경우 메소드가 정의 된 클래스를 나타내는 Class 오브젝트와 연관된 잠금을 잠급니다. ...

이 설명을 바탕으로, 나는 대부분의 이전 답변이 정확하다고 말하고 동기화 된 메소드는 정적 메소드에 특히 유용 할 수 있습니다. 한정된."

편집 : 원래 이들은 실제 Java 사양의 인용이라고 생각했습니다. 이 페이지는 사양에 대한 요약 / 설명 일뿐입니다.


1

TLDR; synchronized수정 자나 synchronized(this){...}표현식을 사용 하지 말고 개인 오브젝트를 보유하는 최종 인스턴스 필드는 synchronized(myLock){...}어디에 있습니까 myLock?


synchronized메소드 선언 에서 수정자를 사용하는 synchronized(..){ }것과 메소드 본문 의 표현식 의 차이점 은 다음과 같습니다.

  • synchronized방법의 서명에 지정된 수정
    1. 생성 된 JavaDoc에 표시됩니다.
    2. Modifier.SYNCHRONIZED 에 대한 메소드 수정자를 테스트 할 때 리플렉션을 통해 프로그래밍 방식으로 결정할 수 있습니다 .
    3. 에 비해 입력 및 들여 쓰기가 덜 필요 synchronized(this) { .... }하며
    4. (IDE에 따라) 클래스 개요 및 코드 완성에 표시됩니다.
    5. this비 정적 메소드에서 선언 될 때 오브젝트를 잠금으로 사용 하거나 정적 메소드에서 선언 될 때 엔 클로징 클래스를 사용합니다.
  • synchronized(...){...}표현을 할 수 있습니다
    1. 메소드 본문 부분의 실행 만 동기화
    2. 생성자 내에서 사용하거나 ( 정적 ) 초기화 블록 내에서 사용
    3. 동기화 된 액세스를 제어하는 ​​잠금 개체를 선택합니다.

그러나 synchronized수정자를 사용하거나 잠금 객체로 사용하는 경우 ( synchronized(...) {...}this같이 synchronized(this) {...}) 동일한 단점이 있습니다. 둘 다 동기화 할 잠금 개체로 자체 인스턴스를 사용합니다. 이것은 위험하므로 객체 자체뿐만 아니라 임의의 잠재적으로 심각한 부작용 (성능 저하와 함께 동기 로크로 사용할 수있는 객체에 대한 참조를 보유하고 다른 외부 물체 / 코드 교착 ).

따라서 가장 좋은 방법은 synchronized수정 자나 synchronized(...)표현식 this을 잠금 객체 와 함께 사용하지 않고이 객체의 전용 잠금 객체를 사용하는 것입니다. 예를 들면 다음과 같습니다.

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

여러 잠금 개체를 사용할 수도 있지만 중첩 사용시 교착 상태가 발생하지 않도록 특별한주의를 기울여야합니다.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

이 질문이 Thread Safe SingletonDouble check locking을 사용한 Lazy 초기화 의 차이점에 관한 것 입니다. 특정 싱글 톤을 구현해야 할 때 항상이 기사를 참조하십시오.

글쎄, 이것은 Thread Safe Singleton입니다 .

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

장점 :

  1. 게으른 초기화가 가능합니다.

  2. 스레드 안전합니다.

단점 :

  1. getInstance () 메소드는 동기화되어 여러 스레드가 동시에 액세스 할 수 없으므로 성능이 저하됩니다.

이것은 이중 점검 잠금을 사용한 지연 초기화입니다 .

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

장점 :

  1. 게으른 초기화가 가능합니다.

  2. 또한 스레드 안전합니다.

  3. 키워드 동기화로 인해 성능이 저하되었습니다.

단점 :

  1. 처음에는 성능에 영향을 줄 수 있습니다.

  2. 단점으로. 이중 체크 잠금 방식은 고성능 멀티 스레드 애플리케이션에 사용될 수 있습니다.

자세한 내용은이 기사를 참조하십시오.

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/


-3

스레드와 동기화 1) 작동하지 않는 스레드에서 동기화 된 것을 사용하지 마십시오. (this)와 동기화하면 현재 스레드가 잠금 스레드 오브젝트로 사용됩니다. 각 스레드는 다른 스레드와 독립적이므로 동기화 조정이 없습니다. 2) 코드 테스트에 따르면 Mac의 Java 1.6에서는 메소드 동기화가 작동하지 않습니다. 3) synchronized (lockObj) 여기서 lockObj는 동기화하는 모든 스레드의 공통 공유 객체입니다. 4) ReenterantLock.lock () 및 .unlock ()이 작동합니다. 이에 대한 Java 학습서를 참조하십시오.

다음 코드는 이러한 점을 보여줍니다. 또한 ArrayList로 대체되는 스레드 안전 Vector가 포함되어있어 Vector에 추가하는 많은 스레드가 정보를 잃지 않고 ArrayList와 동일한 정보가 손실 될 수 있음을 보여줍니다. 0) 현재 코드는 경쟁 조건으로 인한 정보 손실을 표시합니다. A) 현재 레이블이있는 A 행을 주석 처리하고 그 위의 A 행을 주석 해제 한 다음 실행하면 메소드가 데이터를 유실하지만 그렇지 않아야합니다. B) A 단계를 취소하고 주석 처리를 제거하고 // 블록 종료}. 그런 다음 데이터 손실이없는 결과를 보려면 실행하십시오. C) 주석을 해제하십시오. C의 주석 처리를 제거하십시오. 모든 변형을 완료 할 시간이 없습니다. 이것이 도움이되기를 바랍니다. (this)에서 동기화하거나 메소드 동기화가 작동하는 경우 테스트 한 Java 및 OS 버전을 지정하십시오. 감사합니다.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

1
와 동기화 '(이)' 않는 일을, 그리고 않습니다 하지 , '동기화 객체로 현재 스레드를 사용' 하지 않는 한 현재의 객체가 스레드를 확장하는 클래스이다. -1
Lorne의 후작
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.