Sharing Object
2 장 초반에 올바른 동시 프로그램 작성은 주로 변경 가능 공유 상태에 대한 액세스 관리에 관한 것이라고 언급했습니다. 이 장에서는 여러 스레드가 안전하게 액세스 할 수 있도록 객체를 공유 및 게시하는 기술에 대해 설명합니다. 이 둘은 스레드 안전 클래스를 구축하고 java.util.concurrent 라이브러리 클래스를 사용하여 동시 애플리케이션을 안전하게 구성하기위한 토대를 마련합니다. 우리는 동기화 된 블록과 메소드가 어떻게 동작이 원자 적으로 실행되도록 하는지를 보았지만 동기화는 원 자성 또는 "중요한 부분"에 대한 경계라는 일반적인 오해입니다. 동기화에는 또 다른 중요하고 미묘한 측면이 있습니다 : 메모리 가시성. 우리는 다른 스레드가 객체를 사용할 때 한 스레드가 객체의 상태를 수정하지 못하게 할뿐만 아니라 스레드가 객체의 상태를 수정할 때 다른 스레드가 실제로 변경 사항을 볼 수 있도록 보장하려고합니다. 그러나 동기화하지 않으면 이런 일이 발생하지 않을 수 있습니다. 명시 적 동기화를 사용하거나 라이브러리 클래스에 내장 된 동기화를 사용하여 오브젝트를 안전하게 공개 할 수 있습니다.
3.1. Visibility
잘못 될 수있는 것은 너무 직관적이기 때문에 가시성은 미묘합니다. 단일 스레드 환경에서 변수에 값을 쓰고 나중에 중간에 쓰지 않고 해당 변수를 읽으면 동일한 값을 얻을 수 있습니다. 이것은 자연스러운 것 같습니다. 처음에는 받아들이 기 어려울 수 있지만 읽기 및 쓰기가 다른 스레드에서 발생하는 경우에는 해당되지 않습니다. 일반적으로, 읽기 스레드가 다른 스레드가 적시에 또는 전혀 기록하지 않은 값을 볼 것이라는 보장은 없습니다. 스레드 간 메모리 쓰기의 가시성을 확보하려면 동기화를 사용해야합니다. Listing 3.1의 NoVisibility는 스레드가 동기화없이 데이터를 공유 할 때 무엇이 잘못 될 수 있는지를 보여줍니다. 메인 스레드와 리더 스레드의 두 스레드는 공유 변수 준비 및 수에 액세스합니다. 주 스레드는 판독기 스레드를 시작한 다음 숫자를 42로 설정하고 true로 준비합니다. 리더 스레드는 준비가 완료 될 때까지 회전 한 다음 숫자를 인쇄합니다. NoVisibility가 42를 인쇄한다는 것은 명백해 보이지만 실제로는 0을 인쇄하거나 전혀 종료되지 않을 수 있습니다! 적절한 동기화를 사용하지 않기 때문에 주 스레드가 작성한 ready 및 number 값이 리더 스레드에 표시 될 것이라는 보장은 없습니다.
Listing 3.1. Sharing Variables without Synchronization. Don't Do this.
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready) Thread.yield();
System.out.println(number); }
}
public static void main(String[] args) { new ReaderThread().start();
number = 42;
ready = true;
}
}
ready의 값이 판독기 스레드에 표시되지 않을 수 있으므로 NoVisibility가 영원히 반복 될 수 있습니다. 더 이상하게도 NoVisibility는 0으로 인쇄 할 수 있는데, 이는 번호에 쓰기 전에 리더 스레드에 표시 될 수 있기 때문입니다. 해당 스레드 내에서 재정렬을 감지 할 수없는 한, 하나의 스레드에서 작업이 프로그램에서 제공 한 순서대로 수행된다는 보장은 없습니다. [1] 동기화하지 않으면 리더 스레드는 해당 쓰기가 반대 순서로 발생하거나 전혀 발생하지 않는 것을 볼 수 있습니다.
동기화가 없으면 컴파일러, 프로세서 및 런타임은 작업이 실행되는 순서대로 약간 이상한 작업을 수행 할 수 있습니다. 불충분하게 동기화 된 멀티 스레드 프로그램에서 메모리 동작 "필수"가 발생하는 순서에 대한 추론은 거의 확실하지 않습니다. NoVisibility는 동시 프로그램이 두 개의 스레드와 두 개의 공유 변수를 얻을 수있는 것처럼 간단하지만 여전히 그 기능이나 종료 여부에 대한 잘못된 결론을 내리기가 너무 쉽습니다. 동기화되지 않은 동시 프로그램에 대한 추론은 엄청나게 어렵습니다. 이것은 모두 약간 무섭게 들릴 수 있습니다. 다행히도 이러한 복잡한 문제를 피할 수있는 쉬운 방법이 있습니다. 데이터가 스레드간에 공유 될 때마다 항상 올바른 동기화를 사용하십시오.
3.1.1. Stale Data 오래된 데이터
NoVisibility는 프로그램이 불충분하게 동기화되어 놀라운 결과를 초래할 수있는 방법 중 하나 인 오래된 데이터를 보여주었습니다. 판독기 스레드가 준비 상태를 검사하면 오래된 값이 표시 될 수 있습니다. 변수에 액세스 할 때마다 동기화가 사용되지 않으면 해당 변수의 오래된 값을 볼 수 있습니다. 더구나, 부실은 전부가 아니면 아무것도 아닙니다 : 스레드는 한 변수의 최신 값을 볼 수 있지만 처음 작성된 다른 변수의 부실 값을 볼 수 있습니다. 음식이 오래되지 않은 경우에도 음식은 여전히 덜 즐겁습니다. 그러나 오래된 데이터는 더 위험 할 수 있습니다. 웹 응용 프로그램의 오래된 적중 카운터가 그렇게 나쁘지는 않지만 [2] 오래된 값은 심각한 안전 또는 작동 장애를 일으킬 수 있습니다. NoVisibility에서 오래된 값으로 인해 잘못된 값이 인쇄되거나 프로그램이 종료되지 않을 수 있습니다. 연결된 목록 구현의 링크 포인터와 같은 오래된 객체 참조 값으로 인해 상황이 더욱 복잡해질 수 있습니다. 오래된 데이터는 예기치 않은 예외, 손상된 데이터 구조, 부정확 한 계산 및 무한 루프와 같은 심각하고 혼란스러운 오류를 일으킬 수 있습니다. [2] 동기화없이 데이터를 읽는 것은 성능에 대한 정확성을 기꺼이 교환하려는 데이터베이스에서 READ_UNCOMMITTED 격리 수준을 사용하는 것과 유사합니다. 그러나 동기화되지 않은 읽기의 경우 공유 변수에 대한 가시적 값이 임의로 무효화 될 수 있으므로 더 큰 정확도로 거래하고 있습니다.
Listing 3.2의 MutableInteger는 값 필드가 동기화없이 get과 set에서 액세스되므로 스레드로부터 안전하지 않다. 하나의 스레드 호출이 설정되면 get을 호출하는 다른 스레드가 해당 업데이트를 보거나 보지 못할 수 있습니다. Listing 3.3의 SynchronizedInteger와 같이 getter와 setter를 동기화하여 MutableInteger 스레드를 안전하게 만들 수있다. setter 만 동기화하는 것만으로는 충분하지 않습니다. get을 호출하는 스레드는 여전히 오래된 값을 볼 수 있습니다.
Listing 3.2. Nonthreadsafe Mutable Integer Holder.
@NotThreadSafe
public class MutableInteger {
private int value;
public int get() { return value; }
public void set(int value) { this.value = value; }
}
Listing 3.3. Threadsafe Mutable Integer Holder.
@ThreadSafe
public class SynchronizedInteger {
@GuardedBy("this") private int value;
public synchronized int get() { return value; }
public synchronized void set(int value) { this.value = value; }
}
3.1.2. 비 원자 64 비트 연산 Nonatomic 64bit Operations
스레드가 동기화없이 변수를 읽으면 오래된 값이 표시 될 수 있지만 적어도 임의의 값이 아닌 일부 스레드에 의해 실제로 배치 된 값이 표시됩니다. 이 안전 보증은 얇은 공기 안전에서 불려집니다. 소량의 공기 안전은 모든 변수에 적용되지만 한 가지 예외는 휘발성으로 선언되지 않은 64 비트 숫자 변수 (이중 및 긴)입니다 (섹션 3.1.4 참조). Java 메모리 모델에서는 페치 및 저장 조작이 원 자성이어야하지만 비 휘발성 long 및 double 변수의 경우 JVM은 64 비트 읽기 또는 쓰기를 두 개의 개별 32 비트 조작으로 처리 할 수 있습니다. 읽기 및 쓰기가 다른 스레드에서 발생하는 경우 비 휘발성 long을 읽고 한 값의 높은 32 비트와 다른 값의 낮은 32 비트를 되돌릴 수 있습니다. 따라서 오래된 값에 신경 쓰지 않아도 다중 스레드 프로그램에서 공유 가변 가변 길이 및 이중 변수를 선언하지 않는 한 안전하지 않습니다. 휘발성이거나 잠금 장치로 보호됩니다.
3.1.3. 잠금 및 가시성 Locking and Visibility
그림 3.1에 설명 된 것처럼 하나의 스레드가 다른 스레드의 영향을 예측 가능한 방식으로 볼 수 있도록 내장 잠금을 사용할 수 있습니다. 스레드 A가 동기화 된 블록을 실행 한 다음 스레드 B가 동일한 잠금에 의해 보호되는 동기화 된 블록에 들어가면 잠금을 해제하기 전에 A에 표시되었던 변수 값은 잠금을 획득 할 때 B에 표시됩니다. 다시 말해, 동기화 된 블록에서 또는 이전에 수행 된 모든 A는 동일한 잠금으로 보호 된 동기화 된 블록을 실행할 때 B에 표시됩니다. 동기화가 없으면 그러한 보장이 없습니다.
공유 변경 가능 변수에 액세스 할 때 모든 스레드가 동일한 잠금에서 동기화해야하는 규칙의 다른 이유를 하나의 스레드가 작성한 값이 다른 스레드에 표시되도록 보장 할 수 있습니다. 그렇지 않으면 스레드가 적절한 잠금을 유지하지 않고 변수를 읽는 경우 오래된 값이 표시 될 수 있습니다. 잠금은 단순히 상호 배제에 관한 것이 아닙니다. 또한 메모리 가시성에 관한 것입니다. 모든 스레드가 공유 가변 변수의 최신 값을 볼 수있게하려면 읽기 및 쓰기 스레드가 공통 잠금에서 동기화되어야합니다.
3.1.4. 휘발성 변수 Volatile Variables
Java 언어는 또한 변수에 대한 업데이트가 다른 스레드에 예측 가능하게 전파되도록 대체 약한 동기화 형식 인 휘발성 변수를 제공합니다. 필드가 휘발성으로 선언되면 컴파일러와 런타임은이 변수가 공유되고 다른 메모리 작업과 순서가 바뀌지 않아야한다는 점에 주목합니다. 휘발성 변수는 레지스터 나 다른 프로세서에서 숨겨진 캐시에 캐시되지 않으므로 휘발성 변수를 읽으면 항상 스레드가 가장 최근에 쓴 쓰기를 반환합니다. 휘발성 변수에 대해 생각하는 좋은 방법은 SynchronizedInteger 클래스와 거의 비슷하게 동작한다고 상상하는 것입니다. [4] 한 값의 상위 32 비트와 다른 값의 하위 32 비트를 되돌립니다. 따라서 오래된 값에 신경 쓰지 않아도 Listing 3.3에서는 휘발성 변수의 읽기 및 쓰기를 get 및 set 호출로 대체합니다. 휘발성에 접근 변수는 잠금을 수행하지 않으므로 실행 스레드가 차단되지 않으므로 휘발성 변수가 더 가벼워집니다. 동기화보다 가중치 동기화 메커니즘.
휘발성 변수의 가시성 효과는 휘발성 변수 자체의 값을 넘어 확장됩니다. 스레드 A가 휘발성 변수에 기록한 다음 스레드 B가 동일한 변수를 읽는 경우, 휘발성 변수에 쓰기 전에 A에 표시되었던 모든 변수의 값은 휘발성 변수를 읽은 후 B에 표시됩니다. 따라서 메모리 가시성 관점에서 휘발성 변수를 작성하는 것은 동기화 된 블록을 종료하는 것과 같고 휘발성 변수를 읽는 것은 동기화 된 블록을 입력하는 것과 같습니다. 그러나 가시성을 위해 휘발성 변수에 너무 많이 의존하지 않는 것이 좋습니다. 임의 상태의 가시성을 위해 휘발성 변수를 사용하는 코드는 잠금을 사용하는 코드보다 취약하고 이해하기 어렵습니다. 휘발성 변수는 동기화 정책 구현 및 확인을 단순화 할 때만 사용하십시오. 정확성을 검증 할 때 변동성 변수를 사용하지 마십시오. 가시성에 대한 미묘한 추론이 필요합니다. 휘발성 변수의 올바른 사용에는 자체 상태, 참조하는 객체의 가시성 확보 또는 중요한 수명주기 이벤트 (예 : 초기화 또는 종료)가 발생했음을 나타내는 것이 포함됩니다.
Listing 3.4는 휘발성 변수의 일반적인 사용법을 보여줍니다. 루프 종료 시점을 결정하기 위해 상태 플래그 확인. 이것에서 예를 들어, 의인화 된 실은 양을 세는 시간을 존중하는 방법으로 잠을 자려고합니다. 에 대한 이 예제가 작동하려면 수면 플래그가 일시적이어야합니다. 그렇지 않으면 다른 스레드가 잠들었을 때 스레드가 인식하지 못할 수 있습니다. 대신 잠김 변경 사항의 가시성을 확보하기 위해 잠금을 사용할 수 있었지만 코드가 더 번거로워졌습니다.
Listing 3.4. Counting Sheep.
volatile boolean asleep; ...
while (!asleep)
countSomeSheep();
휘발성 변수는 편리하지만 한계가 있습니다. 휘발성 변수의 가장 일반적인 용도는 완료, 중단 또는 상태 플래그 (예 : Listing 3.4의 수면 플래그)입니다. 휘발성 변수는 다른 종류의 상태 정보에 사용될 수 있지만이를 시도 할 때는 더 많은주의가 필요합니다. 예를 들어, 휘발성의 의미는 변수가 단일 스레드에서만 작성되도록 보장 할 수없는 경우 증분 연산 (카운트 ++)을 원 자성으로 만들기에 충분하지 않습니다. (원자 변수는 원자 읽기 수정 쓰기 지원을 제공하며 종종 "보다 휘발성 변수"로 사용될 수 있습니다. 15 장을 참조하십시오.)
잠금은 가시성과 원 자성을 모두 보장 할 수 있습니다. 휘발성 변수는 가시성을 보장 할 수 있습니다.
다음 기준을 모두 충족하는 경우에만 휘발성 변수를 사용할 수 있습니다.
변수에 대한 쓰기는 현재 값에 의존하지 않거나 단일 스레드 만 값을 업데이트하도록 할 수 있습니다.
변수는 다른 상태 변수가있는 변형에 참여하지 않습니다.
변수에 액세스하는 동안 다른 이유로 잠금이 필요하지 않습니다.
3.2. 공개와 유출 Publication and Escape
객체를 게시한다는 것은 다른 코드가 찾을 수있는 위치에 대한 참조를 저장하거나 비공개 메서드에서 반환하거나 다른 클래스의 메서드로 전달하는 등 현재 범위를 벗어난 코드에 사용할 수있게하는 것을 의미합니다. 많은 상황에서, 우리는 객체와 그 내부가 공개되지 않도록하려고합니다. 다른 상황에서는 일반적인 용도로 객체를 게시하려고하지만 스레드 안전 방식으로 게시하려면 동기화가 필요할 수 있습니다. 내부 상태 변수를 게시하면 캡슐화가 손상되고 불변을 유지하기가 더 어려워 질 수 있습니다. 개체가 완전히 구성되기 전에 게시하면 스레드 안전성이 저하 될 수 있습니다. 해서는 안될 때 게시 된 개체가 이스케이프되었다고합니다. 섹션 3.5는 안전한 출판을위한 관용구를 다룹니다. 지금, 우리는 물체가 어떻게 탈출 할 수 있는지 봅니다. 가장 뻔뻔한 출판 형식은 클래스와 스레드가 볼 수있는 공개 정적 필드에 참조를 저장하는 것입니다. Listing 3.5와 같이. initialize 메소드는 새로운 HashSet을 인스턴스화하고 알려진 Secrets에 참조를 저장하여 공개합니다.
Listing 3.5. Publishing an Object.
public static Set<Secret> knownSecrets;
public void initialize() {
knownSecrets = new HashSet<Secret>();
}
한 개체를 게시하면 다른 개체를 간접적으로 게시 할 수 있습니다. 공개 된 알려진 비밀 세트에 시크릿을 추가하면 모든 코드가 세트를 반복하고 새 시크릿에 대한 참조를 얻을 수 있기 때문에 해당 비밀도 공개했습니다. 마찬가지로 비 개인적 메서드에서 참조를 반환하면 반환 된 개체도 게시됩니다. Listing 3.6의 UnsafeStates는 아마도 개인의 상태 약어 배열을 공개한다.
Listing 3.6 Allowing Internal Mutable State to Escape. Don't Do this.
class UnsafeStates {
private String[] states = new String[] {
"AK", "AL" ... };
public String[] getStates() { return states; }
}
이 방법으로 상태를 게시하면 호출자가 내용을 수정할 수 있기 때문에 문제가됩니다. 이 경우 상태 배열은 의도 한 범위를 벗어났습니다. 개인 상태로 가정 된 것이 효과적으로 공개 되었기 때문입니다. 객체를 게시하면 비공개 필드가 참조하는 객체도 게시됩니다. 보다 일반적으로, 비공개 필드 참조 및 메소드 호출 체인을 따라 게시 된 개체에서 도달 할 수있는 모든 개체도 게시되었습니다. 클래스 C의 관점에서 볼 때 외계인 메서드는 C에 의해 동작이 완전히 지정되지 않은 메서드입니다. 여기에는 다른 클래스의 메서드와 C 자체의 재정의 가능한 메서드 (비공개 또는 마지막이 아닌 메서드)가 포함됩니다. 객체를 외계인 메서드에 전달하는 것도 해당 객체를 게시하는 것으로 간주해야합니다. 실제로 어떤 코드가 호출되는지 알 수 없기 때문에 외계인 메서드가 객체를 게시하거나 나중에 다른 스레드에서 사용될 수있는 객체에 대한 참조를 유지하지 않을 것입니다. 오용의 위험이 여전히 존재하기 때문에 다른 스레드가 실제로 게시 된 참조로 무언가를 수행하는지 여부는 중요하지 않습니다. 객체가 빠져 나가면 다른 클래스 나 스레드가 악의적으로 또는 부주의하게 오용 할 수 있다고 가정해야합니다. 이는 캡슐화를 사용해야하는 강력한 이유입니다. 프로그램의 정확성을 분석하고 실수로 설계 제약 조건을 위반하기 어렵게 만듭니다.
객체 또는 내부 상태를 게시 할 수있는 마지막 메커니즘은 Listing 3.7의 ThisEscape에 표시된대로 내부 클래스 인스턴스를 게시하는 것입니다. 내부 클래스 인스턴스에 엔 클로징 인스턴스에 대한 숨겨진 참조가 포함되어 있기 때문에 ThisEscape가 EventListener를 공개하면 엔 클로징 ThisEscape 인스턴스도 내재적으로 공개합니다.
Listing 3.7. Implicitly Allowing the this Reference to Escape. Don't Do this.
public class ThisEscape {
public ThisEscape(EventSource source) {
source.registerListener(
new EventListener() {
public void onEvent(Event e) {
doSomething(e);
}
});
}
}
3.2.1. 안전한 건축 관행 Safe Construction Practices
ThisEscape는이 참조가 구성 중에 이스케이프 될 때 중요한 특수 이스케이프 사례를 보여줍니다. 내부 EventListener 인스턴스가 게시되면 둘러싼 ThisEscape 인스턴스도 게시됩니다. 그러나 객체는 생성자가 반환 된 후에 만 예측 가능하고 일관된 상태에 있으므로 생성자 내에서 객체를 게시하면 불완전하게 구성된 객체를 게시 할 수 있습니다. 게시가 생성자의 마지막 명령문 인 경우에도 마찬가지입니다. 이 참조가 구성 중에 이스케이프되면 오브젝트가 올바르게 구성되지 않은 것으로 간주됩니다.
시공 중에이 참조가 빠져 나가지 않도록하십시오.
구성하는 동안이 참조를 벗어날 수있는 일반적인 실수는 생성자에서 스레드를 시작하는 것입니다. 객체가 생성자에서 스레드를 만들면 거의 항상이 참조를 새 스레드와 명시 적으로 (생성자에 전달하여) 또는 암시 적으로 (스레드 또는 Runnable이 소유 객체의 내부 클래스이기 때문에) 공유합니다. 그러면 새 스레드가 소유 오브젝트를 완전히 구성하기 전에 볼 수 있습니다. 생성자에서 스레드를 만드는 데 아무런 문제가 없지만 스레드를 즉시 시작하지 않는 것이 가장 좋습니다. 대신 소유 한 스레드를 시작하는 시작 또는 초기화 메소드를 노출하십시오. (서비스 수명주기 문제에 대한 자세한 내용은 7 장을 참조하십시오.) 생성자에서 재정의 가능한 인스턴스 메서드 (비공개 또는 최종이 아닌 메서드)를 호출하면이 참조가 이스케이프 될 수 있습니다. 이벤트 리스너를 등록하거나 생성자에서 스레드를 시작하려는 유혹이있는 경우 Listing 3.8의 SafeListener에 표시된 것처럼 개인 생성자와 공용 팩토리 메소드를 사용하여 부적절한 구성을 피할 수 있습니다.
Listing 3.8. Using a Factory Method to Prevent the this Reference from Escaping During Construction.
public class SafeListener {
private final EventListener listener;
private SafeListener() {
listener = new EventListener() {
public void onEvent(Event e) {
doSomething(e);
} };
}
public static SafeListener newInstance(EventSource source) {
SafeListener safe = new SafeListener();
source.registerListener(safe.listener);
return safe;
}
}
3.3. 스레드 감금 Thread Confinement
공유되고 변경 가능한 데이터에 액세스하려면 동기화를 사용해야합니다. 이 요구 사항을 피하는 한 가지 방법은 공유하지 않는 것입니다. 단일 스레드에서만 데이터에 액세스하는 경우 동기화가 필요하지 않습니다. 스레드 감금 기술은 스레드 안전을 달성하는 가장 간단한 방법 중 하나입니다. 객체가 스레드에 한정된 경우, 한정된 객체 자체가 [CPJ 2.3.2]가 아니더라도 이러한 사용법은 자동으로 스레드 안전합니다. 스윙은 스레드 감금을 광범위하게 사용합니다. Swing 시각적 구성 요소 및 데이터 모델 개체는 스레드로부터 안전하지 않습니다. 대신 안전을 Swing 이벤트 디스패치 스레드로 제한하여 안전을 달성합니다. Swing을 올바르게 사용하려면 이벤트 스레드 이외의 스레드에서 실행중인 코드가 이러한 객체에 액세스하면 안됩니다. 이를 쉽게하기 위해 Swing은 이벤트 스레드에서 실행을 위해 Runnable을 예약 할 수있는 invokeLater 메커니즘을 제공합니다. Swing의 많은 동시성 오류 응용 프로그램은 다른 스레드에서 이러한 제한된 개체를 잘못 사용하여 발생합니다. 스레드 제한의 또 다른 일반적인 적용은 풀링 된 JDBC (Java Database Connectivity) 연결 개체를 사용하는 것입니다. JDBC 사양에서는 Connection 객체가 스레드로부터 안전하지 않아도됩니다. 일반적인 서버 응용 프로그램에서 스레드는 풀에서 연결을 가져 와서 단일 요청을 처리하는 데 사용하여 반환합니다. 서블릿 요청 또는 EJB (Enterprise JavaBeans) 호출과 같은 대부분의 요청은 단일 스레드에 의해 동기식으로 처리되며 풀은 다른 스레드에 대한 동일한 연결을 리턴 할 때까지 분배하지 않으므로이 연결 관리 패턴은 암시 적으로 요청 기간 동안 해당 스레드에 연결
언어에 변수가 잠금으로 보호되도록하는 메커니즘이없는 것처럼 객체를 스레드로 제한 할 수는 없습니다. 스레드 제한은 구현시 반드시 적용해야하는 프로그램 디자인의 요소입니다. 언어 및 핵심 라이브러리는 스레드 제한 로컬 변수 및 ThreadLocal 클래스를 유지하는 데 도움이되는 메커니즘을 제공하지만 이러한 경우에도 스레드 제한 객체가 의도 한 스레드에서 벗어나지 않도록하는 것은 프로그래머의 책임입니다.
3.3.1. 애드혹 스레드 감금 Ad-hoc Thread Confinement
임시 스레드 제한은 스레드 제한 유지에 대한 책임이 구현에 전적으로 영향을 미치는시기를 설명합니다. 가시성 수정 자 또는 로컬 변수와 같은 언어 기능은 대상 스레드에 객체를 제한하는 데 도움이되지 않으므로 임시 스레드 제한은 취약 할 수 있습니다. 실제로 GUI 응용 프로그램의 시각적 구성 요소 또는 데이터 모델과 같은 스레드 제한 개체에 대한 참조는 종종 공개 필드에서 유지됩니다. 스레드 제한 사용 결정은 종종 GUI와 같은 특정 하위 시스템을 단일 스레드 하위 시스템으로 구현하기로 결정한 결과입니다. 단일 스레드 서브 시스템은 임시 스레드 제한의 취약성을 능가하는 단순성 이점을 제공 할 수 있습니다.
스레드 제한의 특별한 경우는 휘발성 변수에 적용됩니다. 휘발성 변수가 단일 스레드에서만 작성되도록하는 한 공유 휘발성 변수에 대한 읽기 수정 쓰기 작업을 수행하는 것이 안전합니다. 이 경우 경쟁 조건을 방지하기 위해 수정을 단일 스레드로 제한하고 변동 변수에 대한 가시성 보장을 통해 다른 스레드가 최신 값을 볼 수 있습니다. 취약성 때문에 애드혹 실 감금은 드물게 사용해야합니다. 가능하면보다 강력한 형태의 스레드 제한 (스택 제한 또는 ThreadLocal) 중 하나를 대신 사용하십시오.
3.3.2. 스택 감금 Stack Confinement
스택 감금은 객체가 지역 변수를 통해서만 도달 할 수있는 특수한 스레드 감금입니다. 캡슐화를 통해 불변 값을보다 쉽게 보존 할 수있는 것처럼 로컬 변수를 사용하면 객체를 스레드에보다 쉽게 제한 할 수 있습니다. 지역 변수는 본질적으로 실행 스레드에 국한됩니다. 그것들은 실행중인 스레드의 스택에 존재하며 다른 스레드는 액세스 할 수 없습니다. 스택 제한 (스레드 또는 스레드 로컬 사용 내에서 호출되지만 ThreadLocal 라이브러리 클래스와 혼동하지 않아야 함)은 임시 스레드 제한보다 유지 관리가 간단하고 취약하지 않습니다. Listing 3.9의 loadTheArk에있는 numPairs와 같은 기본적으로 유형이 지정된 지역 변수의 경우 시도한 경우에도 스택 제한을 위반할 수 없습니다. 기본 변수에 대한 참조를 얻을 수있는 방법이 없으므로 언어 의미론은 기본 로컬 변수가 항상 스택 제한되어 있는지 확인합니다.
Listing 3.9. Thread Confinement of Local Primitive and Reference Variables.
public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numPairs = 0;
Animal candidate = null;
// animals confined to method, don't let them escape!
animals = new TreeSet<Animal>(new SpeciesGenderComparator()); animals.addAll(candidates);
for (Animal a : animals) {
if (candidate == null || !candidate.isPotentialMate(a))
candidate = a;
else {
ark.load(new AnimalPair(candidate, a)); ++numPairs;
candidate = null;
}
}
return numPairs;
}
객체 참조에 대한 스택 제한을 유지하려면 해당 참조자가 탈출하지 않도록 프로그래머의 도움이 조금 더 필요합니다. loadTheArk에서 treeSet을 인스턴스화하고 참조를 동물에 저장합니다. 이 시점에서 Set에 대한 하나의 참조가 로컬 변수에 유지되어 실행 스레드로 제한됩니다. 그러나 만약 우리가 세트 (또는 그 내부)에 대한 참조를 발표한다면, 감금은 위반되고 동물은 탈출 할 것입니다. 스레드 내 컨텍스트에서 스레드로부터 안전하지 않은 개체를 사용하는 것은 여전히 스레드 안전입니다. 그러나 객체를 실행 스레드에 국한시켜야하는 설계 요구 사항 또는 제한된 객체가 스레드에 안전하지 않다는 인식은 종종 코드를 작성할 때 개발자의 머리에만 존재합니다. 스레드 사용 내 가정이 명확하게 문서화되지 않은 경우, 미래의 유지 보수 담당자는 실수로 오브젝트가 빠져 나갈 수 있습니다.
3.3.3. ThreadLocal
스레드 제한을 유지하는보다 공식적인 방법은 ThreadLocal이며 스레드 당 값을 값 보유 개체와 연결할 수 있습니다. Thread-Local은이를 사용하는 각 스레드에 대해 별도의 값 사본을 유지하는 get 및 set 접근 자 메소드를 제공하므로 get은 현재 실행중인 스레드에서 설정으로 전달 된 최신 값을 리턴합니다. 스레드 로컬 변수는 종종 가변 싱글 톤 또는 전역 변수를 기반으로하는 설계에서 공유를 방지하는 데 사용됩니다. 예를 들어 단일 스레드 응용 프로그램은 시작시 초기화되는 전역 데이터베이스 연결을 유지하여 모든 메서드에 Connection을 전달하지 않아도됩니다. JDBC 연결은 스레드로부터 안전하지 않을 수 있으므로 추가 조정없이 전역 연결을 사용하는 다중 스레드 응용 프로그램도 스레드로부터 안전하지 않습니다. Listing 3.10의 ConnectionHolder에서와 같이 ThreadLocal을 사용하여 JDBC 연결을 저장하면 각 스레드는 자체 연결을 갖게된다.
Listing 3.10. Using ThreadLocal to Ensure thread Confinement.
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
이 기법은 자주 사용하는 작업에 버퍼와 같은 임시 개체가 필요한 경우에도 사용할 수 있습니다. 각 호출에서 임시 오브젝트를 재할 당하지 않으려 고합니다. 예를 들어 Java 5.0 이전의 Integer.toString ThreadLocal을 사용하여 공유 고정 버퍼를 사용하거나 (잠금이 필요한) 각 호출에 대해 새 버퍼를 할당하는 대신 결과 형식을 지정하는 데 사용되는 12 바이트 버퍼를 저장했습니다.
스레드가 처음으로 ThreadLocal.get을 호출하면 해당 스레드의 초기 값을 제공하기 위해 initialValue가 참조됩니다. 개념적으로 ThreadLocal <T>은 스레드 특정 값을 저장하는 Map <Thread, T>를 보유하는 것으로 생각할 수 있지만 실제로 구현되는 방식은 아닙니다. 스레드 특정 값은 Thread 개체 자체에 저장됩니다. 스레드가 종료되면 스레드 특정 값을 가비지 수집 할 수 있습니다.
단일 스레드 응용 프로그램을 다중 스레드 환경으로 이식하는 경우 다음과 같은 방법으로 스레드 안전을 유지할 수 있습니다 공유 전역 변수의 의미가 허용하는 경우 공유 전역 변수를 ThreadLocals로 변환 응용 프로그램 전체 캐시는 여러 스레드 로컬 캐시로 바뀌면 유용하지 않습니다. ThreadLocal은 응용 프로그램 프레임 워크 구현에 널리 사용됩니다. 예를 들어, J2EE 컨테이너는 EJB 호출 기간 동안 트랜잭션 컨텍스트를 실행 스레드와 연관시킵니다. 이것은 트랜잭션 컨텍스트를 보유하는 정적 Thread-Local을 사용하여 쉽게 구현됩니다. 프레임 워크 코드는 현재 실행중인 트랜잭션을 결정해야 할 때이 ThreadLocal에서 트랜잭션 컨텍스트를 가져옵니다. 이는 실행 컨텍스트 정보를 모든 메소드에 전달할 필요성을 줄이지 만이 메커니즘을 사용하는 모든 코드를 프레임 워크에 결합한다는 점에서 편리합니다. 스레드 제한 특성을 글로벌 변수를 사용하기위한 라이센스로 또는 "숨겨진"메소드 인수를 작성하는 수단으로 처리하여 ThreadLocal을 악용하기 쉽습니다. 전역 변수와 마찬가지로 스레드 로컬 변수는 재사용 성을 떨어 뜨리고 클래스간에 숨겨진 커플 링을 유발할 수 있으므로주의해서 사용해야합니다.
3.4. 불변성
동기화의 필요성에 대한 다른 끝은 불변의 객체를 사용하는 것입니다 [EJ Item 13]. 오래된 값보기, 업데이트 손실 또는 개체가 일관성이없는 상태를 관찰하는 것과 같이 지금까지 설명한 거의 모든 원 자성 및 가시성 위험 요소는 동일한 변경 가능한 상태에 액세스하려는 여러 스레드의 구성 요소와 관련이 있습니다. 동시에. 객체의 상태를 수정할 수없는 경우 이러한 위험과 복잡성은 사라집니다. 불변 개체는 생성 후 상태를 변경할 수없는 개체입니다. 불변의 객체는 본질적으로 스레드로부터 안전합니다. 그들의 불변은 생성자에 의해 확립되고, 상태가 변경 될 수 없다면, 이러한 불변은 항상 유지됩니다.
불변 객체는 항상 스레드 안전합니다.
불변 개체는 간단합니다. 생성자에 의해 신중하게 제어되는 하나의 상태에만있을 수 있습니다. 프로그램 설계에서 가장 어려운 요소 중 하나는 복잡한 객체의 가능한 상태에 대한 추론입니다. 반면에 불변 객체의 상태에 대한 추론은 사소한 일입니다. 불변 개체도 더 안전합니다. 신뢰할 수없는 코드에 변경 가능한 객체를 전달하거나 신뢰할 수없는 코드가 찾을 수있는 위치에 게시하면 신뢰할 수없는 코드가 상태를 수정하거나, 참조를 유지하고 나중에 다른 스레드에서 상태를 수정하는 것이 위험 할 수 있습니다. 반면, 불변의 객체는 악성 코드 나 버그가있는 코드로 이러한 방식으로 전복 될 수 없으므로 방어적인 사본을 만들 필요없이 자유롭게 공유하고 게시 할 수 있습니다 [EJ Item 24]. Java 언어 사양이나 Java 메모리 모델 중 어느 것도 공식적으로 불변성을 정의하지 않지만, 불변성은 객체 최종의 모든 필드를 선언하는 것과 동등하지 않습니다. final 필드는 변경 가능한 객체에 대한 참조를 보유 할 수 있으므로 필드가 모두 final 인 객체는 여전히 변경 가능합니다.
다음과 같은 경우 객체를 변경할 수 없습니다.
시공 후 상태는 수정할 수 없습니다.
모든 field가 final로 선언 되어있다.
올바르게 구성되었습니다 (이 참조는 구성 중에 이스케이프되지 않습니다)
변경 불가능한 객체는 여전히 내부에서 변경 가능한 객체를 사용하여 상태를 관리 할 수 있습니다 (Listing 3.11의 ThreeStooges에 의해 설명 됨). 이름을 저장하는 Set은 변경할 수 있지만 ThreeStooges의 디자인은 생성 후 해당 Set을 수정할 수 없습니다. stooges 참조는 final이므로 모든 객체 상태는 final 필드를 통해 도달합니다. 생성자가이 참조를 생성자와 호출자 이외의 다른 코드에서 액세스 할 수있게하는 작업을 수행하지 않으므로 마지막 요구 사항 인 올바른 구성이 쉽게 충족됩니다.
Listing 3.11. Immutable Class Built Out of Mutable Underlying Objects.
@Immutable
public final class ThreeStooges {
private final Set<String> stooges = new HashSet<String>();
public ThreeStooges() {
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}
public boolean isStooge(String name) {
return stooges.contains(name);
}
}
프로그램 상태는 항상 변하기 때문에 변경 불가능한 객체의 사용이 제한적이라고 생각할 수도 있지만, 그렇지 않습니다. 불변의 객체와 불변의 객체의 참조에는 차이가 있습니다. 불변 객체에 저장된 프로그램 상태는 새로운 상태를 유지하는 새로운 인스턴스로 불변 객체를 "대체"하여 업데이트 할 수 있습니다. 다음 섹션에서는이 기술의 예를 제공합니다.
3.4.1. 최종 필드 Final Fields
C ++의 const 메커니즘의보다 제한된 버전 인 final 키워드는 변경 불가능한 객체의 구성을 지원합니다. 최종 필드는 수정할 수 없지만 (참조 할 수있는 객체는 변경 가능한 경우 수정할 수 있지만) Java 메모리 모델에 특수 의미가 있습니다. 초기화 필드 (3.5.2 참조)를 보장하는 최종 필드를 사용하여 불변 개체를 동기화없이 자유롭게 액세스하고 공유 할 수 있습니다. 객체가 변경 가능하더라도 객체의 변경 가능성을 제한하면 가능한 상태 세트가 제한되므로 일부 필드를 최종으로 만들면 상태에 대한 추론을 단순화 할 수 있습니다. "주로 불변"이지만 1 개 또는 2 개의 변경 가능한 상태 변수가있는 개체는 여전히 변경 가능한 변수가 많은 개체보다 간단합니다. 마지막 필드 선언은 유지 보수 담당자에게 이러한 필드가 변경되지 않을 것이라고 문서화합니다. 더 큰 가시성을 필요로하지 않는 한 모든 필드를 비공개로 설정하는 것이 좋습니다 [EJ Item 12]. 변경이 필요하지 않은 경우 모든 필드를 최종으로 만드는 것이 좋습니다.
3.4.2. 예 : 휘발성을 사용하여 변경 불가능한 객체 Example: Using Volatile to Publish Immutable Objects
게시 24 페이지의 UnsafeCachingFactorizer에서 두 개의 AtomicReferences를 사용하여 마지막 숫자와 마지막 요소를 저장하려고했지만 두 개의 관련 값을 원자 적으로 가져 오거나 업데이트 할 수 없으므로 스레드 안전하지 않았습니다. 이러한 값에 휘발성 변수를 사용하면 같은 이유로 스레드로부터 안전하지 않습니다. 그러나 불변 개체는 때때로 약한 형태의 원 자성을 제공 할 수 있습니다. 팩토링 서블릿은 원자 적이어야하는 두 가지 작업을 수행합니다. 캐시 된 결과를 업데이트하고 캐시 된 번호가 요청 된 번호와 일치하는 경우 캐시 된 요소를 조건부로 가져옵니다. 관련 데이터 항목 그룹을 원자 적으로 수행해야 할 때마다 Listing 3.12의 OneValueCache [14]와 같이 변경 불가능한 홀더 클래스를 작성하는 것이 좋습니다.
변경 불가능한 객체를 사용하여 모든 변수를 보유함으로써 여러 관련 변수에 액세스하거나 업데이트하는 경쟁 조건을 제거 할 수 있습니다. 가변 홀더 객체를 사용하면 원 자성을 보장하기 위해 잠금을 사용해야합니다. 불변의 것을 사용하면 스레드가 참조를 획득하면 다른 스레드가 상태를 수정하는 것에 대해 걱정할 필요가 없습니다. 변수를 업데이트 할 경우 새 홀더 객체가 생성되지만 이전 홀더로 작업하는 모든 스레드는 여전히 일관된 상태로 표시됩니다.
Listing 3.12. Immutable Holder for Caching a Number and its Factors.
@Immutable
class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger i,
BigInteger[] factors) {
lastNumber = i;
lastFactors = Arrays.copyOf(factors, factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
Listing 3.13의 VolatileCachedFactorizer는 OneValueCache를 사용하여 캐시 된 수와 요소를 저장한다. 스레드가 새로운 OneValueCache를 참조하도록 휘발성 캐시 필드를 설정하면 캐시 된 새 데이터가 다른 스레드에 즉시 표시됩니다. 캐시 관련 작업은 One-ValueCache가 변경 불가능하고 캐시 필드가 관련 코드 경로 각각에서 한 번만 액세스되므로 서로 간섭 할 수 없습니다.
불변에 의해 관련된 여러 상태 변수에 대한 불변 홀더 개체와 적시에 가시성을 확보하는 데 사용되는 휘발성 참조의 이러한 조합은 VolatileCachedFactorizer가 명시적인 잠금이 없어도 스레드로부터 안전합니다.
3.5. 안전한 출판 Safe Publication
지금까지 우리는 객체가 스레드 또는 다른 객체 내에 한정되어있을 때와 같이 객체가 게시되지 않도록하는 데 중점을 두었습니다. 물론 여러 스레드에서 객체를 공유하고 싶을 때도 있으므로 안전하게 공유해야합니다. 불행히도 Listing 3.14와 같이 단순히 객체에 대한 참조를 공개 필드에 저장하는 것만으로는 해당 객체를 안전하게 게시하기에 충분하지 않습니다.
Listing 3.13. Caching the Last Result Using a Volatile Reference to an Immutable Holder Object.
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache =
new OneValueCache(null, null);
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(resp, factors);
}
}
위의 코드에서 getFactors()로 얻는 값인 cache는 volatile 타입 선언으로 가시성을 확보하지만, getFactors()에 여러 스레드가 동시에 접근하다고 했을 때, 동시성은 보장 못하는 코드로 보여진다.
즉, 가시성을 보장하고 동시성을 보장하지 않을 때 우리는 ThreadSafety 하다고 말 할 수 있나??
Listing 3.14. Publishing an Object without Adequate Synchronization. Don't Do this.
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
이 무해한보기가 얼마나 나쁘게 실패 할 수 있는지에 놀랄 것입니다. 가시성 문제로 인해, 생성자가 불변 값을 올바르게 설정 했음에도 불구하고 Holder가 다른 스레드에 일관성이없는 것처럼 보일 수 있습니다! 이 부적절한 게시를 통해 다른 스레드가 부분적으로 구성된 개체를 관찰 할 수 있습니다.
3.5.1. 부적절한 출판물 : 좋은 물건이 나빠질 때 Improper Publication: When Good Objects Go Bad
부분적으로 구성된 개체의 무결성에 의존 할 수 없습니다. 관찰 스레드는 객체가 일관성이없는 상태에있는 것을 볼 수 있으며 나중에 게시 이후 수정되지 않았더라도 상태가 갑자기 변경되는 것을 볼 수 있습니다.
실제로 Listing 3.15의 홀더가 Listing 3.14의 안전하지 않은 출판 관용구를 사용하여 게시되고 다른 스레드는 게시 스레드가 assertSanity를 호출하는 것보다 AssertionError가 발생할 수 있습니다!
Listing 3.15. Class at Risk of Failure if Not Properly Published.
public class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n)
}
}
홀더를 다른 스레드가 볼 수 있도록 동기화가 사용되지 않았기 때문에 홀더가 올바르게 작동하지 않았다고합니다. 출판. 잘못 게시 된 개체에는 두 가지 문제가 발생할 수 있습니다. 다른 스레드는 오래된 값을 볼 수 있습니다. 홀더 필드이므로 값이 홀더에 배치 된 경우에도 널 참조 또는 다른 이전 값을 볼 수 있습니다. 그러나 지금까지 더 나쁜 것은 다른 스레드가 홀더 참조에 대한 최신 값을 볼 수 있지만 [16] 보유자. 상황을 예측하기 어렵게 만들기 위해 스레드는 처음 필드를 읽을 때 오래된 값을 볼 수 있습니다. 다음 번에 더 최신 값을 가지므로 assertSanity가 AssertionError를 발생시킬 수 있습니다.
반복 할 위험이 있으므로 데이터를 충분한 동기화없이 스레드간에 공유하면 매우 이상한 일이 발생할 수 있습니다.
3.5.2. 불변 객체 및 초기화 안전 Immutable Objects and Initialization Safety
불변 객체는 매우 중요하기 때문에 JavaMemory Model은 불변 객체를 공유하기위한 초기화 안전성을 특별하게 보장합니다. 앞에서 본 것처럼 객체 참조가 다른 스레드에 표시된다고해서 해당 객체의 상태가 소비 스레드에 표시되는 것은 아닙니다. 객체의 상태를 일관되게 보려면 동기화가 필요합니다. 반면, 변경 불가능한 객체는 동기화를 사용하여 객체 참조를 게시하지 않더라도 안전하게 액세스 할 수 있습니다. 초기화 안전성을 유지하려면 불변성에 대한 모든 요구 사항을 충족해야합니다. 수정 불가능한 상태, 모든 필드가 최종적이며 올바른 구성 (Listing 3.15의 Holder가 불변 인 경우, AsserSanity는 Holder가 제대로 게시되지 않았더라도 AssertionError를 던질 수 없습니다.) 동기화를 사용하여 게시하지 않은 경우에도 추가 동기화없이 스레드에서 변경 불가능한 개체를 안전하게 사용할 수 있습니다. 이 보증은 올바르게 구성된 객체의 모든 최종 필드 값까지 확장됩니다. 추가 동기화없이 최종 필드에 안전하게 액세스 할 수 있습니다. 그러나 최종 필드가 변경 가능한 객체를 참조하는 경우 참조하는 객체의 상태에 액세스하려면 동기화가 여전히 필요합니다.
3.5.3. 안전한 출판 관용구 Safe Publication Idioms
변경할 수없는 개체는 안전하게 게시해야하며, 일반적으로 게시 스레드와 소비 스레드에 의한 동기화가 수반됩니다. 현재 소비 스레드가 객체를 게시 된 상태로 볼 수 있도록하는 데 중점을 두겠습니다. 게시 후 수정 사항의 가시성을 곧 다룰 것입니다.
객체를 안전하게 게시하려면 객체에 대한 참조와 객체 상태가 동시에 다른 스레드에 표시되어야합니다.
올바르게 구성된 객체는 다음을 통해 안전하게 게시 할 수 있습니다.
정적 초기화 기에서 객체 참조 초기화 참조를 휘발성 필드 또는 AtomicReference에 저장하는 단계;
적절하게 구성된 객체의 최종 필드에 참조를 저장하는 단계;
또는 참조를 잠금으로 올바르게 보호되는 필드에 저장합니다.
스레드 안전 컬렉션의 내부 동기화는 Vector 또는 synchronizedList와 같은 스레드 안전 컬렉션에 개체를 배치하면 이러한 요구 사항 중 마지막 요구 사항을 충족한다는 의미입니다. 스레드 A가 오브젝트 X를 스레드 안전 콜렉션에 배치하고 스레드 B가 후속 적으로이를 검색하면, B는 X를 처리하는 응용 프로그램 코드가 명시 적으로 동기화되지 않더라도 X의 상태를 A가 그대로 유지 한 것으로 간주됩니다. 스레드 안전 라이브러리 콜렉션은 Javadoc이 주제에 대해 명확하지 않은 경우에도 다음과 같은 안전한 발행을 보장합니다. Hashtable, synchronizedMap 또는 Concurrent-Map에 키 또는 값을 배치하면 직접 또는 반복자를 통해 맵에서 검색하는 스레드에 키 또는 값을 안전하게 게시합니다. Vector, CopyOnWriteArrayList, CopyOnWrite-ArraySet, synchronizedList 또는 synchronizedSet에 요소를 배치하면 컬렉션에서 해당 요소를 검색하는 스레드에 안전하게 게시됩니다. BlockingQueue 또는 ConcurrentLinkedQueue에 요소를 배치하면 해당 요소를 큐에서 검색하는 스레드에 안전하게 게시됩니다. 클래스 라이브러리의 다른 핸드 오프 메커니즘 (예 : Future 및 Exchanger)도 안전한 게시를 구성합니다. 우리는 소개 될 때 안전한 출판을 제공하는 것으로 식별 할 것입니다. 정적 이니셜 라이저를 사용하는 것이 정적으로 구성 될 수있는 객체를 게시하는 가장 쉽고 안전한 방법입니다.
public static Holder holder = new Holder(42);
정적 초기화는 클래스 초기화 시간에 JVM에 의해 실행됩니다. JVM의 내부 동기화로 인해이 메커니즘은 이러한 방식으로 초기화 된 모든 객체를 안전하게 게시 할 수 있습니다 [JLS 12.4.2].
3.5.4. Effectively Immuatable Objects 효과적으로 불변 개체
안전한 게시는 다른 스레드가 추가 동기화없이 게시 후 수정되지 않은 개체에 안전하게 액세스하기에 충분합니다. 안전한 게시 메커니즘은 개체의 게시 된 상태를 참조하는 개체가 표시되는 즉시 액세스하는 모든 스레드에 표시되도록하며, 해당 상태가 다시 변경되지 않는 경우 모든 액세스를 보장하기에 충분합니다 은 안전하다. 기술적으로 변경할 수 없지만 게시 후 상태가 수정되지 않는 객체를 효과적으로 변경할 수 없습니다. 그들은 섹션 3.4에서 엄격한 불변성의 정의를 충족시킬 필요는 없다. 그것들은 마치 출판 된 후에 불변 인 것처럼 프로그램에 의해 처리 될 필요가 있습니다. 효과적으로 변경할 수없는 객체를 사용하면 동기화 필요성을 줄여 개발을 단순화하고 성능을 향상시킬 수 있습니다. 안전하게 게시 된 불변 개체는 추가 동기화없이 모든 스레드에서 안전하게 사용할 수 있습니다. 예를 들어, Date는 변경 가능하지만 [17] 변경할 수없는 것처럼 사용하면 스레드간에 Date를 공유 할 때 필요한 잠금을 제거 할 수 있습니다. 각 사용자의 마지막 로그인 시간을 저장하는 맵을 유지한다고 가정하십시오.
public Map<String, Date> lastLogin = Collections.synchronizedMap(new HashMap<String, Date>());
날짜 값이 맵에 배치 된 후 수정되지 않은 경우, synchronizedMap 구현의 동기화는 날짜 값을 안전하게 공개하기에 충분하며 액세스 할 때 추가 동기화가 필요하지 않습니다.
3.5.5. 가변 객체 Mutable Objects
생성 후 객체를 수정할 수있는 경우 안전한 게시는 게시 된 상태의 가시성 만 보장합니다. 동기화는 변경 가능한 객체를 게시 할 때뿐만 아니라 후속 수정의 가시성을 보장하기 위해 객체에 액세스 할 때마다 사용해야합니다. 변경 가능한 객체를 안전하게 공유하려면 안전하게 게시되고 스레드로부터 안전하거나 잠금으로 보호되어야합니다.
객체의 게시 요구 사항은 변경 가능성에 따라 다릅니다.
- 불변 개체는 모든 메커니즘을 통해 게시 할 수 있습니다.
- 효과적으로 변경할 수없는 객체는 안전하게 게시해야합니다.
- 가변 객체는 안전하게 게시해야하며 스레드 안전 또는 잠금 장치로 보호되어야합니다.
3.5.6. 안전하게 객체 공유 Sharing Objects Safely
객체에 대한 참조를 얻을 때마다 객체로 무엇을 할 수 있는지 알아야합니다. 사용하기 전에 잠금을 획득해야합니까? 상태를 수정하거나 읽을 수만 있습니까? 많은 동시성 오류는 공유 객체에 대한 이러한 "결합 규칙"을 이해하지 못해 발생합니다. 객체를 게시 할 때 객체에 액세스하는 방법을 문서화해야합니다. 동시 프로그램에서 객체를 사용하고 공유하기위한 가장 유용한 정책은 다음과 같습니다. 스레드 한정. 스레드 제한 오브젝트는 하나의 스레드 만 독점적으로 소유하고 제한하며 소유 스레드에 의해 수정 될 수 있습니다. 공유 읽기 전용. 공유 읽기 전용 개체는 추가 동기화없이 여러 스레드가 동시에 액세스 할 수 있지만 스레드가 수정할 수는 없습니다. 공유 읽기 전용 객체에는 불변 및 사실상 불변의 객체가 포함됩니다. 공유 스레드 안전. 스레드 안전 개체는 내부적으로 동기화를 수행하므로 여러 스레드가 추가 동기화없이 공용 인터페이스를 통해 자유롭게 액세스 할 수 있습니다. 가드. 보호 된 오브젝트는 특정 잠금을 보유한 상태에서만 액세스 할 수 있습니다. 보호 대상 개체에는 다른 스레드 안전 개체 내에 캡슐화 된 개체와 특정 잠금으로 보호되는 것으로 알려진 게시 된 개체가 포함됩니다.
'JAVA > Concurrency' 카테고리의 다른 글
# Java Concurrency In Practice - Chapter 2 (0) | 2020.02.13 |
---|---|
# Java Concurrency In Practice - Chapter 1 (0) | 2020.02.03 |
# Java Concurrency and Multithreading Tutorial - Concurrency vs Parallelism (0) | 2020.01.31 |
# Java Concurrency and Multithreading Tutorial - Multithreading Benefits (0) | 2019.12.31 |
# Java Concurrency and Multithreading Tutorial (0) | 2019.12.30 |