ThreadLocal 무효
첫 번째 솔루션의 대표적인 대표는 Synchonized입니다.두 번째 대표적인 대표는 ThreadLocal입니다.CopyOnWrite는 이 두 방안의 융합이다.
ThreadLocal은 모든 라인의 병렬 접근 데이터에 대한 복사본을 만들고 복사본에 대한 조작을 통해 임계 구역의 오염을 격리합니다.메모리 공간의 소모를 증가시켰지만 라인 동기화가 가져오는 성능 소모를 크게 줄였고 라인 병렬 제어의 복잡도도 감소시켰다.
물론 ThreadLocal은 병발 문제를 완전히 해결할 수 없다. 왜냐하면 임계 구역을 격리하고 임계 구역의 동기화 문제를 버리고 여러 던전의 존재를 초래하기 때문이다.그래서 사용할 때는 장소에 따라 적절하게 사용하고 자신의 논리에 부합해야 한다.
Demo
ThreadLocal의 사용은 매우 간단합니다. 아래와 같이 ThreadLocal의 개인 변수를 설명합니다.그리고 라인이 시작될 때 set () 를 부여하고, 사용할 때 get () 을 추가합니다.
private ThreadLocal local = new ThreadLocal();
public void run() {
local.set(objLocal);
// ......
local.get();
초기 값을 덮어쓰는 방법인 initialValue()를 사용하여 값을 지정할 수도 있습니다.
private ThreadLocal local = new ThreadLocal() {
@Override
protected ObjectinitialValue() {
// ......
return XXX;
}
};
Code
그렇다면 ThreadLocal은 어떻게 현지화 임계구역의 기능을 실현할 수 있습니까?우리 JDK에 가서 끝까지 보자.
온라인 클래스 Thread에서는 맵 속성 threadLocals를 통해 임계 영역의 데이터를 저장합니다.나는 당분간 로컬 데이터 구역이라고 부른다.이 맵의 키는 ThreadLocal 인스턴스이고value는 임계 영역의 데이터 값입니다.
이 ThreadLocalMap은 Map 인터페이스를 실현하지 못했지만, 산열표를 사용하여 데이터를 구성했다.
ThreadLocal.ThreadLocalMap threadLocals = null;
이어서 ThreadLocal 수치get () 을 보았을 때 로컬 데이터 영역 t.threadLocals를 가져오려면 현재 루트 t를 전송해야 합니다.데이터 영역을 가져오면 ThreadLocal 인스턴스를 키로 값을 가져옵니다.
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
마찬가지로 값 set () 도 차이가 많지 않은 논리입니다.t.threadLocals가 없으면 ThreadLocalMap 객체를 새로 만듭니다.
로컬 데이터 영역을 가져오는 것은 현재 루트로 전송되는 것입니다.threadLocals는 구체적인 Thread 대상에 걸려 있기 때문입니다.따라서 set()는 라인이 실행될 때 진행해야 한다. 그렇지 않으면 메인 라인으로 전송되고 메인 라인의 로컬 데이터 구역을 찾을 수 있다.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
마지막으로 ThreadLocal이 로컬 데이터 영역에 값을 부여할 때 Entry를 만들어서 저장합니다. 키는 ThreadLocal 실례이고,value는 복사본 값입니다.여기는value의 값만 부여하고 clone 처리를 하지 않았습니다.
그래서 주의해야 할 것은 여기는 값 복사일 뿐 클론 (깊이 복사) 을 하지 않았다는 것이다.만약 인용이 들어온다면 격리할 수 없다.
static class Entry extendsWeakReference {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
P.S 격리 실패
위의 ThreadLocal 소스는 다음과 같습니다.
1) ThreadLocal이 초기값을 부여할 때 루틴이 실행 중인run()에서 실행되어야 합니다. 그렇지 않으면 ThreadLocalMap이 루틴을 잘못 끊을 수 있습니다.
2) ThreadLocal 격리를 사용하는 값은 인용이 될 수 없습니다. 그렇지 않으면 격리된 것은 인용일 뿐이고 인용이 가리키는 대상은 격리에 실패합니다.
3) 로컬 데이터 영역 ThreadLocalMap은 Thread 객체에 걸려 있기 때문에 스레드 재사용(스레드 풀)이 가져오는 오염에 주의해야 한다.
set 참조
다음 프로그램에서 SHARE를 만듭니다LIST를 임계 구역으로 하고 두 개의 라인을 만들어서 동시에 수정합니다.
각 스레드에 ThreadLocal 속성을 도입하여 SHARE를 로컬화하려면LIST는 다중 스레드 충돌을 격리합니다.
ThreadLocal의 디자인 취지에 따라 각 Thread에서 자신의 로컬 데이터 구역을 만들고 서로 영향을 주지 않아야 한다.그러다 보니 SHARELIST가 오염되어 모든 스레드의 수정이 같은 SHARE에 기록되었습니다LIST.마지막으로 주 라인과 다른 두 라인의 출력 결과는 모두 같다.
여기서 ThreadLocal은 인용 값만 로컬화(격리)했을 뿐 인용의 대상 자체가 로컬화되지 않았기 때문에 이런 현상이 나타났다.
public static ListSHARE_LIST = new CopyOnWriteArrayList();
public static void main(String[]args) throws Exception {
SHARE_LIST.add("Int");
System.out.println("Change before : " + SHARE_LIST);
new MyThread2("Tom").start();
new MyThread2("Jack").start();
System.out.println("Change after :");
Thread.currentThread().sleep(500);
System.out.println(Thread.currentThread() + ":" + SHARE_LIST);
}
static class MyThread2 extends Thread {
privateThreadLocal> local = new ThreadLocal>();
private String name;
public MyThread2(Stringname) {
this.name = name;
}
@Override
public void run() {
local.set(SHARE_LIST);
local.get().add(name); // Change the parameter locally.
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ":" + local.get());
}
}
잘못된 스레드 걸기
위의 코드를 약간 수정하고 ThreadLocal의 초기 값을 루틴 실례를 만드는 구조 함수에 놓으십시오.프로그램을 실행한 후 로컬 데이터 영역을 가져오는 것을 발견합니다.get () 에서 NullPointer Exception을 던졌습니다.
이것은 루틴을 만들 때 주 루틴main에 있기 때문에 이럴 때 set()를 부여하면main의 로컬 데이터 구역에 데이터를 저장합니다.하위 루틴run()이 되면 로컬 데이터 영역 get()을 가져오면 하위 루틴을 가져오기 때문에 바늘을 비웁니다.
public static void main(String[]args) throws InterruptedException {
SHARE_LIST.add("Int");
System.out.println("Change before : " + SHARE_LIST);
new MyThread3("Tom", SHARE_LIST).start();
new MyThread3("Jack", SHARE_LIST).start();
System.out.println("Change after :");
Thread.currentThread().sleep(500);
System.out.println(Thread.currentThread() + ":" + SHARE_LIST);
}
static class MyThread3 extends Thread {
privateThreadLocal> local = new ThreadLocal>();
private String name;
public MyThread3(String name, Listlist) {
this.name = name;
local.set(list);
}
@Override
public void run() {
local.get().add(name); // Changethe parameter locally.
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + ":" + local.get());
}
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
【Java】 STS (Eclipse)에 AdoptOpen JDK 설정· Eclipse를 2020-09로 업데이트하면 jre로 Eclipse를 움직이고 있습니다! 라는 메시지가 나온다. ・메모리 상태의 파악을 위해 MissionControl 넣으려고 하면 JDK로 움직이지 않으면 안 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.