programing

Java에서 동기화되지 않도록 하시겠습니까?

prostudy 2022. 5. 6. 19:44
반응형

Java에서 동기화되지 않도록 하시겠습니까?

자바 동기화에 대한 SO에 대한 질문이 나올 때마다, 일부 사람들은 이 문제를 지적하기를 매우 열망하고 있다.synchronized(this)피해야 한다.대신에 그들은 사적인 참고자료에 대한 자물쇠가 선호되어야 한다고 주장한다.

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

나를 포함한 다른 사람들은 다음과 같이 주장한다.synchronized(this)자바 도서관에서 많이 사용되는 숙어로서 안전하고 잘 이해된다.버그가 있고 멀티스레드 프로그램에서 무슨 일이 일어나고 있는지 전혀 모르기 때문에 피해서는 안 된다.즉, 적용 가능한 경우 사용하십시오.

나는 자물쇠가 잠기는 것을 피하는 몇 가지 실제 사례들을 보고 싶다.this는 것이 바람직하다.synchronized(this)그 일 또한 할 수 있을 것이다.

따라서 항상 피해야 하며 개인 참조에 대한 잠금 장치로 교체해야 하는가?


일부 추가 정보(답변 제공으로 업데이트됨):

  • 인스턴스 동기화에 대해 이야기하고 있다.
  • 암묵적인 (둘 다)synchronized방법) 및 의 명시적 형식synchronized(this)라고
  • 이 주제에 대해 Bloch나 다른 당국의 말을 인용할 경우 마음에 들지 않는 부분(예: 효과적인 Java, Thread Safety의 항목:일반적으로 인스턴스 자체에 대한 잠금이지만 예외가 있다.)
  • 다음 이외의 잠금 장치가 필요한 경우synchronized(this)제공, 제공, 제공synchronized(this)해당되지 않으므로 문제가 되지 않음

각 포인트는 따로따로 커버하겠다.

  1. 어떤 악의적인 코드들은 당신의 자물쇠를 훔칠 수 있다. (이것은 매우 인기있고, 또한 "우연한" 변종이 있다)

    는 우발적으로 더 걱정된다.중요한 것은 이 용도의 사용되었다는 것이다.this클래스의 노출된 인터페이스의 일부로서 문서화되어야 한다.때때로 당신의 잠금을 사용하는 다른 코드의 능력이 필요하다.이것은 같은 것에 해당된다.Collections.synchronizedMap(자바독 참조).

  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)정말 아무것도 주지 않아

동기화된(이)를 사용하는 동안 클래스 인스턴스를 잠금 자체로 사용하는 경우, 나사산 1에 의해 잠금이 획득되는 동안 나사산 2는 기다려야 한다.

다음 코드를 가정해 보십시오.

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


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

방법 1은 변수 a를 수정하고 방법 2는 변수 b를 수정하며, 동일한 변수를 두 개의 스레드로 동시에 수정하는 것은 피해야 하며, 이는 다음과 같다.그러나 스레드1asrad2를 수정하는 동안 b를 수정하는 것은 경주 조건 없이 수행될 수 있다.

불행하게도, 위의 코드는 자물쇠에 대해 동일한 참조를 사용하고 있기 때문에 이것을 허용하지 않을 것이다.이것은 비록 그들이 경기 상황이 아니더라도 스레드는 기다려야 한다는 것을 의미하며 코드는 프로그램의 동시성을 희생시킨다.

해결책은 다음과 같은 두 가지 변수에 서로 다른 두 개의 잠금 장치를 사용하는 것이다.

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개의 잠금장치(변수 ab잠금장치)를 사용하며, 그 결과 더 나은 동시성을 허용하고, 반면에 첫 번째 예제보다 더 복잡해졌다.

독단적인 규칙을 맹목적으로 고수하지 않는 것에 대해서는 동의하지만, "잠금 도둑질" 시나리오는 당신에게 그렇게 별난 것처럼 보이나?실이 정말로 당신의 물체에 "영원히" 자물쇠를 획득할 수 있을 것이다.synchronized(theObject) {...}(), 동기화된 인스턴스 메서드에서 대기 중인 다른 스레드 차단.

악성 코드를 믿지 않는 경우, 이 코드가 타사(예를 들어, 어떤 종류의 응용 프로그램 서버를 개발하는 경우)에서 발생할 수 있다고 가정하십시오.

"우발적인" 버전은 덜 가능성이 있어 보이지만, 그들이 말하듯이, "바보 방지 장치를 만들면 누군가가 더 나은 바보를 발명할 것이다."

그래서 나는 계급에 의존하는 사고방식에 동의한다.


다음 엘젠소의 처음 3개 주석을 편집하십시오.

자물쇠 도둑질 문제는 경험해 본 적이 없지만, 여기 가상의 시나리오가 있다.

있는 의 서의 서의 라고 하자. 그리고 우리가 고려하고 있는 대상은ServletContext실행그것들getAttribute컨텍스트 속성이 공유 데이터이므로 메서드는 스레드-세이프여야 함. 따라서 다음과 같이 선언함synchronized컨테이너 구현에 기반한 공개 호스팅 서비스를 제공한다고 가정해 봅시다.

나는 당신의 고객이고 당신의 사이트에 나의 "좋은" 서블릿을 배치한다.내 코드에 다음 호출이 포함된 경우getAttribute.

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

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

동일한 서블릿 컨텍스트를 공유한다고 가정할 때(두 서블릿이 동일한 가상 호스트에 있는 한 규격에서 허용됨)getAttribute영원히어어어 가 내 해커가 내 서블릿에서 DoS를 획득했다.

이 공격은 다음과 같은 경우에 가능하지 않다.getAttribute타사 코드가 이 잠금을 획득할 수 없기 때문에 개인 잠금에서 동기화됨.

서블릿 컨테이너가 어떻게 작동하는지 그 예가 조작되고 지나치게 단순화된 관점은 인정하지만 IMHO가 그 점을 증명한다.

보안 고려사항에 따라 설계를 선택할 수 있으며, 인스턴스에 대한 액세스 권한이 있는 코드를 완전히 제어할 수 있는가?실이 인스턴스의 자물쇠를 무한정 붙잡고 있으면 어떤 결과가 나올까?

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

전체 작업 예제 보기 여기에

작은 소개.

스레드 및 공유 가능한 엔티티
여러 개의 스레드가 여러 개의 연결과 같이 동일한 엔티티에 액세스할 수 있다.단일 메시지Queue를 공유하는 스레드.스레드가 동시에 실행되기 때문에 다른 스레드가 데이터를 무시할 가능성이 있으며, 이는 잘못된 상황일 수 있다.
따라서 우리는 공유 가능한 실체가 한 번에 하나의 스레드로만 접근되도록 하는 어떤 방법이 필요하다.

동기화된 블록
동기화된 블록은 공유 가능한 엔터티의 동시 액세스를 보장하는 방법이다.
첫째, 작은 비유.
2인용 P1, P2(스레드) 세면대(공유 가능한 실체)가 세면대 안에 있고 문(잠금)이 있다고 가정하자.
이제 우리는 한 번에 한 사람이 세면대를 사용하기를 원한다.
때을 잠그는 이 접근법이다. 때 로 문을 이다. P2는 p1이 그의 작업을 완료할 때까지 기다린다.
P1이 도어를 잠금 해제한다.
그러면 오직 p1만이 세면대를 사용할 수 있다.

구문.

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

"이것"은 클래스와 관련된 본질적인 잠금을 제공했다(Java 개발자는 각 객체가 모니터로 작동할 수 있는 방식으로 객체 클래스를 설계했다).위의 접근방식은 공유 실체와 다중 스레드(1:N)가 하나만 있을 때 잘 작동한다.
여기에 이미지 설명을 입력하십시오.N개의 공유 가능한 엔티티-M 스레드
이제 세면대 안에 세면대가 두 개 있고 문이 한 개밖에 없는 상황을 생각해 보라.만약 우리가 이전 접근법을 사용하고 있다면, p1만이 한 번에 하나의 세면대를 사용할 수 있고 p2는 밖에서 기다릴 것이다.아무도 B2(세척기)를 사용하지 않기 때문에 자원의 낭비다.
더 현명한 접근법은 세면장 내부에 작은 방을 만들어 세면대당 하나의 문을 제공하는 것이다.이렇게 해서 P1은 B1에 접속할 수 있고 P2는 B2에 접속할 수 있으며, 그 반대의 경우도 접속할 수 있다.

washbasin1;  
washbasin2;

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

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

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

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

이에 대해 C#와 자바 진영에서는 서로 다른 의견의 일치가 있는 것 같다.내가 본 자바 코드의 대부분은 다음을 사용한다.

// 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.util.concurrent패키지는 내 스레드 안전 코드의 복잡성을 크게 줄였다.나는 계속해야 할 일화적인 증거밖에 없지만, 내가 본 대부분의 작품은synchronized(x)잠금, 세마포어 또는 래치를 다시 구현하지만 하위 수준의 모니터를 사용하는 것으로 나타난다.

이를 염두에 두고, 이러한 메커니즘 중 하나를 사용하여 동기화하는 것은 자물쇠가 새는 것이 아니라 내부 물체에 동기화하는 것과 유사하다.이것은 두 개 이상의 스레드로 모니터의 입력을 제어할 수 있다는 절대적인 확신이 있다는 점에서 유익하다.

  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()
     }
   }
 }

동기화된 잠금 기능(이 기능)의 장점

  1. 동기화된 방법이나 문장의 사용은 모든 잠금 획득과 해제가 블록 구조화된 방식으로 일어나도록 강요한다.

  2. 잠금 구현은 동기화된 방법 및 문장의 사용에 대한 추가적인 기능을 제공한다.

    1. 잠금 장치 획득을 위한 비차단 시도(Non-blocking 시도)tryLock())
    2. 중단될 수 있는 잠금을 획득하려는 시도().lockInterruptibly())
    3. 시간 초과가 가능한 잠금 획득 시도(tryLock(long, TimeUnit)).
  3. 잠금 클래스는 또한 다음과 같은 암묵적 모니터 잠금의 동작과 의미론도 제공할 수 있다.

    1. 보증주문
    2. 부정사용.
    3. 교착 탐지

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

동기화 대 잠금

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

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

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

동시 수집을 통해 대용량 데이터 수집을 쉽게 관리할 수 있으며 동기화의 필요성을 크게 줄일 수 있다.

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

스레드LocalRandom(JDK 7)은 여러 스레드로부터 가성비를 효율적으로 생성해 준다.

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

그렇게 결정한 경우:

  • 당신이 해야 할 일은 현재 물체를 잠그는 것이다.
  • 전체 방법보다 작은 세분성으로 잠그고자 하는 경우

그렇다면 나는 동기화에 대한 금기가 보이지 않는다.

어떤 사람들은 실제로 어떤 물체가 동기화되고 있는지를 "독자에게 더 이상"이라고 생각하기 때문에 일부러 방법의 전체 내용 안에서 싱크로나이즈드(이것)를 사용한다.사람들이 정보에 입각한 선택을 하는 한(예를 들어, 그렇게 함으로써 그들이 실제로 추가 바이트 코드를 방법에 삽입하고 이것이 잠재적인 최적화에 연쇄적인 영향을 미칠 수 있다는 것을 이해한다) 나는 이것에 특별히 문제가 있다고 보지 않는다.당신은 항상 프로그램의 동시 행동을 문서화해야 한다. 그래서 나는 "동기화된" 주장이 그렇게 설득력 있는 것으로 보지 않는다.

어떤 물체의 자물쇠를 사용해야 하는가에 대한 질문에 대해서는, 만약 이것이 여러분이 무엇을 하고 있고, 여러분의 클래스가 일반적으로 어떻게 사용될지에 대한 논리로 예상된다면, 현재의 물체에 동기화하는 것은 아무런 문제가 없다고 생각한다.예를 들어, 컬렉션을 사용할 경우 논리적으로 잠글 것으로 예상하는 오브젝트는 일반적으로 컬렉션 그 자체다.

나는 브라이언 괴츠의 Java Concurrency In Practice라는 책에서 이 각각의 기술들이 왜 당신의 허리띠 아래 중요한 기술인지에 대한 좋은 설명이 있다고 생각한다.그는 한 가지 요점을 분명히 했다 - 당신은 당신의 물체의 상태를 보호하기 위해 동일한 자물쇠 "Everywhere"를 사용해야 한다.동기화된 방법과 물체에 대한 동기화는 종종 병행된다.예: 벡터는 모든 방법을 동기화한다.만약 당신이 벡터 객체에 대한 핸들을 가지고 있고 "없을 경우 put"을 할 것이라면, 단지 그것의 개별적인 방법을 동기화하는 벡터가 당신을 국가의 부패로부터 보호하지는 못할 것이다.동기화된 벡터 핸들을 사용하여 동기화해야 한다.이는 벡터에 대한 핸들을 가지고 있는 모든 나사산에 의해 동일한 잠금이 획득되고 벡터의 전반적인 상태를 보호하게 될 것이다.이것을 클라이언트측 잠금이라고 한다.사실 벡터는 모든 방법을 동기화하며, 따라서 객체 벡터핸들에 동기화하는 것이 벡터 객체 상태의 적절한 동기화를 초래한다는 것을 우리는 알고 있다.당신이 실의 안전한 수집품을 사용한다고 해서 실이 안전하다고 믿는 것은 어리석은 짓이다.이것이 바로 ConcurrentHashMap이 그러한 운영을 원자성으로 만들기 위해 putIfAbsent 방법을 명시적으로 도입한 정확한 이유다.

요약하면

  1. 메서드 수준에서 동기화하면 클라이언트 측 잠금이 허용된다.
  2. 개인 잠금 객체가 있는 경우 - 클라이언트 측 잠금이 불가능하게 된다.만약 당신의 클래스에 "put if out" 유형의 기능이 없다는 것을 안다면 이것은 괜찮다.
  3. 만약 당신이 라이브러리를 설계하고 있다면, 이것에 동기화하거나 방법을 동기화하는 것이 종종 더 현명하다.왜냐하면 당신은 수업이 어떻게 사용될지 결정할 수 있는 위치에 있는 경우가 거의 없기 때문이다.
  4. 만약 벡터가 개인 자물쇠 물체를 사용했다면, "없을 경우"를 바로 잡는 것은 불가능했을 것이다.클라이언트 코드는 절대 개인 자물쇠에 대한 핸들을 잡지 못할 것이며, 따라서 상태를 보호하기 위해 정확히 동일한 자물쇠를 사용하는 기본 규칙을 위반할 것이다.
  5. 다른 사람들이 지적한 바와 같이, 누군가 자물쇠를 채우고 절대 풀지 않을 수도 있다.다른 모든 실들은 자물쇠가 풀리기를 계속 기다릴 것이다.
  6. 그러니 네가 무엇을 하고 있는지 알고 맞는 것을 채택해라.
  7. 어떤 사람은 개인용 잠금 물체를 갖는 것이 더 세분화(예: 두 개의 조작이 관련이 없는 경우)를 더 잘 만들 수 있다고 주장했는데, 그것들은 서로 다른 잠금 장치에 의해 보호되어 처리량이 더 향상될 수 있다.그러나 이것은 코드 냄새가 아니라 디자인 냄새라고 생각한다 - 만약 두 번의 조작이 완전히 관련이 없다면 왜 그들은 동일한 클래스에 속할 것인가?왜 학급 클럽은 전혀 관련 없는 기능을 해야 하는가?유틸리티 수업일 수도 있어?흠 - 같은 인스턴스를 통해 문자열 조작과 캘린더 날짜 형식을 제공하는 활용도? ... 적어도 나는 이해가 안 돼!!

아니, 항상 그래선 안 돼.그러나, 나는 특정한 대상에 대해 자신들에 관해서만 안전하기만 하면 되는 여러 가지 우려가 있을 때 그것을 피하는 경향이 있다.예를 들어, "label" 및 "parent" 필드가 있는 돌연변이 데이터 객체가 있을 수 있다. 이러한 개체는 스레드 안전해야 하지만 하나를 변경하면 다른 필드가 쓰기/읽는 것을 차단할 필요는 없다. (실제로 필드는 휘발성이 있다고 선언하고/또는 Java.util.concurrent의 AtomFoo 래퍼를 사용하여 이를 피할 수 있다.)

일반적으로 동기화는 좀 서툴다. 왜냐하면 그것은 어떻게 실이 서로 작용하도록 허용될 수 있는지를 정확히 생각하기보다는 큰 자물쇠를 채우기 때문이다.사용.synchronized(this)"내가 자물쇠를 잡고 있는 동안 아무도 이 수업에서 어떤 도 바꿀 수 없다"는 말이 있기 때문에 더 서툴고 반사회적이다.얼마나 자주 그렇게 해야 하니?

나는 훨씬 더 세분화된 자물쇠를 갖고 싶다; 모든 것이 변하는 것을 막고 싶어도, 같은 것을 이루기 위해 모든 자물쇠를 획득하면 된다. 그리고 그것은 더 명백하다.사용할 때synchronized(this)당신이 왜 동기화하고 있는지, 또는 부작용이 무엇인지 정확히 알 수 없다.사용한다면synchronized(labelMonitor), 또는 더 나은labelLock.getWriteLock().lock()당신이 무엇을 하고 있는지 그리고 당신의 비판적인 부분의 영향은 무엇으로 제한되는지는 명확하다.

단답:차이점을 이해하고 코드에 따라 선택을 해야 한다.

긴 대답:일반적으로 나는 경합을 줄이기 위해 동기화를 피하려고 하지만 개인 잠금 장치는 당신이 알아야 할 복잡성을 더한다.따라서 올바른 작업에 올바른 동기화를 사용하십시오.다중 스레드 프로그래밍에 익숙하지 않다면 인스턴스 잠금을 고수하고 이 주제에 대해 읽어보겠다. (즉, 동기화(이것)를 사용한다고 해서 클래스가 자동으로 완전히 안전하게 되는 것은 아니다.)이것은 쉽지 않은 주제지만 일단 익숙해지면 동기화(이것) 사용 여부에 대한 답이 자연스럽게 나온다.

잠금 장치는 가시성 또는 경주로 이어질 수 있는 동시 수정으로부터 일부 데이터를 보호하는 데 사용된다.

원시 유형 연산을 원자형으로 만들면 다음과 같은 옵션이 있다.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따로따로 그러면 안전하지 않았을 것이다.

이 예는 단지 그것이 구현되어야 하는 방식을 증명하기 위한 것일 뿐 반드시 그렇게 구현되어야 하는 것은 아니다.그것을 하는 가장 좋은 방법은 그것을 IMMANCLUBLE로 만드는 것일 것이다.

이제 에 반대하여Point를 들면, ,의 「예」의 「가」가」의 예가 있다.TwoCounters이미 @Andreas에 의해 제공된 상태로서 두 개의 다른 잠금장치에 의해 보호되고 있는 상태는 서로 관련이 없다.

관련 없는 상태를 보호하기 위해 서로 다른 잠금 장치를 사용하는 프로세스를 잠금 스트라이핑 또는 잠금 분할이라고 한다.

이것에 동기화하지 않는 이유는 때때로 두 개 이상의 자물쇠가 필요하기 때문이다(두 번째 자물쇠는 종종 추가적인 사고 후에 제거되지만, 당신은 여전히 중간 상태에서는 그것이 필요하다).이것을 잠그면, 당신은 항상 두 개의 자물쇠 중 어느 것이 이것인지 기억해야 한다. 만약 당신이 개인 오브젝트를 잠그면, 변수 이름은 당신에게 그것을 알려준다.

독자의 관점에서, 만약 당신이 이것을 고정하는 것을 본다면, 당신은 항상 다음의 두 가지 질문에 답해야 한다.

  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(...)두 나사산이 모두 다른 물체의 잠금을 획득할 수 없기 때문에 우리는 교착 상태에 빠졌다.

이 예에서 우리는 짧은 작동과 긴 작동에 대한 잠금 장치 두 개를 사용하여 교착 상태를 제거할 수 있다.

여기서 말했듯이 동기화된 블록은 사용자 정의 변수를 잠금 개체로 사용할 수 있으며 동기화된 함수가 "이것"만 사용할 경우그리고 물론 당신은 동기화되어야 하는 당신의 기능의 영역 등을 가지고 조작할 수 있다.

그러나 모든 사람들은 "이것"을 자물쇠 물체로 사용하여 전체 기능을 포괄하는 동기화된 기능과 블록의 차이는 없다고 말한다.그것은 사실이 아니다. 두 상황에서 생성될 바이트 코드의 차이는 있다.동기화된 블록 사용의 경우 "이것"을 참조하는 로컬 변수를 할당해야 한다.그리고 그 결과 우리는 조금 더 큰 크기의 함수를 갖게 될 것이다(여러분이 함수의 수만을 가지고 있는 경우에는 관련이 없다).

여기에서 확인할 수 있는 차이에 대한 자세한 설명: http://www.artima.com/insidejvm/ed2/threadsynchP.html

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

동기화된 키워드는 한 영역에서 매우 제한적이다. 동기화된 블록을 종료할 때, 해당 잠금을 대기 중인 모든 스레드는 차단 해제되어야 하지만, 그 중 하나만 잠금을 가져간다. 다른 모든 스레드는 잠금이 해제되어 차단된 상태로 되돌아간다.이는 단순히 많은 처리 주기가 낭비되는 것이 아니다. 스레드를 차단 해제하기 위한 컨텍스트 전환은 또한 디스크에서 메모리를 호출하는 것을 수반한다. 그것은 매우, 매우, 매우, 비싸다.

이 영역에 대한 자세한 내용은 다음 기사를 참조하십시오. http://java.dzone.com/articles/synchronized-considered

이는 다른 답변에 대한 보충에 불과하지만, 개인용 객체를 잠그는 것에 대한 당신의 주된 반대 의견이 비즈니스 논리와 관련이 없는 분야로 당신의 클래스를 어지럽힌다는 것이라면, Project Lombok은 컴파일 시간에 보일러를 생성해야 한다.

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

로 편찬하다.

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

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

동기화된 사용에 대한 좋은 예.

// 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에는 또한 도움이 될 수 있는 몇 가지 멋진 잠금 장치가 있다 :)

나는 단지 의존성이 없는 코드의 원자 부분에 있는 고유한 개인 참조에 대한 가능한 해결책을 언급하고 싶다.자물쇠가 있는 정적 해시맵과 스택 정보(전체 클래스 이름 및 줄 번호)를 사용하여 필요한 참조를 자동으로 생성하는 간단한 정적 방법()을 사용할 수 있다.그런 다음 새 잠금 개체를 쓰지 않고 동기화 문에서 이 방법을 사용할 수 있다.

// 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)잠금 메커니즘으로:이것은 전체 클래스 인스턴스를 잠그고 교착 상태를 유발할 수 있다.이러한 경우, 특정 방법이나 변수만 잠그도록 코드를 리팩터링하여 전체 클래스가 잠기지 않도록 하십시오.Synchronisedmethod level 내에서 사용될 수 있다.
사용하는 대신synchronized(this)아래 코드는 당신이 어떻게 단지 방법을 잠글 수 있는지를 보여준다.

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

이 문제가 이미 해결될 수 있었는데도 2019년 나의 2센트.

여러분이 무엇을 하고 있는지 알고 있다면 '이것'을 잠그는 것은 나쁘지 않지만, 장면 뒤에서 '이것'을 잠그는 것은 (안타깝게도 방법 정의에서 동기화된 키워드가 허용하는 것)이다.

클래스의 사용자가 잠금을 '스티어링(steal)'할 수 있도록 하려면(즉, 다른 스레드가 잠금을 처리하는 것을 방지) 다른 동기화 방법이 실행되는 동안 모든 동기화 방법을 기다리십시오.의도적이고 잘 생각해 보아야 한다(따라서 사용자의 이해를 돕기 위해 문서화).

더 자세히 설명하자면, 그 반대로 접근할 수 없는 자물쇠에 잠근다면, 당신은 당신이 무엇을 '감고'(또는 '닫고' 있는 지)를 알아야 한다. (아무도 당신의 자물쇠를 '강조'할 수 없기 때문에, 당신은 완전히 통제할 수 있다...)

문제는 메서드 정의 서명의 키워드를 동기화하면 프로그래머들이 멀티스레드 프로그램에서 문제에 부딪히고 싶지 않다면 무엇을 잠그어야 할지 생각하지 않는 것이 너무 쉽다는 것이다.

사람들은 '일반적으로' 당신 반의 사용자들이 이런 것들을 할 수 있기를 바라지 않거나, 당신이 원하는 '일반적으로'...어떤 기능을 코딩하느냐에 따라 달라진다.모든 사용 사례를 예측할 수 없기 때문에 엄지의 법칙을 만들 수는 없다.

예를 들어 내부 잠금을 사용하지만 출력이 인터리빙되지 않도록 하려면 여러 스레드에서 사용하려고 애쓰는 인쇄기를 생각해 보십시오.

당신의 자물쇠가 교실 밖에서 접근 가능한지 아닌지는 클래스가 가지고 있는 기능성에 기초하여 프로그래머로서의 당신의 결정이다.그것은 api의 일부분이다.예를 들어, 동기화된(이것)에서 동기화된(ProvateObjet)으로 이동할 수 없으며, 이를 사용하여 코드 변경을 중단하지 않을 수 없다.

주 1: 명시적 잠금 객체를 사용하여 이를 노출함으로써 동기화된 (이) '어키브'를 달성할 수 있다는 것을 알지만, 당신의 행동이 잘 문서화되어 있고 '이것'에 대한 잠금이 무엇을 의미하는지 알고 있다면 불필요하다고 생각한다.

주 2: 나는 어떤 코드가 실수로 당신의 자물쇠를 훔쳐간다면 버그가 되고 당신은 그것을 해결해야 한다는 주장에 동의하지 않는다.이것은 어떻게 보면 나의 모든 방법을 공개해서는 안 되더라도 공개할 수 있다고 말하는 것과 같은 주장이다.만약 누군가가 '우발적으로' 내가 사적인 방법을 사용하려고 의도한 것을 '버그'라고 부르고 있다면 그것은 버그다.애초에 왜 이 사고를 가능하게 했는가!!!만약 당신의 자물쇠를 훔치는 능력이 당신의 반의 문제라면 그것을 허락하지 마라.그것처럼 간단해.

동기화에는 다음 3가지 부분이 포함된다.원자성, 가시성 및 순서

동기화된 블록은 매우 거친 수준의 동기화다.그것은 당신이 기대했던 것과 똑같이 가시성과 순서를 강요한다.그러나 원자성을 위해서는 그다지 보호해 주지 않는다.원자성은 지역적 지식보다는 프로그램에 대한 세계적인 지식을 필요로 한다.(그리고 그것은 멀티스레딩 프로그래밍을 매우 어렵게 만든다)

수업이 있다고 하자.Account분별력이 있는deposit그리고withdraw 이 둘은 다음과 같은 개인 잠금을 기반으로 동기화된다.

class Account {
    private Object lock = new Object();

    void withdraw(int amount) {
        synchronized(lock) {
            // ...
        }
    }

    void deposit(int amount) {
        synchronized(lock) {
            // ...
        }
    }
}

다음과 같이 이전을 처리하는 상위 클래스를 구현해야 한다는 점을 고려하십시오.

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        if (fromAcc.getBalance() > amount) {
            fromAcc.setBalance(fromAcc.getBalance() - amount);
            toAcc.setBalance(toAcc.getBalance + amount);
        }
    }
}

지금 계좌가 2개라고 가정하면

Account john;
Account marry;

만약Account.deposit()그리고Account.withdraw()내부 잠금으로만 잠겨 있음.다음과 같은 두 개의 스레드가 작동 중일 때 문제가 발생할 수 있음:

// Some thread
void threadA() {
    john.withdraw(500);
}

// Another thread
void threadB() {
    accountManager.transfer(john, marry, 100);
}

왜냐하면 둘 다 가능하기 때문이다.threadA그리고threadB동아리에 있다. 는 조건부 그리고 실 B는 조건부 점검을 끝내고 실 A는 물러나고 실 B는 다시 물러난다.이것은 우리가 존의 계좌에 돈이 충분하지 않더라도 100달러를 인출할 수 있다는 것을 의미한다.이것은 원자성을 깨뜨릴 것이다.

당신은 그것을 제안할 수 있다: 왜 추가하지 않는가.withdraw()그리고deposit()AccountManager이는 멀티 스레드 해. 하지만 이 제안서에 따르면, 우리는 다중 스레드 금고를 만들 필요가 있다.Map서로 다른 계정에서 자물쇠로 연결되는 지도.실행 후 잠금을 삭제해야 한다(그렇지 않으면 메모리가 누출된다).그리고 다른 사람이 접근하지 못하도록 해야 해Account.withdraw()은 많은 이것은 많은 미묘한 벌레들을 소개할 것이다.

정확하고 가장 관용적인 방법은 자물쇠를 문틈에 노출시키는 것이다.Account. 그리고 그렇게 합시다.AccountManager자물쇠를 이용하기 위해서.하지만 이 경우, 왜 그냥 물체 자체를 사용하지 않는가?

class Account {
    synchronized void withdraw(int amount) {
        // ...
    }

    synchronized void deposit(int amount) {
        // ...
    }
}

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        // Ensure locking order to prevent deadlock
        Account firstLock = fromAcc.hashCode() < toAcc.hashCode() ? fromAcc : toAcc;
        Account secondLock = fromAcc.hashCode() < toAcc.hashCode() ? toAcc : fromAcc;

        synchronized(firstLock) {
            synchronized(secondLock) {
                if (fromAcc.getBalance() > amount) {
                    fromAcc.setBalance(fromAcc.getBalance() - amount);
                    toAcc.setBalance(toAcc.getBalance + amount);
                }
            }
        }
    }
}

간단한 영어로 결론짓자면, 개인 자물쇠는 조금 더 복잡한 다중 스레드 프로그램에서는 작동하지 않는다.

(https://stackoverflow.com/a/67877650/474197)에서 다시 게시됨

나는 1번 포인트(다른 누군가가 당신의 자물쇠를 사용하는 것)와 2번 포인트(동일한 자물쇠를 불필요하게 사용하는 모든 방법)는 어떤 큰 어플리케이션에서도 일어날 수 있다고 생각한다.특히 개발자 간 소통이 원활하지 않을 때는 더욱 그렇다.

그것은 돌로 주조된 것이 아니라 대부분 좋은 관행과 오류를 방지하는 문제야.

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

반응형