Programing

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

lottogame 2020. 2. 28. 18:34
반응형

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


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

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

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

나는 일을 this할 때 잠금을 피하는 것이 선호되는 실제 예제 (foobar 내용 없음)를 보는 데 관심 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)블록 주위에 물건을 할 수 있다고 주장 할 수 있습니다 . 일반적인 이유는 동기화 검사를 전혀하지 않아도되기 때문에 모든 종류의 동시성 문제, 특히 이중 검사 잠금 문제가 발생 하기 때문에 상대적으로 간단한 검사를 수행하는 것이 얼마나 어려운지를 보여줍니다. 스레드 세이프.

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

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

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


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 각각) 그 결과 더 동시성을 허용하는 반면에, 그 첫 번째 예보다 훨씬 복잡했다 ...


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

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

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

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


eljenso의 처음 3 개의 댓글 수정 :

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

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

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

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

동기화 됨 (this.getServletConfig (). getServletContext ()) {
   동안 (참) {}
}

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

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

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

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


이에 대한 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 커뮤니티는 시간이 지남에 따라 약간 덜 안전하지만 좀 더 간결한 버전으로 끌어 들인 것 같습니다.

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


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

전체 작업 예를 보려면 여기

작은 소개.

스레드 및 공유 가능한 엔티티
여러 스레드가 동일한 엔티티에 액세스 할 수 있습니다 (예 : 단일 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;
  }

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

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


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

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


  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 패키지도 참조하십시오 .


결정한 경우 :

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

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

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

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


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

요약해서 말하자면

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

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

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

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


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

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

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

이 예제는 단지 구현 방법을 보여주기위한 것이 아니며 반드시 구현해야하는 것은 아닙니다. 이를 수행하는 가장 좋은 방법은 IMMUTABLE 로 만드는 것 입니다.

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

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


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

독자의 관점에서 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(...)스레드 중 어느 것도 다른 객체의 잠금을 획득 할 수 없기 때문에 교착 상태가 발생합니다.

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


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

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

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

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

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

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


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

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

컴파일

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

public int foo() {
    synchronized($lock) {
        return 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...
}

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

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


그것은 당신이하고 싶은 일에 달려 있지만 나는 그것을 사용하지 않을 것입니다. 또한 달성하려는 스레드 저장을 처음에 동기화하여 수행 할 수 없었는지 확인하십시오. 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'에 대한 잠금은 수행중인 작업을 알고 있지만 'this'에 대한 잠금 뒤에있는 경우 나쁘지 않습니다 (불행히도 메소드 정의의 동기화 된 키워드가 허용하는 것).

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

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

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

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

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

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

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

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


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

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

참고 URL : https://stackoverflow.com/questions/442564/avoid-synchronizedthis-in-java



반응형