공유 데이터는 동시 프로그램의 가장 중요한 기능 중 하나입니다. 이는 Thread 클래스를 상속하는 객체이든 Runnable 인터페이스를 구현하는 객체이든 매우 중요한 측면입니다.
Runnable 인터페이스를 구현하는 클래스의 객체를 생성하고 해당 객체를 사용하여 일련의 스레드를 시작하는 경우 해당 스레드는 모두 동일한 속성을 공유합니다. 즉, 한 스레드가 속성을 수정하면 나머지 모든 스레드도 변경 사항의 영향을 받습니다.
때로는 동일한 개체로 시작된 다른 스레드와 공유하는 대신 스레드 내에서 단독으로 사용하는 것을 선호합니다. Java 동시성 인터페이스는 스레드 로컬 변수라고 하는 이 요구 사항을 충족하는 매우 명확한 메커니즘을 제공합니다. 이 메커니즘의 성능도 매우 인상적입니다.
알아요
샘플 프로그램을 완료하려면 아래 단계를 따르세요.
1. 먼저 위의 문제점을 가지고 프로그램을 구현해 보세요. UnsafeTask라는 클래스를 만들고 Runnable 인터페이스를 구현합니다. 클래스에서 java.util.Date 유형의 개인 특성을 선언하십시오. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
공개 클래스 UnsafeTask는 Runnable을 구현합니다.
비공개 날짜 startDate;
2. startDate 속성을 인스턴스화하고 해당 값을 콘솔에 출력하는 UnsafeTask의 run() 메서드를 구현합니다. 임의의 시간 동안 휴면 상태를 유지한 다음 startDate 속성 값을 콘솔에 다시 인쇄합니다. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
@보수
공개 무효 실행() {
startDate = 새로운 날짜();
System.out.printf("시작 스레드: %s : %s/n",
Thread.currentThread().getId(), startDate);
노력하다 {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} 잡기(InterruptedException e) {
e.printStackTrace();
}
System.out.printf("스레드 종료: %s : %s/n",
Thread.currentThread().getId(), startDate);
}
3. 문제가 있는 프로그램의 메인 클래스를 구현합니다. main() 메서드인 UnsafeMain을 사용하여 클래스를 만듭니다. main() 메소드에서 UnsafeTask 객체를 생성하고 이 객체를 사용하여 10개의 Thread 객체를 생성하여 10개의 스레드를 시작합니다. 각 스레드 중간에 2초 동안 잠을 자세요. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
공개 클래스 UnsafeMain {
공개 정적 무효 메인(String[] args) {
UnsafeTask 작업 = new UnsafeTask();
for (int i = 0; i < 10; i++) {
스레드 스레드 = 새 스레드(작업);
thread.start();
노력하다 {
TimeUnit.SECONDS.sleep(2);
} 잡기(InterruptedException e) {
e.printStackTrace();
}
}
}
}
4. 위의 논리에서 각 스레드의 시작 시간은 다릅니다. 그러나 아래 출력 로그에 따르면 동일한 시간 값이 많이 있습니다. 다음과 같이:
다음과 같이 코드 코드를 복사합니다.
시작 스레드: 9 : 2013년 9월 29일 일요일 23:31:08 CST
시작 스레드: 10 : 2013년 9월 29일 일요일 23:31:10 CST
시작 스레드: 11 : 2013년 9월 29일 일요일 23:31:12 CST
시작 스레드: 12 : 2013년 9월 29일 일요일 23:31:14 CST
스레드 완료: 9: 9월 29일 일요일 23:31:14 CST 2013
시작 스레드: 13 : 2013년 9월 29일 일요일 23:31:16 CST
스레드 완료: 10 : Sun Sep 29 23:31:16 CST 2013
시작 스레드: 14 : 2013년 9월 29일 일요일 23:31:18 CST
스레드 완료: 11: Sun Sep 29 23:31:18 CST 2013
시작 스레드: 15 : 2013년 9월 29일 일요일 23:31:20 CST
스레드 완료: 12: 일요일 9월 29일 23:31:20 CST 2013
시작 스레드: 16 : 2013년 9월 29일 일요일 23:31:22 CST
시작 스레드: 17: 2013년 9월 29일 일요일 23:31:24 CST
스레드 완료: 17: 9월 29일 일요일 23:31:24 CST 2013
스레드 완료: 15: 2013년 9월 29일 일요일 23:31:24 CST
스레드 완료: 13: 9월 29일 일요일 23:31:24 CST 2013
시작 스레드: 18 : 2013년 9월 29일 일요일 23:31:26 CST
스레드 완료: 14: 9월 29일 일요일 23:31:26 CST 2013
스레드 완료: 18: 2013년 9월 29일 일요일 23:31:26 CST
스레드 완료: 16: 9월 29일 일요일 23:31:26 CST 2013
5. 위에 표시된 대로 이 문제를 해결하기 위해 스레드 로컬 변수 메커니즘을 사용할 것입니다.
6. SafeTask라는 클래스를 생성하고 Runnable 인터페이스를 구현합니다. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
공개 클래스 SafeTask는 Runnable을 구현합니다.
7. ThreadLocal<Date> 유형의 개체를 선언합니다. 개체가 인스턴스화되면initialValue() 메서드가 재정의되고 실제 날짜 값이 이 메서드에 반환됩니다. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
개인 정적 ThreadLocal<Date> startDate = 신규
ThreadLocal<날짜>() {
@보수
보호된 날짜 초기 값() {
새로운 날짜()를 반환합니다;
}
};
8. SafeTask 클래스의 run() 메서드를 구현합니다. 이 메서드는 startDate 속성의 메서드가 약간 조정된 점을 제외하면 UnsafeTask의 run() 메서드와 동일합니다. 코드는 다음과 같습니다:
다음과 같이 코드 코드를 복사합니다.
@보수
공개 무효 실행() {
System.out.printf("시작 스레드: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
노력하다 {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} 잡기(InterruptedException e) {
e.printStackTrace();
}
System.out.printf("스레드 종료: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
}
9. 이 안전한 예제의 메인 클래스는 UnsafeTask를 SafeTask로 수정해야 한다는 점을 제외하면 기본적으로 안전하지 않은 프로그램의 메인 클래스와 동일합니다. 구체적인 코드는 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
공개 클래스 SafeMain {
공개 정적 무효 메인(String[] args) {
SafeTask 작업 = new SafeTask();
for (int i = 0; i < 10; i++) {
스레드 스레드 = 새 스레드(작업);
thread.start();
노력하다 {
TimeUnit.SECONDS.sleep(2);
} 잡기(InterruptedException e) {
e.printStackTrace();
}
}
}
}
10. 프로그램을 실행하고 두 입력 간의 차이점을 분석합니다.
클래스 명명을 표준화하기 위해 본 글에서는 메인 클래스의 명명을 원문과 약간 다르게 하였다. 또한 원본 프로그램과 텍스트 설명이 일치하지 않습니다. 사무적인 오류임에 틀림없습니다.
왜인지 알아
아래는 보안 예제의 실행 결과입니다. 결과에서 각 스레드에는 해당 스레드에 속하는 startDate 속성 값이 있음을 쉽게 알 수 있습니다. 프로그램 입력은 다음과 같습니다.
다음과 같이 코드 코드를 복사합니다.
시작 스레드: 9 : 2013년 9월 29일 일요일 23:52:17 CST
시작 스레드: 10 : Sun Sep 29 23:52:19 CST 2013
시작 스레드: 11 : 2013년 9월 29일 일요일 23:52:21 CST
스레드 완료: 10: Sun Sep 29 23:52:19 CST 2013
시작 스레드: 12 : 2013년 9월 29일 일요일 23:52:23 CST
스레드 완료: 11: Sun Sep 29 23:52:21 CST 2013
시작 스레드: 13 : 2013년 9월 29일 일요일 23:52:25 CST
스레드 완료: 9 : Sun Sep 29 23:52:17 CST 2013
시작 스레드: 14 : 2013년 9월 29일 일요일 23:52:27 CST
시작 스레드: 15 : 2013년 9월 29일 일요일 23:52:29 CST
스레드 완료: 13: 2013년 9월 29일 일요일 23:52:25 CST
시작 스레드: 16 : 2013년 9월 29일 일요일 23:52:31 CST
스레드 완료: 14: 9월 29일 일요일 23:52:27 CST 2013
시작 스레드: 17: 2013년 9월 29일 일요일 23:52:33 CST
스레드 완료: 12: Sun Sep 29 23:52:23 CST 2013
스레드 완료: 16: 9월 29일 일요일 23:52:31 CST 2013
스레드 완료: 15 : Sun Sep 29 23:52:29 CST 2013
시작 스레드: 18 : 2013년 9월 29일 일요일 23:52:35 CST
스레드 완료: 17: 9월 29일 일요일 23:52:33 CST 2013
스레드 완료: 18: 9월 29일 일요일 23:52:35 CST 2013
스레드 지역 변수는 각 스레드의 속성 복사본을 저장합니다. ThreadLocal의 get() 메서드를 사용하여 변수 값을 얻을 수 있고, set() 메서드를 사용하여 변수 값을 설정할 수 있습니다. 스레드 로컬 변수에 처음으로 액세스하고 변수에 아직 값이 할당되지 않은 경우,initialValue() 메서드가 호출되어 각 스레드의 값을 초기화합니다.
끝나지 않는
ThreadLocal 클래스는 이 메서드를 호출하는 스레드에 저장된 지역 변수 값을 삭제하기 위한 Remove() 메서드도 제공합니다.
또한 Java 동시성 API는 하위 스레드가 상속 가능한 모든 스레드 로컬 변수의 초기 값을 수신하여 상위 스레드가 소유한 값을 얻을 수 있도록 하는 InheritableThreadLocal 클래스도 제공합니다. 스레드 A에 스레드 지역 변수가 있는 경우 스레드 A가 스레드 B를 생성하면 스레드 B는 스레드 A와 동일한 스레드 지역 변수를 갖게 됩니다. childValue()를 재정의하여 하위 스레드의 스레드 지역 변수를 초기화할 수도 있습니다. 이 메소드는 상위 스레드에서 매개변수로 전달된 스레드 지역 변수의 값을 허용합니다.
교리를 사용하라
이 글은 "Java 7 Concurrency Cookbook"(D Gua Ge가 "Java7 Concurrency 예제 모음"으로 훔쳤습니다)을 번역한 것이며, 학습 자료로만 사용됩니다. 허가 없이 상업적인 목적으로 사용할 수 없습니다.
작은 성공
다음은 이 섹션의 예제에 포함된 모든 코드의 전체 버전입니다.
UnsafeTask 클래스의 전체 코드:
다음과 같이 코드 코드를 복사합니다.
패키지 com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.Date 가져오기;
import java.util.concurrent.TimeUnit;
/**
* 스레드 안전성을 보장할 수 없는 예
* 날짜: 2013-09-23
* 시간: 23:58
*/
공개 클래스 UnsafeTask는 Runnable을 구현합니다.
비공개 날짜 startDate;
@보수
공개 무효 실행() {
startDate = 새로운 날짜();
System.out.printf("시작 스레드: %s : %s/n",
Thread.currentThread().getId(), startDate);
노력하다 {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} 잡기(InterruptedException e) {
e.printStackTrace();
}
System.out.printf("스레드 종료: %s : %s/n",
Thread.currentThread().getId(), startDate);
}
}
UnsafeMain 클래스의 전체 코드:
다음과 같이 코드 코드를 복사합니다.
패키지 com.diguage.books.concurrencycookbook.chapter1.recipe9;
import java.util.concurrent.TimeUnit;
/**
* 안전하지 않은 스레드 예
* 날짜: 2013-09-24
* 시간: 00:04
*/
공개 클래스 UnsafeMain {
공개 정적 무효 메인(String[] args) {
UnsafeTask 작업 = new UnsafeTask();
for (int i = 0; i < 10; i++) {
스레드 스레드 = 새 스레드(작업);
thread.start();
노력하다 {
TimeUnit.SECONDS.sleep(2);
} 잡기(InterruptedException e) {
e.printStackTrace();
}
}
}
}
SafeTask 클래스의 전체 코드:
다음과 같이 코드 코드를 복사합니다.
패키지 com.diguage.books.concurrencycookbook.chapter1.recipe9;
java.util.Date 가져오기;
import java.util.concurrent.TimeUnit;
/**
* 스레드 안전을 보장하기 위해 스레드 지역 변수를 사용하십시오.
* 날짜: 2013-09-29
* 시간 : 23:34
*/
공개 클래스 SafeTask는 Runnable을 구현합니다.
개인 정적 ThreadLocal<Date> startDate = 신규
ThreadLocal<날짜>() {
@보수
보호된 날짜 초기 값() {
새로운 날짜()를 반환합니다;
}
};
@보수
공개 무효 실행() {
System.out.printf("시작 스레드: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
노력하다 {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} 잡기(InterruptedException e) {
e.printStackTrace();
}
System.out.printf("스레드 종료: %s : %s/n",
Thread.currentThread().getId(), startDate.get());
}
}
SafeMain 클래스의 전체 코드:
다음과 같이 코드 코드를 복사합니다.
패키지 com.diguage.books.concurrencycookbook.chapter1.recipe9;
import java.util.concurrent.TimeUnit;
/**
* 안전한 스레드 예시
* 날짜: 2013-09-24
* 시간: 00:04
*/
공개 클래스 SafeMain {
공개 정적 무효 메인(String[] args) {
SafeTask 작업 = new SafeTask();
for (int i = 0; i < 10; i++) {
스레드 스레드 = 새 스레드(작업);
thread.start();
노력하다 {
TimeUnit.SECONDS.sleep(2);
} 잡기(InterruptedException e) {
e.printStackTrace();
}
}
}
}