문제

SO에서 Java 동기화에 대한 질문이 나타날 때마다 일부 사람들은 다음을 지적하고 싶어합니다. synchronized(this) 피해야한다.대신, 그들은 개인 참조에 대한 잠금이 선호된다고 주장합니다.

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

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

잠금을 피하는 실제 사례(foobar 항목 제외)를 보고 싶습니다. this 다음과 같은 경우에 바람직합니다. synchronized(this) 일도 했을 것이다.

그러므로: 항상 피해야 할까? synchronized(this) 개인 참조에 대한 잠금으로 바꾸시겠습니까?


추가 정보(답변이 제공되면 업데이트됨):

  • 인스턴스 동기화에 대해 이야기하고 있습니다.
  • 둘 다 암시적(synchronized 메소드) 및 명시적 형식 synchronized(this) 고려된다
  • 해당 주제에 대해 Bloch나 다른 권위자를 인용하는 경우 마음에 들지 않는 부분을 빼지 마십시오(예:효과적인 Java, 스레드 안전 항목: 일반적으로 인스턴스 자체에 대한 잠금이지만 예외도 있습니다.)
  • 잠금 이외의 세분성이 필요한 경우 synchronized(this) 제공한 다음 synchronized(this) 적용되지 않으므로 문제가 되지 않습니다.
도움이 되었습니까?

해결책

각 지점을 개별적으로 다룰 것입니다.

  1. 일부 사악한 코드는 당신의 자물쇠를 훔칠 수 있습니다 (매우 인기있는이 코드는 또한 "우연히"변형이 있습니다)

    더 걱정됩니다 우연히. 그것이 중요하다는 것은이 사용입니다 this 클래스의 노출 인터페이스의 일부이며 문서화해야합니다. 때로는 다른 코드가 잠금을 사용하는 능력이 필요합니다. 이것은 같은 것들에 해당됩니다 Collections.synchronizedMap (Javadoc 참조).

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

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

  3. 당신은 (불필요하게) 너무 많은 정보를 드러내고 있습니다

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

다른 팁

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

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

의미 적으로 동일합니다.

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

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

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

또한, 당신이 암시했듯이, 개인 자물쇠는 세분성을 제어 할 수 있습니다. 객체의 한 세트 세트는 다른 작업과 전혀 관련이 없지만 synchronized(this) 그들 모두에 대한 접근을 상호 배제 할 것입니다.

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

동기화 된 (이)를 사용하는 동안 클래스 인스턴스를 잠금 자체로 사용하고 있습니다. 이것은 잠금이 획득되는 동안 스레드 1,, 스레드 2 기다려야합니다.

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

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


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

방법 1 변수 수정 및 방법 2 변수 수정 , 두 스레드에 의한 동일한 변수의 동시 수정을 피해야합니다. 그러나 스레드 1 수정 그리고 스레드 2 수정 경주 상태없이 수행 할 수 있습니다.

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

해결책은 사용하는 것입니다 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 개의 잠금 장치로카 그리고 잠금 변수의 경우 그리고 각각) 그리고 결과적으로 더 나은 동시성을 허용하며, 반면에 그것은 첫 번째 예보다 더 복잡해졌습니다 ...

나는 독단적 인 규칙을 맹목적으로 고수하지 않는 것에 동의하지만, "잠금 훔치기"시나리오는 당신에게 편심 한 것처럼 보입니까? 실은 실제로 "외부 적으로"객체의 잠금을 얻을 수 있습니다 (synchronized(theObject) {...}), 동기화 된 인스턴스 메소드에서 대기하는 다른 스레드 차단.

악성 코드를 믿지 않는 경우이 코드는 제 3 자 (예 : 일종의 애플리케이션 서버를 개발하는 경우)에서 나올 수 있음을 고려하십시오.

"우발적 인"버전은 가능성이 낮아 보이지만, "바로 바보를 방지하고 누군가가 더 나은 바보를 발명 할 것"이라고 말합니다.

그래서 나는 IT- 의존적 인 수업의 생각 학교에 동의합니다.


Eljenso의 첫 3 개의 댓글에 이어 편집 :

나는 자물쇠 도둑질 문제를 경험 한 적이 없지만 여기에 상상의 시나리오가 있습니다.

귀하의 시스템이 서블릿 컨테이너이고 우리가 고려하는 대상은 ServletContext 구현. 그것의 getAttribute 컨텍스트 속성이 공유 데이터이므로 메소드는 스레드 안전이어야합니다. 그래서 당신은 그것을로 선언합니다 synchronized. 또한 컨테이너 구현을 기반으로 공개 호스팅 서비스를 제공한다고 상상해 봅시다.

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

다른 고객으로 위장한 해커는 사이트에 악의적 인 서블릿을 배포합니다. 다음 코드가 포함되어 있습니다 init 방법:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

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

이 공격은 불가능하다면 불가능합니다 getAttribute 제 3 자 코드 가이 잠금을 얻을 수 없기 때문에 개인 잠금 장치에 동기화됩니다.

나는 예제가 고안되었고 서블릿 컨테이너가 어떻게 작동하는지에 대한 지나치게 단순한 견해를 인정하지만, IMHO는 요점을 증명한다.

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

C#과 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 Code는이 위험이 있습니다. 그러나 Java 커뮤니티는 시간이 지남에 따라 약간 덜 안전하지만 약간 더 간결한 버전으로 끌려간 것 같습니다.

그것은 Java에 대한 발굴을 의미하는 것이 아니라 두 언어에서 일하는 내 경험을 반영하는 것입니다.

상황에 따라 다릅니다.
하나의 공유 엔티티 또는 하나 이상의 것이있는 경우.

전체 작업 예를 참조하십시오 여기

작은 소개.

스레드와 공유 가능한 엔티티
여러 스레드가 동일한 엔티티에 액세스 할 수 있습니다. 스레드가 동시에 실행되기 때문에 다른 사람으로 자신의 데이터를 재정의 할 가능성이있을 수 있으며, 이는 엉망인 상황 일 수 있습니다.
따라서 공유 가능한 엔티티에 한 번에 하나의 스레드 만 액세스 할 수있는 방법이 필요합니다. (동시성).

동기화 된 블록
Synchronized () 블록은 공유 가능한 엔티티의 동시 액세스를 보장하는 방법입니다.
첫째, 작은 비유
화장실 안에는 2 인 P1, P2 (스레드) 세탁소 (공유 가능한 엔티티)가 있고 문 (잠금)이 있다고 가정합니다.
이제 우리는 한 사람이 한 번에 Washbasin을 사용하기를 원합니다.
접근 방식은 도어가 잠겨있을 때 P1이 P1을 잠그는 것입니다.
P1은 문을 잠금 해제합니다
그런 다음 P1 만 Washbasin을 사용할 수 있습니다.

통사론.

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

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

washbasin1;  
washbasin2;

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

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

enter image description here
enter image description here

스레드에 대해 자세히 알아보십시오----> 여기

그만큼 java.util.concurrent 패키지는 스레드 안전 코드의 복잡성을 크게 줄였습니다. 나는 계속할 일화적인 증거 만 가지고 있지만, 내가 본 대부분의 일은 synchronized(x) 자물쇠, 세마포어 또는 래치를 다시 구현하는 것처럼 보이지만 하위 레벨 모니터를 사용하는 것으로 보입니다.

이를 염두에두고, 이러한 메커니즘을 사용하여 동기화하는 것은 잠금을 누출하지 않고 내부 객체와 동기화하는 것과 유사합니다. 이는 둘 이상의 스레드에 의해 모니터에 들어가는 것을 제어한다는 절대적인 확실성이 있다는 점에서 유리합니다.

  1. 가능하다면 데이터를 변경할 수 없도록 만드세요( final 변수)
  2. 여러 스레드에서 공유 데이터의 변경을 피할 수 없는 경우 고급 프로그래밍 구성을 사용하세요.세분화된 Lock 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()
     }
   }
 }

동기화에 비해 잠금의 장점(this)

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

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

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

    1. 보장된 주문
    2. 비재진입 사용량
    3. 교착 상태 감지

다양한 유형의 SE 질문을 살펴보십시오. Locks:

동기화와 잠금

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

객체 잠금 많은 동시 애플리케이션을 단순화하는 잠금 관용어를 지원합니다.

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

동시 컬렉션 대규모 데이터 컬렉션을 더 쉽게 관리할 수 있으며 동기화 필요성이 크게 줄어듭니다.

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

스레드로컬랜덤 (JDK 7)은 여러 스레드에서 의사 난수를 효율적으로 생성하는 기능을 제공합니다.

인용하다 java.util.concurrent 그리고 java.util.concurrent.atomic 다른 프로그래밍 구성을 위한 패키지도 있습니다.

그렇게 결정했다면:

  • 당신이해야 할 일은 현재 객체를 잠그는 것입니다.그리고
  • 당신은 원한다 다음보다 작은 세분성으로 잠급니다. 전체 방법;

그러면 syncizd(this)에 대한 금기가 표시되지 않습니다.

어떤 사람들은 어떤 객체가 실제로 동기화되고 있는지가 "독자에게 더 명확하다"고 생각하기 때문에 메소드의 전체 내용 내에서 (메소드를 동기화로 표시하는 대신) 의도적으로 동기화(this)를 사용합니다.사람들이 정보에 입각한 선택을 하는 한(예:그렇게 하면 실제로 메서드에 추가 바이트코드를 삽입하고 있으며 이는 잠재적인 최적화에 연쇄 영향을 미칠 수 있다는 점을 이해하세요.) 특별히 이에 대한 문제는 보이지 않습니다.항상 프로그램의 동시 동작을 문서화해야 합니다. 그러면 "'동기화'가 동작을 게시합니다"라는 주장이 그다지 설득력이 없다고 생각됩니다.

어떤 객체의 잠금을 사용해야 하는지 질문드리자면, 현재 객체에 동기화를 해도 문제가 없을 것 같습니다. 현재 수행 중인 작업의 논리와 클래스가 일반적으로 사용되는 방식에 따라 이것이 예상되는 경우.예를 들어 컬렉션의 경우 논리적으로 잠길 것으로 예상되는 개체는 일반적으로 컬렉션 자체입니다.

Brian Goetz가 실제로 Java Concurrency라는 책에서 왜 이들 각각이 당신의 벨트 아래에 중요한 기술인지에 대한 좋은 설명이 있다고 생각합니다. 그는 한 점을 매우 명확하게합니다. 당신은 당신의 물체의 상태를 보호하기 위해 같은 잠금 "모든 곳에서"를 사용해야합니다. 동기화 된 메소드와 객체의 동기화는 종종 손을 잡고 있습니다. 예를 들어 벡터는 모든 방법을 동기화합니다. 벡터 객체에 대한 핸들이 있고 "결석하면"벡터를 동기화하는 벡터를 동기화하는 것은 상태의 부패로부터 당신을 보호하지 않을 것입니다. 동기화 된 (vectorhandle)를 사용하여 동기화해야합니다. 이로 인해 벡터에 대한 핸들이 있고 벡터의 전체 상태를 보호하는 모든 스레드에 의해 동일한 잠금이 획득됩니다. 이것을 클라이언트 측 잠금이라고합니다. 사실 벡터가 모든 방법을 동기화 / 동기화하는 사실로 알고 있습니다. 따라서 객체 벡터 핸들에 동기화하면 벡터 객체 상태가 올바르게 동기화됩니다. 스레드 안전 컬렉션을 사용하고 있기 때문에 스레드 안전하다고 믿는 것은 어리석은 일입니다. 이것이 바로 그러한 작업을 원자력으로 만드는 Putifabsent 방법을 명시 적으로 도입 한 이유입니다.

요약해서 말하자면

  1. 메소드 레벨에서 동기화하면 클라이언트 측면 잠금이 가능합니다.
  2. 개인 잠금 객체가있는 경우 클라이언트 측 잠금을 불가능하게 만듭니다. 수업에 "결석이없는 경우"기능이 없다는 것을 알고 있다면 괜찮습니다.
  3. 라이브러리를 설계하는 경우 -이를 동기화하거나 동기화하는 방법은 종종 더 현명합니다. 당신은 당신의 수업이 어떻게 사용될 것인지 결정할 수있는 입장에 거의 없기 때문입니다.
  4. 벡터가 개인 잠금 객체를 사용했다면 "결석이 없으면"오른쪽으로 얻는 것은 불가능했을 것입니다. 클라이언트 코드는 개인 잠금 장치에 대한 핸들을 얻지 못하므로 상태를 보호하기 위해 동일한 잠금 장치를 사용하는 기본 규칙을 깨뜨립니다.
  5. 이 또는 동기화 된 메소드를 동기화하면 다른 사람들이 지적했듯이 문제가 발생합니다. 누군가가 잠금을 받고 해제 할 수 없습니다. 다른 모든 스레드는 잠금이 해제되기를 계속 기다릴 것입니다.
  6. 그러니 당신이 무엇을하고 있는지 알고 올바른 것을 입양하십시오.
  7. 누군가가 개인 잠금 객체를 갖는 것이 더 나은 세분성을 제공한다고 주장했다. 그러나 이것은 코드 냄새가 아닌 디자인 냄새라고 생각합니다. 두 작업이 완전히 관련이 없다면 왜 같은 클래스의 일부가 있습니까? 클럽 클럽과 관련이없는 기능이 전혀 없어야하는 이유는 무엇입니까? 유틸리티 클래스 일 수 있습니까? hmmmm- 문자열 조작 및 달력 날짜 형식을 제공하는 일부 유틸리티는 동일한 인스턴스를 통해 ?? ... 적어도 나에게는 말이되지 않습니다 !!

아니, 당신은 안됩니다 언제나. 그러나 특정 물체에 여러 가지 우려 사항이있을 때 자신과 관련하여 실시 해야하는 경우를 피하는 경향이 있습니다. 예를 들어, "레이블"및 "부모"필드가있는 변이 가능한 데이터 객체가있을 수 있습니다. 이것들은 ThreadSafe이어야하지만, 하나를 변경하는 것은 다른 하나를 쓰여진 것으로 차단할 필요가 없습니다. (실제로 나는 휘발성 필드를 선언하거나 java.util.concurrent의 Atomicfoo Wrappers를 사용하여 이것을 피할 것입니다).

일반적으로 동기화는 스레드가 서로 어떻게 작동하는지 정확히 생각하기보다는 큰 잠금을 때리기 때문에 약간 서투른 것입니다. 사용 synchronized(this) "아무도 변할 수 없을 수도 있습니다. 아무것 이 수업에서 내가 자물쇠를 잡는 동안 ". 실제로 얼마나 자주 그렇게해야합니까?

나는 오히려 더 세분화 된 자물쇠를 가지고 싶다. 모든 것이 바뀌는 것을 막기를 원하더라도 (아마도 객체를 직렬화하는 것일 수도 있음), 모든 자물쇠를 획득하여 같은 것을 달성 할 수 있으며, 그렇게 더 명백합니다. 당신이 사용할 때 synchronized(this), 왜 당신이 동기화하는지, 부작용이 무엇인지 정확히 명확하지 않습니다. 사용하는 경우 synchronized(labelMonitor), 또는 더 나은 labelLock.getWriteLock().lock(), 당신이하고있는 일과 중요한 섹션의 영향이 어떤 것으로 제한되는지는 분명합니다.

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

긴 대답: 일반적으로 나는 오히려 피하려고 노력합니다 동기화 (this) 경합을 줄이지 만 개인 자물쇠는 복잡성을 더해야합니다. 따라서 올바른 작업에 올바른 동기화를 사용하십시오. 멀티 스레드 프로그래밍에 경험이 없다면 인스턴스 잠금을 고수 하고이 주제를 읽으십시오. (그 말 : 그냥 사용합니다 동기화 (this) 자동으로 클래스를 완전히 스레드-안전하게 만들지는 않습니다.) 이것은 쉬운 주제가 아니지만 일단 익숙해지면 사용 여부에 대한 답변 동기화 (this) 또는 자연스럽게 오지 않습니다.

잠금 장치는 어느 쪽에도 사용됩니다 시계 또는 일부 데이터를 보호합니다 동시 수정 레이스로 이어질 수 있습니다.

원자가되기 위해 원시 유형 작업을 해야하는 경우 사용 가능한 옵션이 있습니다. 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;
   }
}

위의 예에서는 두 가지를 돌연변이하는 방법이 하나뿐입니다. x 그리고 y 그리고 두 가지 다른 방법이 아닙니다 x 그리고 y 관련이 있고 돌연변이를위한 두 가지 다른 방법을 제공했다면 x 그리고 y 별도로 실로 안전하지 않았을 것입니다.

이 예는 단지 구현 해야하는 방식을 보여주기위한 것입니다. 가장 좋은 방법은 그것을 만드는 것입니다 불변.

이제 반대 Point 예를 들어, 예가 있습니다 TwoCounters @Andreas가 이미 제공 한 상태는 상태가 서로 관련이없는 두 개의 다른 자물쇠로 보호되는 상태가 제공됩니다.

관련없는 상태를 보호하기 위해 다른 자물쇠를 사용하는 과정을 호출합니다. 줄무늬를 잠그거나 잠금 분할

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

독자의 관점에서 잠금이있는 경우 이것, 당신은 항상 두 가지 질문에 대답해야합니다.

  1. 어떤 종류의 접근이 보호됩니다 이것?
  2. 하나의 잠금 장치가 실제로 충분합니까? 누군가 버그를 소개하지 않았습니까?

An example:

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(...), 스레드 중 어느 것도 다른 물체의 잠금을 얻을 수 없기 때문에 교착 상태가 있습니다.

이 예에서는 두 개의 잠금 장치를 사용하여 교착 상태를 제거 할 수 있으며, 하나는 짧은 작업용과 하나는 긴 자물쇠를 사용하여 교착 상태를 제거 할 수 있습니다.

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

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

여기에서 찾을 수있는 차이에 대한 자세한 설명 :http://www.artima.com/insidejvm/ed2/threadsynchp.html

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

동기화 된 키워드는 한 영역에서 매우 제한적입니다. 동기화 된 블록을 종료 할 때 해당 잠금을 기다리는 모든 스레드는 차단 해제되어야하지만 해당 스레드 중 하나만 잠금을 가져갑니다. 다른 모든 사람들은 자물쇠가 가져 와서 차단 된 상태로 돌아갑니다. 그것은 많은 낭비 처리주기가 아닙니다. 종종 스레드를 차단하기위한 컨텍스트 스위치에는 디스크에서 메모리를 페이징하는 것도 매우 비쌉니다.

이 분야의 자세한 내용은이 기사를 읽는 것이 좋습니다.http://java.dzone.com/articles/synchronized-considered

이것은 실제로 다른 답변을 보충하는 것이지만, 잠금을 위해 개인 객체를 사용하는 것에 대한 주요 반대 의견이 있다면, 비즈니스 로직과 관련이없는 필드와 클래스를 방해한다는 것입니다. @Synchronized 컴파일 타임에 보일러 플레이트를 생성하려면 :

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

컴파일

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

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

동기화 된 사용에 좋은 예입니다 (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...
}

여기에서 볼 수 있듯이, 우리는 동기화 된 메소드와 길이로 쉽게 협력하기 위해 동기화를 사용합니다.

물론 개인 필드에서 동기화 된 사용으로 매우 쉽게 다시 작성할 수 있습니다. 그러나 때로는 동기화 된 메소드 (즉, 레거시 클래스, 동기화 된)가 유일한 솔루션이 될 수있는 디자인이 이미 있으면 이미 디자인이있을 때 때로는 이미 디자인이 있습니다.

그것은 당신이하고 싶은 작업에 달려 있지만 나는 그것을 사용하지 않을 것입니다. 또한, 당신이 동반하려는 스레드 사원이 처음에 동기화 (이것)를 통해 수행 할 수 없는지 확인하십시오. 좋은 것도 있습니다 API에서 잠금 그것은 당신을 도울 수 있습니다 :)

의존성없이 코드의 원자 부분에서 고유 한 개인 참조에 대한 가능한 솔루션 만 언급하고 싶습니다. 스택 정보 (전체 클래스 이름 및 줄 번호)를 사용하여 필요한 참조를 자동으로 생성하는 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
}

사용하지 마십시오 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...
          }
        }
      }
    }

이 질문이 이미 해결되었지만 2019 년의 2 센트.

당신이하고있는 일을 알고 있다면 '이것'을 잠그는 것은 나쁘지 않지만 'this'를 잠그는 장면 뒤에 있습니다 (불행히도 메소드 정의에서 동기화 된 키워드가 허용하는 것).

실제로 클래스 사용자가 잠금 장치를 '도용'할 수 있기를 원한다면 (즉, 다른 스레드가 그것을 다루는 것을 방지) 실제로 다른 동기화 메소드가 실행되는 동안 대기하기를 원합니다. 의도적이고 생각이 잘되어 있어야합니다 (따라서 사용자가 이해하도록 돕기 위해 문서화되어 있습니다).

더 자세히 설명하기 위해, 그 반대로, 당신은 당신이 접근 할 수없는 자물쇠를 잠그면 당신이 '얻는'(또는 '잃어버린')를 알아야합니다 (아무도 자물쇠를 훔칠 수 없으며, 당신은 완전히 제어 할 수 있습니다. ..).

나에게 문제는 메소드 정의 서명에서 동기화 된 키워드가 프로그래머에게 너무 쉽게 만든다는 것입니다. 생각하지 않습니다 다중 스레드 프로그램에서 문제를 해결하고 싶지 않다면 생각해야 할 중요한 사항에 대해 어떻게 고정 해야하는지에 대해.

'일반적으로'당신은 수업의 사용자가 이러한 작업을 수행하거나 '일반적으로'원하는 '일반적으로'를 원하지 않는다고 주장 할 수는 없습니다. 코딩하는 기능에 달려 있습니다. 모든 사용 사례를 예측할 수 없으므로 엄지 규칙을 만들 수는 없습니다.

예를 들어 내부 잠금 장치를 사용하는 인쇄 라이터를 고려하지만 사람들은 출력이 인터 리브를 원하지 않으면 여러 스레드에서 사용하기 위해 고군분투합니다.

클래스 밖에서 잠금에 액세스 할 수 있거나 그렇지 않으면 클래스의 기능을 기반으로 프로그래머로서의 결정입니다. API의 일부입니다. 예를 들어 코드를 사용하여 코드의 변화를 위험에 빠뜨리지 않고 동기화 된 (this)에서 동기화 된 (ProvateObjet)로 이동할 수 없습니다.

참고 1 : 명시 적 잠금 객체를 사용하여 노출시켜 동기화 된 (this) '달성'을 달성 할 수 있다는 것을 알고 있지만 동작이 잘 문서화되어 있고 실제로 '이'에 잠금이 무엇인지 알고 있다면 불필요하다고 생각합니다.

참고 2 : 일부 코드가 실수로 잠금 장치를 훔치면 버그를 훔쳐서 해결해야한다는 주장에 동의하지 않습니다. 이것은 어떤 식 으로든 공개적이지 않더라도 내 모든 방법을 공개 할 수 있다고 말하는 것과 같은 주장입니다. 누군가가 개인적으로 개인적으로 부르는 사람을 '우연히'하는 경우 버그입니다. 왜이 사고를 처음으로 가능하게 하는가 !!! 자물쇠를 훔치는 능력이 있으면 수업에 문제가되지 않습니다. 저것과 같이 쉬운.

포인트 1 (잠금 장치를 사용하는 사람)과 두 개 (동일한 잠금을 사용하는 모든 방법)는 상당히 큰 응용 프로그램에서 발생할 수 있다고 생각합니다. 특히 개발자 사이에 좋은 의사 소통이 없을 때.

그것은 돌로 캐스팅되지 않고 대부분 모범 사례와 오류 방지 문제입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top