一, 是什么?怎么用?
是什么?
是每个线程的本地变量,可以存储每个线程独有的变量.
怎么用?
可以为每个线程创建一个独有的变量对象
可以实现线程间的数据隔离
Spring声明式事务中使用ThreadLocal实现数据库隔离
二, 类架构
ThreadLocal属性
private final int threadLocalHashCode
= nextHashCode();
private static AtomicInteger nextHashCode
= new AtomicInteger();
private static final int HASH_INCREMENT
= 0x61c88647;
ThreadLocalMap属性
private static final int INITIAL_CAPACITY
= 16;
private Entry
[] table
;
private int size
= 0;
private int threshold
;
三, 实现原理
1, 为什么ThreadLocal可以实现线程隔离?
public ThreadLocal() {}
public void set(T value
) {
Thread t
= Thread
.currentThread();
ThreadLocalMap map
= getMap(t
);
if (map
!= null
)
map
.set(this, value
);
else
createMap(t
, value
);
}
ThreadLocalMap
getMap(Thread t
) {
return t
.threadLocals
;
}
由上可以看出给ThreadLocal设置值本质上是给Thread的本地变量threadLocals变量设置值,这就是为什么ThreadLocal可以实现线程之间数据隔离
2, 增删查操作
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
);
}
private void set(ThreadLocal
<?> key
, Object value
) {
Entry
[] tab
= table
;
int len
= tab
.length
;
int i
= key
.threadLocalHashCode
& (len
- 1);
for (Entry e
= tab
[i
]; e
!= null
; e
= tab
[i
= nextIndex(i
, len
)]) {
ThreadLocal
<?> k
= e
.get();
if (k
== key
) {
e
.value
= value
;
return;
}
if (k
== null
) {
replaceStaleEntry(key
, value
, i
);
return;
}
}
tab
[i
] = new Entry(key
, value
);
int sz
= ++size
;
if (!cleanSomeSlots(i
, sz
) && sz
>= threshold
)
rehash();
}
private void replaceStaleEntry(ThreadLocal
<?> key
, Object value
, int staleSlot
) {
Entry
[] tab
= table
;
int len
= tab
.length
;
Entry e
;
int slotToExpunge
= staleSlot
;
for (int i
= prevIndex(staleSlot
, len
); (e
= tab
[i
]) != null
; i
= prevIndex(i
, len
))
if (e
.get() == null
)
slotToExpunge
= i
;
for (int i
= nextIndex(staleSlot
, len
); (e
= tab
[i
]) != null
; i
= nextIndex(i
, len
)) {
ThreadLocal
<?> k
= e
.get();
if (k
== key
) {
e
.value
= value
;
tab
[i
] = tab
[staleSlot
];
tab
[staleSlot
] = e
;
if (slotToExpunge
== staleSlot
)
slotToExpunge
= i
;
cleanSomeSlots(expungeStaleEntry(slotToExpunge
), len
);
return;
}
if (k
== null
&& slotToExpunge
== staleSlot
)
slotToExpunge
= i
;
}
tab
[staleSlot
].value
= null
;
tab
[staleSlot
] = new Entry(key
, value
);
if (slotToExpunge
!= staleSlot
)
cleanSomeSlots(expungeStaleEntry(slotToExpunge
), len
);
}
private void rehash() {
expungeStaleEntries();
if (size
>= threshold
- threshold
/ 4)
resize();
}
private void expungeStaleEntries() {
Entry
[] tab
= table
;
int len
= tab
.length
;
for (int j
= 0; j
< len
; j
++) {
Entry e
= tab
[j
];
if (e
!= null
&& e
.get() == null
)
expungeStaleEntry(j
);
}
}
private void resize() {
Entry
[] oldTab
= table
;
int oldLen
= oldTab
.length
;
int newLen
= oldLen
* 2;
Entry
[] newTab
= new Entry[newLen
];
int count
= 0;
for (int j
= 0; j
< oldLen
; ++j
) {
Entry e
= oldTab
[j
];
if (e
!= null
) {
ThreadLocal
<?> k
= e
.get();
if (k
== null
) {
e
.value
= null
;
} else {
int h
= k
.threadLocalHashCode
& (newLen
- 1);
while (newTab
[h
] != null
)
h
= nextIndex(h
, newLen
);
newTab
[h
] = e
;
count
++;
}
}
}
setThreshold(newLen
);
size
= count
;
table
= newTab
;
}
private boolean cleanSomeSlots(int i
, int n
) {
boolean removed
= false;
Entry
[] tab
= table
;
int len
= tab
.length
;
do {
i
= nextIndex(i
, len
);
Entry e
= tab
[i
];
if (e
!= null
&& e
.get() == null
) {
n
= len
;
removed
= true;
i
= expungeStaleEntry(i
);
}
} while ((n
>>>= 1) != 0);
return removed
;
}
private int expungeStaleEntry(int staleSlot
) {
Entry
[] tab
= table
;
int len
= tab
.length
;
tab
[staleSlot
].value
= null
;
tab
[staleSlot
] = null
;
size
--;
Entry e
;
int i
;
for (i
= nextIndex(staleSlot
, len
); (e
= tab
[i
]) != null
; i
= nextIndex(i
, len
)) {
ThreadLocal
<?> k
= e
.get();
if (k
== null
) {
e
.value
= null
;
tab
[i
] = null
;
size
--;
} else {
int h
= k
.threadLocalHashCode
& (len
- 1);
if (h
!= i
) {
tab
[i
] = null
;
while (tab
[h
] != null
)
h
= nextIndex(h
, len
);
tab
[h
] = e
;
}
}
}
return i
;
}
public T
get() {
Thread t
= Thread
.currentThread();
ThreadLocalMap map
= getMap(t
);
if (map
!= null
) {
ThreadLocalMap
.Entry e
= map
.getEntry(this);
if (e
!= null
) {
@SuppressWarnings("unchecked")
T result
= (T
)e
.value
;
return result
;
}
}
return setInitialValue();
}
private T
setInitialValue() {
T value
= initialValue();
Thread t
= Thread
.currentThread();
ThreadLocalMap map
= getMap(t
);
if (map
!= null
)
map
.set(this, value
);
else
createMap(t
, value
);
return value
;
}
protected T
initialValue() {
return null
;
}
public void remove() {
ThreadLocalMap m
= getMap(Thread
.currentThread());
if (m
!= null
)
m
.remove(this);
}
private void remove(ThreadLocal
<?> key
) {
Entry
[] tab
= table
;
int len
= tab
.length
;
int i
= key
.threadLocalHashCode
& (len
- 1);
for (Entry e
= tab
[i
]; e
!= null
; e
= tab
[i
= nextIndex(i
, len
)]) {
if (e
.get() == key
) {
e
.clear();
expungeStaleEntry(i
);
return;
}
}
}
三, 存在问题
1, 内存泄漏问题
因为使用ThreadLocal本质上是使用ThreadLocalMap,在使用完ThreadLocal后,无法手动删除ThreadLocalMap中的key(ThreadLocal的引用),所以可能会引起内存泄漏问题,但是代码在设计的时候就考虑到了这一点,所以将ThreadLocalMap中的key(ThreadLocal)设置为了弱引用(WeakReference),即很容易被GC掉,但即便如此,我们还是要在使用完后调用ThreadLocal的remove方法,手动删除ThreadLocal引用,避免内存泄漏.
2, 子线程不能访问父线程变量
可以使用InheritableThreadLocal
原理
ThreadLocal
<String> local
= new InheritableThreadLocal<>();
local
.set("main local variable");
public void set(T value
) {
Thread t
= Thread
.currentThread();
ThreadLocalMap map
= getMap(t
);
if (map
!= null
)
map
.set(this, value
);
else
createMap(t
, value
);
}
ThreadLocalMap
getMap(Thread t
) {
return t
.inheritableThreadLocals
;
}
Thread thread
= new Thread(() -> {
local
.set("child thread variable");
System
.out
.println("child thread get local variable : " + local
.get());
});
public Thread(Runnable target
) {
init(null
, target
, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g
, Runnable target
, String name
, long stackSize
) {
init(g
, target
, name
, stackSize
, null
, true);
}
private void init(ThreadGroup g
, Runnable target
, String name
,
long stackSize
, AccessControlContext acc
,
boolean inheritThreadLocals
) {
Thread parent
= currentThread();
...
if (inheritThreadLocals
&& parent
.inheritableThreadLocals
!= null
)
this.inheritableThreadLocals
=
ThreadLocal
.createInheritedMap(parent
.inheritableThreadLocals
);
}
static ThreadLocalMap
createInheritedMap(ThreadLocalMap parentMap
) {
return new ThreadLocalMap(parentMap
);
}
private ThreadLocalMap(ThreadLocalMap parentMap
) {
Entry
[] parentTable
= parentMap
.table
;
int len
= parentTable
.length
;
setThreshold(len
);
table
= new Entry[len
];
for (int j
= 0; j
< len
; j
++) {
Entry e
= parentTable
[j
];
if (e
!= null
) {
@SuppressWarnings("unchecked")
ThreadLocal
<Object> key
= (ThreadLocal
<Object>) e
.get();
if (key
!= null
) {
Object value
= key
.childValue(e
.value
);
Entry c
= new Entry(key
, value
);
int h
= key
.threadLocalHashCode
& (len
- 1);
while (table
[h
] != null
)
h
= nextIndex(h
, len
);
table
[h
] = c
;
size
++;
}
}
}
}