최근에 Android Framework 계층 코드를 읽고 ThreadLocal 클래스를 보았습니다. 나는 조금 익숙하지 않았으므로 다양한 관련 블로그를 하나씩 읽었습니다. 그런 다음 소스 코드를 연구하고 내 이해가 이전에 읽은 블로그 게시물과 다르다는 것을 알았으므로 다음과 같은 역할을 수행 할 수 있기를 바라면서 내 이해에 대해 이야기하기 위해 기사를 작성하기로 결정했습니다.
- 연구 결과를 명확하게하고 이해를 심화시킬 수 있습니다.
- 옥을 유치하는 데 중요한 역할을 할 수 있으며 관심있는 학생들이 자신의 아이디어를 정리할 수 있도록 도와줍니다.
- 학습 경험을 공유하고 모든 사람과 의사 소통하고 배우십시오.
1. ThreadLocal이란 무엇입니까?
Threadlocal은 Java.lang 패키지 아래의 Java 클래스 라이브러리의 기본 클래스입니다.
공식 설명은 다음과 같습니다.
스레드-로컬 스토리지, 즉 각 스레드의 자체 값을 갖는 변수를 구현합니다. 모든 스레드는 동일한 ThreadLocal 객체를 공유하지만 각각은 액세스 할 때 다른 값을 보게되며 한 스레드로 변경된 변경 사항은 다른 스레드에 영향을 미치지 않습니다. 구현은 NULL 값을 지원합니다.
일반적인 의미는 다음과 같습니다.
스레드의 로컬 스토리지 메커니즘을 구현할 수 있습니다. ThreadLocal 변수는 스레드에서 다른 값을 가질 수있는 변수입니다. 모든 스레드는 동일한 ThreadLocal 객체를 공유 할 수 있지만 액세스 할 때 다른 스레드가 다른 값을 얻을 수 있으며 스레드의 변경 사항은 다른 스레드에 영향을 미치지 않습니다. 클래스 구현은 NULL 값을 지원합니다 (NULL 값은 세트 및 GET 메소드에서 전달 및 액세스 할 수 있습니다).
요약하면 세 가지 특성이 있습니다.
- 다른 스레드로 액세스 할 때 다른 값을 얻습니다
- 스레드가 변경되면 다른 스레드에는 영향을 미치지 않습니다.
- NULL 지원
다음은 이러한 기능의 예입니다. 먼저 테스트 클래스를 정의합니다. 이 수업에서는 위에서 언급 한 세 가지 기능을 확인합니다. 클래스 정의는 다음과 같습니다.
test.java
공개 클래스 테스트 {// stroodlocal private static streldlocal name 정의; public static void main (string [] args)은 예외 {name = new ThreadLocal (); // 스레드 정의 a = new Thread () {public void run () {system.out.println ( "value set, value :"+name.get ()); a "); system.out.println ("호출 후, 값은 다음과 같습니다. : "+name.get ());}}; // set를 호출하지 않고, 값을 인쇄하고, valuename.set ("스레드 main ")를 채우기 위해 세트를 호출하십시오. 스레드 bb.start (); b.join (); // 스레드 bsystem.out.println (name.get ())}}별로 변경된 후 값을 인쇄합니다.코드 분석 :
정의에서 우리는 하나의 ThreadLocal 객체 만 선언되고 다른 세 스레드 (기본 스레드, 스레드 A 및 스레드 B)가 동일한 객체를 공유한다는 것을 알 수 있습니다. 그런 다음 객체의 값이 다른 스레드에서 수정되고 객체의 값이 다른 스레드에서 액세스되고 결과는 콘솔에서 출력됩니다.
결과보기 :
콘솔 출력 결과에서 3 개의 널 출력이 있음을 알 수 있습니다. 출력 전에 객체가 할당되지 않았기 때문에 NULL지지 기능을 확인했습니다. 또한 각 스레드에서 객체의 값을 수정했지만 다른 스레드가 객체에 액세스 할 때 수정 된 값이 아니라 스레드-로컬 값이라는 것을 알 수 있습니다. 이것은 또한 다른 두 가지 기능을 확인합니다.
2. ThreadLocal의 역할
사용 시나리오는 대부분 다중 스레드 프로그래밍이라는 것을 모두 알고 있습니다. 특정 기능에 관해서는 어떻게 그렇게 말합니까? 사물의 기능적 속성은 정의 된 후에 모든 사람의 생각을 제한하기 때문에 이것이 일반적인 방식으로 만 정의 될 수 있다고 생각합니다. 예를 들어, 주방 나이프는 야채를 자르는 데 사용되며 많은 사람들이 수박을 자르기 위해 사용하지 않습니다.
여기서는 참조 기능에 대한 내 이해에 대해서만 이야기하고 도움이되기를 바랍니다. 이런 식으로 설명합시다. 멀티 스레드 프로그램이 대부분의 스레드의 일부 작업 (즉, 실행 메소드의 일부 코드)의 일부 작업을 캡슐화 해야하는 경우 Threadlocal을 사용하여 캡슐화 본문의 스레드 관련 멤버 변수를 래프트하여 스레드 액세스의 독점 성을 보장 할 수 있으며 모든 스레드는 캡슐화 객체를 공유 할 수 있습니다. 안드로이드에서 루퍼를 참조 할 수 있습니다. 코드 문제를 설명 할 수없는 프로그래머는 좋은 프로그래머가 아닙니다.
코드를보십시오 : 스레드의 시간이 많이 걸리는 코드를 명시하기위한 도구 (문제를 설명하기 위해 생성)
StatisticCoscosttime.java
// 클래스 통계 통계 비용 timepublic class statisticcoscosttime {// starttime을 녹음 // private strook starttime = new ThreadLocal (); private long starttime; // private strandlocal costtime = new ThreadLocal (); 개인 Long Costtime; privatecosttime () {} // SingletonPublic statisticinstance () {{) instancefactory.instance;} private static class instancefactory {private static final statisticcoscosttime instance = new StatisticCoscosttime ();} // startPublic void start () {// starttime.set (system. nanotime ()); startTime = System.NanOtime ();} // endPublic void end () {// systime.set (system.). starttime.get ()); costtime = system.nanoTime () - startTime;} public long getStartTime () {return startTime; // return startTime.get ();} public long getCostTime () {// return costtime.get (); return costtime;}자, 도구 설계가 완료되었으므로 이제 시간이 소요되는 스레드를 계산하고 시도하는 데 사용합니다.
Main.java
public class main {public static void main (string [] args)은 예외를 던집니다 {// 스레드 정의 athread a = new Thread () {public void run () {try {// record timestatisticcoscosttime.shareInstance (); start (200); // System.out.println ( "a-startTime :"+statisticCosttime.shareInstance (). getStartTime ()); // recordStatisticCostTime.shareInstance (). end (); // system.out.println ( "a :"+statisticccosttime.shareInStance (); getCostTime ();); e) {}}}; // start aa.start (); // 스레드 정의 bthread b = new Thread () {public void run () {try {// b1statisticCostTime.shareInstance (); sleep (100); // 인쇄 시간을 인쇄합니다. consolesystem.out.println ( "b1- 스타트 타임 :"+statisticCosttime.shareInstance (). getStartTime ()); // end record of b1statisticCosttime.shareInstance ()의 end (); // 비용 시간을 인쇄합니다. b1system.out.println ( "b1 :"+statisticcoscosttime.shareinstance (). getCosttime ()); // b2StatisticCostTime.shareInstance (); start (); sleep (100); // 인쇄 시작 시간 b2system.out.println ( "b2-starttime :"+statisticCosttime.shareInstance (). getStartTime ()); // b2StatisticCostTime.shareInstance ()의 end (); // 인쇄 비용 시간 b2system.out.println ( "b2 :"+statisticcoscosttime.shareinstance (). getCosttime ());} catch (예외 e) {}}}; b.start ();}}코드를 실행 한 후 출력 결과는 다음과 같습니다. 출력 결과의 정확도는 나노 초입니다.
결과가 우리가 예상 한 것과 다른지 여부에 따라 다릅니다. A의 결과는 B1+B2와 거의 같아야한다는 것을 알았습니다. 어떻게 B2와 동일하게 되나요? 답은 시작 시간 및 원가 계약 변수를 정의 할 때 원래 의도를 공유해서는 안되지만 스레드에만 배타적이어야한다는 것입니다. 여기서 변수는 싱글 톤과 공유되므로 A의 값을 계산할 때 시작 시간은 실제로 B2로 수정되었으므로 B2와 동일한 결과가 출력됩니다.
이제 StatisticCoscosttime에서 주석을 달린 부분을 열고 ThreadLocal의 선언 방법으로 변경하여 시도해 봅시다.
결과보기 :
아! 이것은 예상 효과를 달성했습니다. 현재 일부 학생들은 이것이 스레드-코커 렌트 액세스가 아니라고 말하며 ThreadLocal을 사용하는 한 스레드 안전을 보장 할 수 있습니까? 대답은 아니오입니다! 우선 스레드 안전 문제가있는 이유를 알아낼 수 있지만 두 가지 상황 만 있습니다.
1. 당신은 스레드간에 공유해서는 안되는 자원을 공유했습니다.
2. 당신은 스레드간에 공유되는 리소스에 대한 질서있는 액세스를 보장하지 않습니다.
전자는 Threadlocal (스레드 로컬 변수를 직접 선언 할 수 있음)을 사용하여 "공간 교환 시간"방법으로 해결 될 수 있으며, 후자는 "공간 교환 시간"방법으로 해결 될 수 있으며, 이는 ResdleLocal이 할 수있는 일이 아닙니다.
3. Threadlocal 원칙
구현 원리는 실제로 매우 간단합니다. Threadlocal 객체에 대한 읽기 및 쓰기 작업이 실제로 읽기 및 쓰기 작업은 스레드의 값 객체에 대한 작업입니다. 여기서는 변수에 의해 할당 된 메모리 공간이 t 객체를 저장하는 데 사용되지 않지만 스레드 값은 t 객체를 저장하는 데 사용되기 때문에 변수의 사본을 작성하지 않음을 분명히합니다. 스레드에서 threadLocal Set 메소드를 호출 할 때마다 실제로는 객체를 스레드의 해당 값 객체에 쓰는 프로세스입니다. ThreadLocal get 메소드를 호출 할 때 실제로는 스레드의 해당 값 객체에서 객체를 가져 오는 프로세스입니다.
소스 코드를 참조하십시오 :
ThreadLocal 멤버 변수 세트
/*** 현재 스레드에 대한이 변수의 값을 설정합니다. * {@code null}으로 설정하면 값이 null로 설정되고 기본 항목이 여전히 존재합니다. * * @param 값 값 발신자 스레드의 변수의 새 값입니다. */public void set (t value) {스레드 currentthread = thread.currentthread (); 값 값 = 값 (currentthread); if (values == null) {value = initializevalues (currentthread); } value.put (this, value);}트레드 로컬 멤버 메소드를 얻습니다
/*** 현재 스레드에 대한이 변수의 값을 반환합니다. 이 스레드 에서이 변수에 대한 항목 *이 아직 존재하지 않으면이 메소드는 * {@link #initialValue ()}의 결과로 값을 채우고 항목을 생성합니다. * * @return 호출 스레드에 대한 변수의 현재 값. */@suppresswarnings ( "확인되지 않은") public t get () {// 빠른 경로에 최적화되었습니다. 스레드 currentthread = thread.currentthread (); 값 값 = 값 (currentthread); if (values! = null) {object [] table = values.table; int index = hash & values.mask; if (this.reference == 테이블 [index]) {return (t) 테이블 [index + 1]; }} else {value = initializeValues (currentthread); } return (t) value.getAftermiss (this);}ThreadLocal Member Method InstentizeValues입니다
/***이 스레드와 가변 유형의 값 인스턴스를 만듭니다. */value initializeValues (스레드 current) {return current.localValues = new values ();}ThreadLocal 멤버 메소드 값
/***이 스레드의 값 인스턴스와 변수 유형을 가져옵니다. */값 값 (스레드 current) {return current.localValues;}그렇다면이 값에서 객체를 어떻게 읽고 쓰나요?
값은 내부 클래스의 threadlocal로 존재합니다. 이 값에는 중요한 배열 객체 []가 포함되어 있는데, 이는 질문에 답하는 것의 핵심 부분입니다. 스레드에 다양한 유형의 트레드 로컬 변수를 저장하는 데 사용됩니다. 문제는 특정 유형의 변수를 취할 때 다른 유형의 값을 얻지 못하도록 어떻게해야합니까? 일반적으로 맵은 키 값에 따라 매핑됩니다. 예, 아이디어는이 아이디어이지만 여기에는지도와 함께 구현되지 않았으며 객체로 구현 된 맵 메커니즘입니다 []; 그러나지도를 사용하여 이해하려면 메커니즘이 동일하기 때문에 가능하지 않습니다. 키는 실제로 ThreadLocal의 약한 참조에 해당하며 값은 우리가 통과 한 객체에 해당합니다.
객체 []를 사용하여 맵 메커니즘을 구현하는 방법을 설명해 봅시다 (그림 1 참조). 키와 값을 구별하기 위해 배열 위시의 패리티를 사용합니다. 즉, 아래 표는 키를 짝수 위치에 저장하고 홀수는 값을 저장합니다. 이것이 어떻게 수행되는지입니다. 관심있는 학생들이 알고리즘 구현을 알고 싶다면 깊이 연구 할 수 있습니다. 여기에서 자세히 설명하지는 않습니다.
이전 첫 번째 예를 바탕으로 저장 상황이 분석됩니다.
프로그램이 실행되면 3 개의 스레드 A, B 및 메인이 있습니다. Name.set ()가 스레드에서 호출되면 3 개의 스레드 인스턴스에 대해 3 개의 스레드 인스턴스에 대해 3 개의 동일한 메모리 공간이 값 객체를 키로 사용하여 값을 저장하고 특정 객체는 세 가지 객체 []에서 값으로 저장됩니다 (아래 그림 참조).
4. 요약
ThreadLocal은 멀티 스레드 프로그래밍 중에 동시성 문제를 완전히 해결할 수 없습니다. 이 문제는 또한 다른 상황, "시간을위한 공간"또는 "공간 시간"에 따라 선택할 수있는 다양한 솔루션이 필요합니다.
ThreadLocal의 가장 큰 기능은 스레드 공유 변수를 스레드-로컬 변수로 변환하여 스레드 간의 격리를 달성하는 것입니다.
위의 모든 것은 Java의 ThreadLocal을 빠르게 이해하는 것입니다. 모든 사람에게 도움이되기를 바랍니다. 단점이 있으면 메시지를 남겨 두십시오. 이 사이트를 지원해 주신 친구들에게 감사드립니다.