Java 동기화에 대해 SO에 대한 질문이 나타날 때마다 일부 사람들은 synchronized(this)
피해야 할 점을 지적하기를 간절히 원합니다. 대신, 그들은 개인 참조에 대한 잠금이 바람직하다고 주장합니다.
주어진 이유 중 일부는 다음과 같습니다.
- 일부 악의적 인 코드 가 잠금을 훔칠 수 있습니다 (매우 인기있는이 코드 에는 “실수로”변형 된 코드가 있음)
- 동일한 클래스 내의 모든 동기화 된 메소드는 정확히 동일한 잠금을 사용하여 처리량을 줄입니다.
- 너는 (불필요하게) 너무 많은 정보를 노출하고있다
저를 포함한 다른 사람들 synchronized(this)
은 Java 라이브러리에서도 많이 사용되는 관용구가 안전하고 잘 이해되고 있다고 주장합니다 . 버그가 있고 멀티 스레드 프로그램에서 무슨 일이 일어나고 있는지에 대한 단서가 없기 때문에 피해서는 안됩니다. 즉, 해당되는 경우 사용하십시오.
나는 일을 this
할 때 잠금을 피하는 것이 선호되는 실제 예제 (foobar 내용 없음)를 보는 데 관심 synchronized(this)
이 있습니다.
따라서 항상 synchronized(this)
개인 참조의 잠금으로 피하고 교체해야합니까?
추가 정보 (답변이 주어지면 업데이트 됨) :
- 우리는 인스턴스 동기화에 대해 이야기하고 있습니다.
- 암시 적 (
synchronized
메소드) 및 명시 적 형식이synchronized(this)
모두 고려됩니다. - 주제에 대한 Bloch 또는 기타 권한을 인용하는 경우, 원하지 않는 부분을 제외하지 마십시오 (예 : 효과적인 Java, 스레드 안전성의 항목 : 일반적으로 인스턴스 자체의 잠금이지만 예외가 있습니다).
synchronized(this)
제공하는 것 이외의 잠금 장치에서 세분성이 필요한 경우synchronized(this)
해당 사항이 없으므로 문제가 아닙니다.
답변
각 포인트를 개별적으로 다룰 것입니다.
-
일부 악의적 인 코드가 잠금을 훔칠 수 있습니다 (매우 인기있는이 코드에는 “우연히”변형이 있습니다)
실수로 더 걱정 됩니다.
this
중요한 것은이 사용이 클래스의 노출 된 인터페이스의 일부이며 문서화되어야한다는 것입니다. 때로는 다른 코드가 잠금을 사용하는 능력이 필요합니다. 이것은Collections.synchronizedMap
(javadoc 참조) 와 같은 것 입니다. -
동일한 클래스 내의 모든 동기화 된 메소드는 정확히 동일한 잠금을 사용하여 처리량을 줄입니다.
이것은 지나치게 단순한 사고입니다. 제거해도
synchronized(this)
문제가 해결되지 않습니다. 처리량에 대한 적절한 동기화는 더 많은 생각이 필요합니다. -
정보가 너무 많이 노출되는 경우
이것은 # 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 ()) { while (true) {} }
동일한 서블릿 컨텍스트를 공유한다고 가정하면 (두 서블릿이 동일한 가상 호스트에있는 한 스펙에서 허용됨) 호출 getAttribute
이 영원히 잠 깁니다. 해커가 내 서블릿에서 DoS를 달성했습니다.
getAttribute
타사 코드가이 잠금을 획득 할 수 없으므로 개인 잠금에서 동기화 된 경우이 공격이 불가능합니다 .
필자는이 예제가 서블릿 컨테이너의 작동 방식에 대해 지나치게 단순화 된 견해를 가지고 있음을 인정하지만 IMHO가 그 점을 증명합니다.
따라서 보안 고려 사항에 따라 디자인을 선택합니다. 인스턴스에 액세스 할 수있는 코드를 완전히 제어 할 수 있습니까? 스레드가 인스턴스에 잠금을 무기한으로 보유한 결과는 무엇입니까?
답변
상황에 따라 다릅니다.
공유 엔터티가 하나만 있거나 둘 이상인 경우
작은 소개.
스레드 및 공유 가능한 엔티티
여러 스레드가 동일한 엔티티에 액세스 할 수 있습니다 (예 : 단일 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;
}
답변
이에 대한 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 커뮤니티는 시간이 지남에 따라 약간 덜 안전하지만 좀 더 간결한 버전으로 몰려 들었습니다.
그것은 자바에 대한 발굴이 아니며 두 언어에 대한 나의 경험을 반영한 것입니다.
답변
이 java.util.concurrent
패키지는 스레드 안전 코드의 복잡성을 크게 줄였습니다. 나는 일화적인 증거 만 가지고 있지만, 내가 본 대부분의 작업 synchronized(x)
은 Lock, Semaphore 또는 Latch를 다시 구현하지만 더 낮은 수준의 모니터를 사용하는 것으로 보입니다 .
이러한 점을 염두에두고 이러한 메커니즘 중 하나를 사용하여 동기화하는 것은 잠금을 누설하지 않고 내부 객체를 동기화하는 것과 유사합니다. 이것은 두 개 이상의 스레드로 모니터에 대한 항목을 제어 할 수 있다는 절대적인 확신이 있다는 점에서 유리합니다.