Java에서 동기화되지 않습니까?


381

Java 동기화에 대해 SO에 대한 질문이 나타날 때마다 일부 사람들은 synchronized(this)피해야 할 점을 지적하기를 간절히 원합니다. 대신, 그들은 개인 참조에 대한 잠금이 바람직하다고 주장합니다.

주어진 이유 중 일부는 다음과 같습니다.

저를 포함한 다른 사람들 synchronized(this)은 Java 라이브러리에서도 많이 사용되는 관용구가 안전하고 잘 이해되고 있다고 주장합니다 . 버그가 있고 멀티 스레드 프로그램에서 무슨 일이 일어나고 있는지에 대한 단서가 없기 때문에 피해서는 안됩니다. 즉, 해당되는 경우 사용하십시오.

나는 일을 this할 때 잠금을 피하는 것이 선호되는 실제 예제 (foobar 내용 없음)를 보는 데 관심 synchronized(this)이 있습니다.

따라서 항상 synchronized(this)개인 참조의 잠금으로 피하고 교체해야합니까?


추가 정보 (답변이 주어지면 업데이트 됨) :

  • 우리는 인스턴스 동기화에 대해 이야기하고 있습니다.
  • 암시 적 ( synchronized메소드) 및 명시 적 형식이 synchronized(this)모두 고려됩니다.
  • 주제에 대한 Bloch 또는 기타 권한을 인용하는 경우, 원하지 않는 부분을 제외하지 마십시오 (예 : 효과적인 Java, 스레드 안전성의 항목 : 일반적으로 인스턴스 자체의 잠금이지만 예외가 있습니다).
  • synchronized(this)제공하는 것 이외의 잠금 장치에서 세분성이 필요한 경우 synchronized(this)해당 사항이 없으므로 문제가 아닙니다.

4
또한 컨텍스트가 중요하다는 점을 지적하고 싶습니다. "일반적으로 인스턴스 자체의 잠금"비트는 잠금을 공개 할 때 조건부 스레드 안전 클래스를 문서화하는 방법에 대한 섹션 내에 있습니다. 다시 말해,이 문장은 이미 결정을 내렸을 때 적용됩니다.
Jon Skeet

내부 동기화가없고 외부 동기화가 필요한 경우 잠금은 종종 인스턴스 자체라고 Bloch는 기본적으로 말합니다. 그렇다면 'this'에 잠금이 설정된 내부 동기화의 경우가 아닌 이유는 무엇입니까? (문서의 중요성은 또 다른 문제입니다.)
eljenso

외부 개체를 잠그면 별도의 캐시 라인을 수정하고 CPU 캐시간에 교환해야하기 때문에 확장 된 세분성 및 추가 CPU 캐시와 버스 요청 오버 헤드간에 상충 관계가 있습니다 (MESIF 및 MOESI 참조).
ArtemGr

1
방어 프로그래밍의 세계에서는 관용구가 아니라 코드로 버그를 예방한다고 생각합니다. 누군가 "나의 동기화가 얼마나 최적화되어 있습니까?"라는 질문을 할 때 "다른 사람이 관용구를 따르지 않는 한"대신 "아주"라고 말하고 싶습니다.
Sgene9

답변:


132

각 포인트를 개별적으로 다룰 것입니다.

  1. 일부 악의적 인 코드가 잠금을 훔칠 수 있습니다 (매우 인기있는이 코드에는 "우연히"변형이 있습니다)

    실수로 더 걱정 됩니다. this중요한 것은이 사용이 클래스의 노출 된 인터페이스의 일부이며 문서화되어야한다는 것입니다. 때로는 다른 코드가 잠금을 사용하는 능력이 필요합니다. 이것은 Collections.synchronizedMap(javadoc 참조) 와 같은 것 입니다.

  2. 동일한 클래스 내의 모든 동기화 된 메소드는 정확히 동일한 잠금을 사용하여 처리량을 줄입니다.

    이것은 지나치게 단순한 사고입니다. 제거해도 synchronized(this)문제가 해결되지 않습니다. 처리량에 대한 적절한 동기화는 더 많은 생각이 필요합니다.

  3. 정보가 너무 많이 노출되는 경우

    이것은 # 1의 변형입니다. 사용은 synchronized(this)인터페이스의 일부입니다. 이 노출을 원하지 않거나 필요로하지 않으면하지 마십시오.


1. "동기화"는 클래스의 노출 된 인터페이스의 일부가 아닙니다. 2. 동의 3. 참조 1
eljenso

66
기본적으로 (이)가 동기화 되어 가 외부 코드가 클래스의 작동에 영향을 미칠 수 있다는 것을 의미하기 때문에 노출. 언어가 아니더라도 인터페이스로 문서화해야한다고 주장합니다.
Darron

15
비슷한. Javadoc for Collections.synchronizedMap ()을 참조하십시오. 반환 된 객체는 내부적으로 synchronized (this)를 사용하며 소비자는 반복과 같은 대규모 원자 연산에 대해 동일한 잠금을 사용하기 위해이를 활용할 것으로 기대합니다.
Darron

3
실제로 Collections.synchronizedMap ()은 내부적으로 synchronized (this)를 사용하지 않으며 개인용 최종 잠금 오브젝트를 사용합니다.
Bas Leijdekkers

1
@Bas Leijdekkers : 문서 는 동기화가 반환 된지 도 인스턴스에서 발생하도록 명확하게 지정합니다. 뭐, 흥미로운 점은 뷰에 의해 반환 된입니다 keySet()values()(자신의)에 고정하지 않는 this,하지만 중요한지도 인스턴스가 모든지도 작업을위한 일관된 동작을 얻을 수 있습니다. 잠금 오브젝트가 변수로 고려되는 이유는 서브맵 SynchronizedSortedMap이 원래 맵 인스턴스에서 잠그는 서브맵을 구현하기 위해 서브 클래스가 필요하기 때문입니다.
Holger

86

글쎄, 먼저 다음을 지적해야합니다.

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

의미 적으로 다음과 같습니다.

public synchronized void blah() {
  // do stuff
}

사용하지 않는 한 가지 이유 synchronized(this)입니다. 당신은 synchronized(this)블록 주위에 물건을 할 수 있다고 주장 할 수 있습니다 . 일반적인 이유는 동기화 검사를 전혀하지 않아도되기 때문에 모든 종류의 동시성 문제, 특히 이중 검사 잠금 문제가 발생 하기 때문에 비교적 간단한 검사가 얼마나 어려운지를 보여줍니다. 스레드 세이프.

개인 잠금 장치는 방어 메커니즘이며 결코 나쁜 생각이 아닙니다.

또한 암시하는 것처럼 개인 잠금 장치는 세분성을 제어 할 수 있습니다. 개체의 한 작업 집합은 다른 작업과 전혀 관련이 없지만 synchronized(this)상호 액세스를 모두 제외합니다.

synchronized(this) 정말 아무 것도주지 않습니다.


4
"동기화 (이것)는 실제로 아무 것도주지 않습니다." 좋아, 그것을 synchronize (myPrivateFinalLock)로 바꿉니다. 그게 나에게 무엇을 주나요? 당신은 그것이 방어 적 메커니즘이라는 것에 대해 이야기합니다. 나는 무엇으로부터 보호 받습니까?
eljenso

14
외부 객체에 의한 'this'의 우연한 (또는 악의적 인) 잠금으로부터 보호됩니다.
cletus 2012 년

14
나는이 답변에 전혀 동의하지 않습니다. 잠금은 항상 가능한 한 가장 짧은 시간 동안 유지되어야하며, 이것이 전체 방법을 동기화하는 대신 동기화 된 블록 주위에 "물건을 만들고"싶은 이유입니다. .
Olivier

5
동기화 된 블록 외부에서 작업하는 것은 항상 좋은 의도입니다. 요점은 사람들은 이중 점검 잠금 문제와 마찬가지로 많은 시간이 걸리고 심지어 그것을 깨닫지 못한다는 것입니다. 지옥으로가는 길은 좋은 의도로 포장되어 있습니다.
cletus 2016 년

16
나는 일반적으로 "X는 방어 적 메커니즘이며 결코 나쁜 생각이 아닙니다"에 동의하지 않습니다. 이 태도 때문에 불필요하게 부풀어 오른 코드가 많이 있습니다.
finnw

54

synchronized (this)를 사용하는 동안 클래스 인스턴스를 잠금 자체로 사용하고 있습니다. 잠금에 의해 획득되는 동안이 방법 것을 쓰레드 (1)스레드 2 기다려야한다.

다음 코드를 가정하십시오.

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

변수 수정 방법 1 가변 수정 방법 2 (B)는 두 개의 스레드로 동일한 변수의 동시적인 변경은 피해야하고있다. 스레드 1 수정 a스레드 2 수정 b 동안 경쟁 조건없이 수행 할 수 있습니다.

불행히도, 위의 코드는 잠금에 대해 동일한 참조를 사용하기 때문에이를 허용하지 않습니다. 이것은 스레드가 경쟁 조건에 있지 않더라도 대기해야하며 코드가 프로그램의 동시성을 희생한다는 것을 의미합니다.

이 용액을 사용하는 것 (2) 에 대한 다른 잠금 가지 변수 :

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }


    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

위의 예에서 사용하는 더 세밀한 자물쇠 (2 잠금 대신 한 ( 라카lockB 변수 및 B 각각) 그 결과 더 동시성을 허용하는 반면에, 그 첫 번째 예보다 훨씬 복잡했다 ...


이것은 매우 위험합니다. 이제 클라이언트 측 (이 클래스의 사용자) 잠금 순서 요구 사항을 소개했습니다. 두 개의 스레드가 다른 순서로 method1 () 및 method2 ()를 호출하는 경우 교착 상태가 발생할 가능성이 있지만이 클래스의 사용자는 이것이 사실인지 전혀 모릅니다.
daveb

7
"동기화 됨 (this)"이 제공하지 않는 세분성은 내 질문의 범위를 벗어납니다. 그리고 잠금 필드가 최종적이지 않아야합니까?
eljenso

10
교착 상태가 발생하려면 A에 의해 동기화 된 블록에서 B.
daveb에

3
내가 볼 수있는 한이 예제에는 교착 상태가 없습니다. 나는 그것이 의사 코드 일 뿐이지 만 java.util.concurrent.locks.ReentrantLock과 같은 java.util.concurrent.locks.Lock 구현 중 하나를 사용합니다.
Shawn Vader

15

나는 교의적인 규칙에 맹목적으로 응하지 않는 것에 동의하지만 "잠금 훔치기"시나리오가 당신에게 편심 해 보이는가? 스레드는 실제로 "외부"객체의 잠금을 획득 할 수 있습니다 (synchronized(theObject) {...} ) 동기화 된 인스턴스 메소드를 기다리는 다른 스레드를 차단할 수 있습니다.

악의적 인 코드를 믿지 않는 경우이 코드가 타사에서 제공 될 수 있습니다 (예 : 응용 프로그램 서버를 개발하는 경우).

"우연한"버전은 덜 가능성이있는 것처럼 보이지만 "멍청한 무언가를 만들면 더 나은 바보를 발명 할 것"이라고합니다.

그래서 저는 수업에 따라 달라지는 생각 학교에 동의합니다.


다음 eljenso의 첫 3 개의 댓글 수정 :

잠금 도용 문제를 경험 한 적이 없지만 가상 시나리오는 다음과 같습니다.

시스템이 서블릿 컨테이너이고 우리가 고려하는 객체가 ServletContext구현 이라고 가정 해 봅시다 . 그 getAttribute컨텍스트 속성은 공유 데이터이기 때문에 방법은 스레드 안전해야합니다; 그래서 당신은로 선언합니다 synchronized. 컨테이너 구현을 기반으로 퍼블릭 호스팅 서비스를 제공한다고 가정 해 봅시다.

저는 귀하의 고객이며 귀하의 사이트에 "좋은"서블릿을 배포합니다. 내 코드에에 대한 호출이 포함되어 있습니다 getAttribute.

다른 고객으로 위장한 해커는 사이트에 악의적 인 서블릿을 배포합니다. 그것은에 다음 코드를 포함init메소드에 .

동기화 됨 (this.getServletConfig (). getServletContext ()) {
   while (true) {}
}

동일한 서블릿 컨텍스트를 공유한다고 가정하면 (두 서블릿이 동일한 가상 호스트에있는 한 스펙에서 허용됨) 호출 getAttribute이 영원히 잠 깁니다. 해커가 내 서블릿에서 DoS를 달성했습니다.

getAttribute타사 코드가이 잠금을 획득 할 수 없으므로 개인 잠금에서 동기화 된 경우이 공격이 불가능합니다 .

필자는이 예제가 서블릿 컨테이너의 작동 방식에 대해 지나치게 단순화 된 견해를 가지고 있음을 인정하지만 IMHO가 그 점을 증명합니다.

따라서 보안 고려 사항에 따라 디자인을 선택합니다. 인스턴스에 액세스 할 수있는 코드를 완전히 제어 할 수 있습니까? 스레드가 인스턴스에 잠금을 무기한으로 보유한 결과는 무엇입니까?


그것은 클래스에 따라 다릅니다 : 그것이 '중요한'객체라면 개인 참조에 고정됩니까? 다른 인스턴스 잠금만으로 충분합니까?
eljenso

6
예, 잠금 도용 시나리오는 나에게 훨씬 반가운 것 같습니다. 모두가 그것을 언급하지만 누가 실제로 그것을했거나 경험 했습니까? 실수로 "실수로"개체를 잠그면 이런 종류의 상황에 대한 이름이 있습니다. 이는 버그입니다. 고치세요
eljenso

4
또한 내부 참조에 대한 잠금은 "외부 동기화 공격"으로부터 자유롭지 않습니다. 코드의 특정 동기화 된 부분이 외부 이벤트 (예 : 파일 쓰기, DB 값, 타이머 이벤트)가 발생할 때까지 대기한다는 것을 알고 있다면 그것도 막을 수 있도록 준비하십시오.
eljenso

내가 어릴 때했다고해도 내가 그 바보 중 하나라고 고백하겠습니다. 명시 적 잠금 객체를 만들지 않고 코드가 더 깨끗하다고 ​​생각했지만 대신 모니터에 참여해야하는 다른 개인 최종 객체를 사용했습니다. 객체 자체가 자체적으로 동기화되었음을 알지 못했습니다. 당신은 다음의 hijinx를 상상할 수 있습니다 ...
Alan Cabrera

12

상황에 따라 다릅니다.
공유 엔터티가 하나만 있거나 둘 이상인 경우

전체 작업 예를 보려면 여기 클릭하십시오.

작은 소개.

스레드 및 공유 가능한 엔티티
여러 스레드가 동일한 엔티티에 액세스 할 수 있습니다 (예 : 단일 messageQueue를 공유하는 다중 connectionThreads). 스레드가 동시에 실행되기 때문에 엉망인 상황 일 수있는 다른 데이터로 데이터를 대체 할 수 있습니다.
따라서 공유 가능한 엔터티에 한 번에 하나의 스레드 만 액세스 할 수있는 방법이 필요합니다. (CONCURRENCY).

동기화 된 블록
synchronized () 블록은 공유 가능한 엔티티에 대한 동시 액세스를 보장하는 방법입니다.
첫째, 작은 비유
화장실 내부에 세 사람 P1, P2 (스레드) 세면기 (공유 가능 개체)가 있고 문 (자물쇠)이 있다고 가정합니다.
이제 한 번에 한 사람이 세면기를 사용하기를 원합니다.
P1이 작업을 완료 할 때까지 대기합니다.
P1이 도어
를 잠금 해제 한 다음 p1 만 세면기를 사용할 수 있습니다.

통사론.

synchronized(this)
{
  SHARED_ENTITY.....
}

"this"는 클래스와 관련된 고유 잠금을 제공했습니다 (자바 개발자는 각 오브젝트가 모니터로 작동 할 수있는 방식으로 오브젝트 클래스를 설계했습니다). 위의 방법은 하나의 공유 엔터티와 여러 스레드 (1 : N) 만있는 경우에는 제대로 작동합니다. N 개의 공유 가능 엔티티 -M 스레드 이제 화장실 내부에 두 개의 세면기가 있고 하나의 문만있는 상황을 생각해보십시오. 이전 방식을 사용하는 경우 p2 만 한 번에 하나의 세면기를 사용할 수 있지만 p2는 외부에서 대기합니다. 아무도 B2 (세면기)를 사용하지 않기 때문에 자원 낭비입니다. 더 현명한 접근법은 세면실 내부에 더 작은 방을 만들고 세면기 당 하나의 문을 제공하는 것입니다. 이런 식으로 P1은 B1에 액세스 할 수 있고 P2는 B2에 액세스 할 수 있으며 그 반대도 마찬가지입니다.
여기에 이미지 설명을 입력하십시오

washbasin1;  
washbasin2;

Object lock1=new Object();
Object lock2=new Object();

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

여기에 이미지 설명을 입력하십시오
여기에 이미지 설명을 입력하십시오

스레드 ----> 에 대한 자세한 내용은 여기를 참조 하십시오.


11

이에 대한 C # 및 Java 캠프에는 다른 합의가 있습니다. 내가 본 Java 코드의 대부분은 다음을 사용합니다.

// apply mutex to this instance
synchronized(this) {
    // do work here
}

반면 C # 코드의 대다수는 논란의 여지없이 더 안전한 것을 선택합니다.

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

C # 관용구가 더 안전합니다. 앞에서 언급했듯이 인스턴스 외부에서는 잠금에 악의적이거나 우발적으로 액세스 할 수 없습니다. Java 코드에도 이러한 위험이 있지만 Java 커뮤니티는 시간이 지남에 따라 약간 덜 안전하지만 좀 더 간결한 버전으로 몰려 들었습니다.

그것은 자바에 대한 발굴이 아니며 두 언어에 대한 나의 경험을 반영한 것입니다.


3
아마도 C #은 더 젊은 언어이기 때문에 Java 캠프에서 알아 낸 나쁜 패턴과 이와 같은 코드를 통해 더 잘 배울 수 있습니까? 싱글 톤도 적습니까? :)
Bill K

3
그는. 아마도 사실이지만 나는 미끼에 오르지 않을 것입니다! 내가 확실히 말할 수있는 한 가지 생각은 C # 코드에 더 많은 대문자가 있다는 것입니다.)
serg10

1
사실이 아님 (잘 정리하기 위해)
tcurdt

7

java.util.concurrent패키지는 스레드 안전 코드의 복잡성을 크게 줄였습니다. 나는 일화적인 증거 만 가지고 있지만, 내가 본 대부분의 작업 synchronized(x)은 Lock, Semaphore 또는 Latch를 다시 구현하지만 더 낮은 수준의 모니터를 사용하는 것으로 보입니다 .

이러한 점을 염두에두고 이러한 메커니즘 중 하나를 사용하여 동기화하는 것은 잠금을 누설하지 않고 내부 객체를 동기화하는 것과 유사합니다. 이것은 두 개 이상의 스레드로 모니터에 대한 항목을 제어 할 수 있다는 절대적인 확신이 있다는 점에서 유리합니다.


6
  1. 가능하면 데이터를 변경할 수 없도록 만드십시오 ( final 변수)
  2. 여러 스레드에서 공유 데이터의 변형을 피할 수없는 경우 고급 프로그래밍 구문 (예 : 세분화 된 LockAPI)을 사용하십시오.

잠금은 공유 자원에 대한 독점 액세스를 제공합니다. 한 번에 하나의 스레드 만 잠금을 획득 할 수 있으며 공유 자원에 대한 모든 액세스는 먼저 잠금을 획득해야합니다.

인터페이스 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()
     }
   }
 }

동기화 잠금의 장점 (this)

  1. 동기화 된 메소드 또는 명령문을 사용하면 모든 잠금 획득 및 해제가 블록 구조 방식으로 발생합니다.

  2. 잠금 구현은 다음을 제공하여 동기화 된 메소드 및 명령문 사용에 대한 추가 기능을 제공합니다.

    1. 잠금을 획득하려는 비 차단 시도 (tryLock() )
    2. 중단 될 수있는 잠금 획득 시도 (lockInterruptibly() )
    3. 시간 초과 될 수있는 잠금 획득 시도 ( tryLock(long, TimeUnit)).
  3. Lock 클래스는 다음과 같이 암시 적 모니터 잠금과는 다른 동작 및 의미를 제공 할 수 있습니다.

    1. 보장 된 주문
    2. 재진입이 아닌 사용법
    3. 교착 상태 감지

다양한 유형에 대한이 SE 질문을 살펴보십시오 Locks.

동기화 및 잠금

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

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

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

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

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

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

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


5

결정한 경우 :

  • 당신이해야 할 일은 현재 객체를 잠그는 것입니다. 과
  • 전체 방법보다 작은 단위로 고정하려고합니다.

그런 다음 synchronizezd (this)에 대한 금기가 보이지 않습니다.

어떤 사람들은 실제로 동기화되는 객체가 "독자에게 더 명확"하다고 생각하기 때문에 메소드의 전체 내용 내에서 의도적으로 동기화 된 (this) 메소드를 사용합니다. 사람들이 현명한 선택을하는 한 (예 : 그렇게하면 실제로 여분의 바이트 코드를 메소드에 삽입하고 잠재적 최적화에 영향을 줄 수 있음을 이해합니다), 특히 이것에 문제가 없습니다. . 당신은 항상 당신의 프로그램의 동시 행동을 문서화해야합니다. 그래서 나는 " '동기화'가 행동을 주장한다"라는 주장이 그렇게 강력하다는 것을 보지 못합니다.

어떤 객체의 잠금을 사용 해야하는지에 관해서는, 현재 객체의 동기화 와 클래스의 일반적인 사용 방식 에 따라 현재 객체를 동기화하는 데 아무런 문제가 없다고 생각 합니다 . 예를 들어 컬렉션의 경우 논리적으로 잠그려고하는 개체는 일반적으로 컬렉션 자체입니다.


1
"이것이 논리에 의해 예상된다면 ..."은 내가 넘어 가려고하는 요점이다. 나는 항상 개인 잠금 장치를 사용 하는 요점을 보지 못합니다 .하지만 일반적인 합의는 그것이 상처를 입지 않고 방어 적이므로 더 나은 것으로 보입니다.
eljenso

4

Brian Goetz의 Java Concurrency In Practice라는 책에서 이러한 기술이 왜 중요한 기술인지에 대한 좋은 설명이 있다고 생각합니다. 그는 한 점을 매우 명확하게합니다. 개체의 상태를 보호하기 위해 동일한 잠금 장치 "모든 곳에"를 사용해야합니다. 동기화 된 방법과 객체의 동기화는 종종 함께 진행됩니다. 예를 들어 Vector는 모든 방법을 동기화합니다. 벡터 객체에 대한 핸들이 있고 "없는 경우 입력"을 수행하려는 경우 자체 메서드를 동기화하는 벡터만으로는 상태 손상으로부터 보호 할 수 없습니다. 동기화 된 (vectorHandle)을 사용하여 동기화해야합니다. 이로 인해 벡터에 대한 핸들이있는 모든 스레드에서 SAME 잠금을 획득하고 벡터의 전체 상태를 보호합니다. 이를 클라이언트 측 잠금이라고합니다. 우리는 사실 벡터가 동기화 (이) / 모든 메소드를 동기화하므로 객체 vectorHandle을 동기화하면 벡터 객체 상태가 올바르게 동기화됩니다. 스레드 안전 컬렉션을 사용하기 때문에 스레드 안전하다고 믿는 것은 어리석은 일입니다. 이것이 ConcurrentHashMap이 putIfAbsent 메소드를 명시 적으로 도입 한 이유입니다.

요약하자면

  1. 메소드 레벨에서 동기화하면 클라이언트 측 잠금이 가능합니다.
  2. 개인 잠금 개체가있는 경우 클라이언트 쪽 잠금이 불가능합니다. 클래스에 "없는 경우 입력"유형의 기능이 없다는 것을 알고 있으면 좋습니다.
  3. 라이브러리를 디자인하는 경우이 라이브러리를 동기화하거나 메소드를 동기화하는 것이 더 현명합니다. 수업이 어떻게 진행될 지 결정하기 어려운 위치에 있기 때문입니다.
  4. Vector가 개인 잠금 개체를 사용했다면 "없으면 입력"이 불가능했을 것입니다. 클라이언트 코드는 개인 잠금을 처리하지 않으므로 EXACT SAME LOCK을 사용하여 상태를 보호하는 기본 규칙을 위반합니다.
  5. 다른 사람이 지적 했듯이이 방법이나 동기화 된 방법으로 동기화하면 문제가 발생합니다. 누군가가 잠금을 해제하여 절대 해제 할 수는 없습니다. 다른 모든 스레드는 잠금이 해제 될 때까지 계속 대기합니다.
  6. 그래서 당신이하고있는 일을 알고 올바른 것을 채택하십시오.
  7. 누군가는 개인 잠금 개체를 갖는 것이 더 세분성을 제공한다고 주장했다. 그러나 이것은 디자인 냄새이며 코드 냄새가 아니라고 생각합니다. 두 작업이 완전히 관련이 없다면 왜 SAME 클래스의 일부입니까? 클래스 클럽이 왜 관련이없는 기능을해야합니까? 유틸리티 클래스 일 수 있습니까? 흠-같은 인스턴스를 통해 문자열 조작 및 달력 날짜 형식을 제공하는 일부 유틸리티? ... 적어도 말이 안 돼요 !!

3

아니요, 항상 그런 것은 아닙니다 . 그러나 특정 객체에 대해 자체적으로 스레드 안전 해야하는 여러 문제가있을 때 피하는 경향이 있습니다. 예를 들어 "label"및 "parent"필드가있는 변경 가능한 데이터 개체가있을 수 있습니다. 이것들은 스레드 안전해야하지만 하나를 변경하면 다른 하나가 쓰거나 읽히는 것을 막을 필요는 없습니다. (실제로 나는 필드를 휘발성으로 선언하거나 java.util.concurrent의 AtomicFoo 래퍼를 사용하여 이것을 피할 것입니다).

일반적으로 동기화는 스레드가 서로 어떻게 작동 할 수 있는지 정확하게 생각하지 않고 큰 잠금을 해제하기 때문에 약간 어색합니다. synchronized(this)"아무도 아무것도 바꿀 수 없다"는 말이 있기 때문에 사용 은 더 성 가시고 반 사회적입니다 내가 자물쇠를 쥐고있는 동안 아무도이 수업의 이다. 실제로 얼마나 자주해야합니까?

차라리 더 세밀한 자물쇠를 갖고 싶습니다. 모든 것이 변경되는 것을 멈추고 싶을지라도 (아마도 객체를 직렬화하는 경우), 모든 잠금을 획득하여 동일한 것을 달성하고 더 명확하게 표현할 수 있습니다. 를 사용할 때 synchronized(this)왜 동기화하고 있는지 또는 부작용이 무엇인지 명확하지 않습니다. 당신이 synchronized(labelMonitor)또는 더 나은 것을 사용한다면 , labelLock.getWriteLock().lock()당신이하고있는 일과 중요한 부분의 효과가 제한되는 것이 분명합니다.


3

짧은 대답 : 코드에 따라 차이점을 이해하고 선택해야합니다.

긴 대답 : 일반적으로 경쟁을 줄이기 위해 동기화 (이) 를 피하려고 하지만 개인 잠금은 알아야 할 복잡성을 추가합니다. 따라서 올바른 작업에 올바른 동기화를 사용하십시오. 멀티 스레드 프로그래밍에 익숙하지 않은 경우 인스턴스 잠금을 고수 하고이 주제를 읽으십시오. (그것이 말했다 : 단지 sync (this)를 사용한다고 해서 클래스가 완전히 스레드로부터 안전 해지지는 않습니다.) 이것은 쉬운 주제는 아니지만 일단 익숙해지면 synchronize (this) 를 사용할 지에 대한 대답 은 자연스럽지 않습니다. .


그것이 당신의 경험에 달려 있다고 말할 때 당신을 올바르게 이해합니까?
eljenso

우선, 작성하려는 코드에 따라 다릅니다. sync (this)를 사용하지 않기 위해 전환 할 때 약간의 경험이 필요할 수 있습니다.
tcurdt 2018 년

2

잠금은 가시성 또는 일부 데이터의 동시 수정 을 방지 하여 레이스로 이어질 수 있습니다.

기본 유형 작업을 원 자성으로 만들어야 할 때와 같은 사용 가능한 옵션이 있습니다 AtomicInteger.

그러나 서로 관련이있는 두 개의 정수가 x있고y 서로 관련되어 있으며 원자 방식으로 변경해야 좌표를. 그런 다음 동일한 잠금을 사용하여 보호합니다.

잠금은 서로 관련된 상태 만 보호해야합니다. 더 이상 그리고 더 이상. synchronized(this)각 메소드에서 사용 하면 클래스의 상태가 관련이 없어도 관련되지 않은 상태를 업데이트하더라도 모든 스레드가 경합에 직면합니다.

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

상기 예에서는 단 하나의 방법을 모두 가지고있는 변이 xy없는 두 가지 방법과 같은 xy관련된 돌연변이와 I는 두 가지 방법을 준 경우 xy별도로 그럼 없었을 것이다 스레드 안전.

이 예제는 단지 구현 방법을 보여주기위한 것이 아니며 반드시 구현해야하는 것은 아닙니다. 가장 좋은 방법은 그것을 확인하는 것입니다 수행하는 불변의 입니다.

Point예 와 반대로 , TwoCounters@Andreas 가 이미 제공 한 예가 있습니다. 여기서 상태는 서로 다른 두 개의 잠금으로 보호되는 상태가 서로 관련이 없습니다.

서로 다른 잠금을 사용하여 관련없는 상태를 보호하는 프로세스를 잠금 스트라이핑 또는 잠금 분할이라고합니다.


1

이것을 동기화하지 않는 이유 는 때로는 두 개 이상의 잠금이 필요하기 때문입니다 (두 번째 잠금은 추가로 생각한 후에 종종 제거되지만 중간 상태에서는 여전히 필요합니다). 이것을 잠그면 항상 두 자물쇠 중 어느 것이 이것 인지 기억해야 합니다. . 개인 객체를 잠그면 변수 이름이 알려줍니다.

독자의 관점에서 this에 잠금 이 표시 되면 항상 두 가지 질문에 대답해야합니다.

  1. 이것에 의해 어떤 종류의 액세스가 보호 됩니까?
  2. 하나의 자물쇠가 정말 충분합니까, 누군가가 버그를 도입하지 않았습니까?

예를 들면 :

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

두 개의 스레드 longOperation()가 두 개의 서로 다른 인스턴스에서 시작되면 BadObject잠금을 획득합니다. 호출 할 때l.onMyEvent(...) 스레드가 다른 객체의 잠금을 획득 할 수 없기 때문에 교착 상태가 발생합니다.

이 예에서는 짧은 조작과 긴 잠금을 위해 두 개의 잠금을 사용하여 교착 상태를 제거 할 수 있습니다.


2
이 예제에서 교착 상태를 얻는 유일한 방법은 BadObjectA가 longOperationB를 호출 하고 A를 전달 myListener하고 그 반대도 마찬가지입니다. 불가능하지는 않지만 이전 단계를 뒷받침하는 상당히 복잡합니다.
eljenso

1

이미 언급했듯이 동기화 된 기능이 "this"만 사용하는 경우 동기화 된 블록은 사용자 정의 변수를 잠금 객체로 사용할 수 있습니다. 물론 동기화해야 할 기능 영역을 조작 할 수 있습니다.

그러나 모든 사람들은 "this"를 잠금 객체로 사용하여 전체 기능을 다루는 동기화 된 기능과 블록의 차이점은 없다고 말합니다. 그것은 사실이 아니며, 두 상황에서 생성되는 바이트 코드의 차이입니다. 동기화 된 블록 사용의 경우 "this"에 대한 참조를 보유하는 로컬 변수를 할당해야합니다. 결과적으로 우리는 약간 더 큰 기능의 크기를 갖게 될 것입니다 (함수의 수가 적은 경우에는 관련이 없습니다).

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

또한 다음 관점으로 인해 동기화 된 블록의 사용이 좋지 않습니다.

synchronized 키워드는 한 영역에서 매우 제한되어 있습니다. 동기화 된 블록을 종료 할 때 해당 잠금을 기다리는 모든 스레드는 차단 해제되어야하지만 해당 스레드 중 하나만 잠금을 수행합니다. 다른 모든 사람들은 잠금이 해제 된 것을보고 차단 상태로 돌아갑니다. 이는 많은 낭비되는 처리주기가 아닙니다. 종종 스레드 차단을 해제하기위한 컨텍스트 전환에는 디스크에서 메모리를 페이징하는 것이 포함되기 때문에 비용이 매우 많이 듭니다.

이 영역에 대한 자세한 내용은 다음 기사를 읽는 것이 좋습니다. http://java.dzone.com/articles/synchronized-considered


1

이것은 실제로 다른 답변을 보완하는 것이지만 개인 객체를 잠금에 사용하는 것에 대한 주된 반대 의견이 비즈니스 로직과 관련이없는 필드로 클래스를 어지럽히는 경우 Project Lombok은 @Synchronized컴파일 타임에 상용구를 생성해야합니다.

@Synchronized
public int foo() {
    return 0;
}

컴파일

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}

0

synchronized (this)를 사용하는 좋은 예입니다.

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

여기에서 볼 수 있듯이, 동기화 된 메소드를 사용하여 긴 (루프 방식의 무한 루프 방법)과 쉽게 협력 할 수 있도록 동기화합니다.

물론 개인 필드에서 동기화를 사용하여 쉽게 다시 작성할 수 있습니다. 그러나 때로는 동기화 된 메소드가있는 디자인이 이미있는 경우 (예 : 레거시 클래스에서 파생 된 것은 동기화 된 것입니다 (이)가 유일한 솔루션 일 수 있음).


어떤 물체라도 여기서 자물쇠로 사용할 수 있습니다. 반드시 그럴 필요는 없습니다 this. 개인 필드 일 수 있습니다.
finnw

맞지만이 예제의 목적은 메소드 동기화를 사용하기로 결정한 경우 적절한 동기화 방법을 보여주는 것이 었습니다.
Bart Prokop

0

그것은 당신이하고 싶은 일에 달려 있지만 나는 그것을 사용하지 않을 것입니다. 또한 달성하려는 스레드 저장을 처음에 동기화하여 수행 할 수 없었는지 확인하십시오. API에는 유용한 잠금이 있습니다. :)


0

의존성없이 코드의 원자 부분에서 고유 한 개인 참조에 대한 가능한 솔루션에 대해서만 언급하고 싶습니다. 스택 정보 (전체 클래스 이름 및 줄 번호)를 사용하여 필요한 참조를 자동으로 생성하는 atomic ()이라는 간단한 정적 메소드와 잠금이있는 정적 해시 맵을 사용할 수 있습니다. 그런 다음 새 잠금 오브젝트를 쓰지 않고 동기화 명령문에서이 메소드를 사용할 수 있습니다.

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}

0

synchronized(this)잠금 메커니즘으로 사용하지 마십시오 . 이렇게하면 전체 클래스 인스턴스가 잠기고 교착 상태가 발생할 수 있습니다. 이러한 경우 전체 클래스가 잠기지 않도록 특정 메소드 또는 변수 만 잠그도록 코드를 리팩터링하십시오. Synchronised메소드 레벨 내부에서 사용할 수 있습니다.
대신 사용하는 synchronized(this)코드를 보여주는 아래, 당신은 단지 방법을 잠글 수있는 방법.

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }

0

이 질문이 이미 해결되었을 수도 있지만 2019 년 내 2 센트.

'this'에 대한 잠금은 수행중인 작업을 알고 있지만 'this'에 대한 잠금 뒤에있는 경우 나쁘지 않습니다 (불행히도 메소드 정의에서 동기화 된 키워드가 허용하는 것).

실제로 클래스 사용자가 잠금을 '훔칠'수있게하려면 (즉, 다른 스레드가 처리하지 못하도록) 실제로 다른 동기화 메소드가 실행되는 동안 모든 동기화 된 메소드가 대기하기를 원합니다. 의도적이고 잘 생각해야합니다 (따라서 사용자가 이해하도록 돕기 위해 문서화 됨).

더 정교하게 설명하려면, 반대로 접근 할 수없는 잠금 장치를 잠그면 (아무도 잠금 장치를 '훔칠'수 없으며, 완전히 제어 할 수있는 등) '얻고있는'(또는 '잃어버린') 것을 알아야합니다. ..).

나에게 문제는 메소드 정의 서명의 키워드 동기화로 프로그래머 가 생각 하기가 너무 쉽다는 것입니다 잠금에있는 당신이 여러 문제로 실행하지 않으려는 경우에 대해 생각하는 강력한 중요한 것은 무엇인지에 대해 스레드 프로그램.

'일반적으로'클래스의 사용자가 이러한 일을 할 수 있기를 원하지 않거나 '일반적으로'원하는 것을 주장 할 수는 없습니다 ... 코딩하는 기능에 달려 있습니다. 모든 유스 케이스를 예측할 수 없으므로 엄지 손가락 규칙을 작성할 수 없습니다.

예를 들어 내부 잠금 장치를 사용하지만 출력이 인터리브되기를 원하지 않으면 여러 스레드에서 사용하기 위해 노력하는 프린트 라이터를 고려하십시오.

클래스 외부에서 잠금에 액세스 할 수 있는지 여부는 클래스의 기능에 따라 프로그래머로서 결정하는 것입니다. 그것은 API의 일부입니다. 예를 들어 코드를 사용하여 코드 변경을 중단하지 않으면 서 동기화 된 (this)에서 동기화 된 (provateObjet)으로 이동할 수 없습니다.

참고 1 : 명시 적 잠금 객체를 사용하고 노출하여 동기화 된 (이) '아치 브'를 달성 할 수 있다는 것을 알고 있지만 행동이 잘 문서화되어 있고 '이'에 대한 잠금이 실제로 의미하는 경우 불필요하다고 생각합니다.

참고 2 : 일부 코드가 실수로 자물쇠를 훔치면 버그를 해결해야한다는 주장과 일치하지 않습니다. 이것은 방식이 공개적이지 않더라도 모든 메소드를 공개 할 수 있다고 말하는 것과 같은 주장입니다. 누군가가 실수로 '사적'이라고 부르는 경우 버그로 생각합니다. 왜이 사고를 처음에 가능하게합니까 !!! 만약 당신의 자물쇠를 훔치는 능력이 당신의 수업에 문제라면 그것을 허락하지 마십시오. 저것과 같이 쉬운.


-3

나는 하나의 (누군가의 잠금을 사용하는) 포인트와 두 개의 (같은 잠금을 불필요하게 사용하는 모든 메소드)가 상당히 큰 응용 프로그램에서 발생할 수 있다고 생각합니다. 특히 개발자들 사이에 좋은 의사 소통이 없을 때.

그것은 돌로 만들어지지 않았으며, 대부분 좋은 연습과 오류 방지의 문제입니다.

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