今天來看一下Thread和ThreadLocal類的原始碼。
一、Thread
(1)首先看一下執行緒的構造方法,之後會說每種引數的用法,而所有的建構函式都會指向init方法
//空構造建立一個執行緒
Thread()
//傳入Runnable物件建立一個執行緒
Thread(Runnable target)
//傳入Runnable物件和執行緒名建立一個執行緒
Thread(Runnable target, String name)
//傳入執行緒名建立一個執行緒
Thread(String name)
//傳入執行緒組和Runnable物件建立一個執行緒
Thread(ThreadGroup group, Runnable target)
//傳入執行緒組、Runnable物件和執行緒名建立一個執行緒
Thread(ThreadGroup group, Runnable target, String name)
//傳入執行緒組、Runnable物件、執行緒名和棧大小建立一個執行緒
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
//傳入執行緒組和執行緒名建立一個執行緒
Thread(ThreadGroup group, String name)
(2)常用構造
最常用的構造方法是下面這個,其預設有一個生成執行緒名的函式。
Thread(Runnable target)
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
(3)執行緒的初始化方法
/**
* @param g 執行緒組,每個執行緒都會屬於一個執行緒組
* @param target 執行緒會執行Runnable物件的run方法
* @param name 執行緒名
* @param stackSize 棧深度,預設是0
* @param acc 獲取上下文,其實是獲取classloader
* @param inheritThreadLocals 是否從繼承的本地執行緒的值用於構造執行緒
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//父執行緒,比如在Main方法中啟動一個執行緒,就是Main執行緒
Thread parent = currentThread();
g.addUnstarted();
this.group = g;
//是否為守護執行緒,執行緒分為使用者執行緒和守護執行緒,只要還有一個使用者
//執行緒在跑,守護執行緒就不會掛掉。如果沒有使用者執行緒了,守護執行緒
//會和JVM一起退出
this.daemon = parent.isDaemon();
//執行緒優先順序
this.priority = parent.getPriority();
//獲取上下文
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//設定執行緒的棧深度
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
(4)執行緒的start方法
【注意】只可執行一次start方法,不可重複執行
//threadStatus是用volatile修飾的,保證記憶體可見性
private volatile int threadStatus = 0;
public synchronized void start() {
//threadStatus是執行緒狀態,一開始是0,啟動一次之後就是其他值
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
//呼叫本地方法,啟動執行緒
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
啟動兩次的案例:
第一次呼叫start方法:
第二次呼叫start方法:此時就會丟擲IllegalThreadStateException
(5)activeCount()方法
//獲取當前執行緒組存活執行緒數量
public static int activeCount() {
return currentThread().getThreadGroup().activeCount();
}
可以在主執行緒中用來等待其他執行緒完成,例如
//而isInterrupted是判斷執行緒是否已被中斷
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
(6)interrupt、interrupted、isInterruped方法
//interrupt僅僅是將執行緒的標記為中斷,並不會真的中斷執行緒
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
//而isInterrupted是判斷執行緒是否已被中斷
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
當用isInterrupted返回為true的時候,可以用來中斷執行緒
//此方法返回執行緒是否有“中斷”標記,呼叫之後執行緒的“中斷”標記會被去掉
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
下面是中斷執行緒的例子:
Thread a = new Thread(new Runnable() {
@Override
public void run() {
if (Thread.currentThread().isInterrupted()) {
System.out.println("【"+Thread.currentThread().getName()+"】當前執行緒數量"+Thread.activeCount());
System.out.println("執行緒被中斷");
return;
}
while (true) {
}
}
},"a");
a.start();
a.interrupt();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【Main】當前執行緒數量"+Thread.activeCount());
執行結果:
(7)執行緒狀態
public enum State {
//剛建立執行緒還未呼叫start方法
NEW,
//呼叫start方法後,正在執行run方法
RUNNABLE,
//阻塞,等待其他執行緒釋放監視器
BLOCKED,
//等待,等待其他執行緒呼叫notify或notifyAll方法
WAITING,
//等待另一個執行緒執行動作達到指定等待時間的執行緒處於此狀態。
TIMED_WAITING,
//已退出
TERMINATED;
}
二、ThreadLocal
此類裡面可攜帶私有變數,這個變數是私有的,其他執行緒不可見。以下是兩種建立方法並且設定值。
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(new Supplier<Integer>() {
@Override
public Integer get() {
return 1;//設定私有變數值
}
});
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
//設定私有變數值
threadLocal.set(1);
上面是設定值,那如何獲取值呢?
//獲取私有變數值
threadLocal.get()
如何從記憶體中刪除這個ThreadLocal呢?
//將當前ThreadLocal變數從記憶體用刪除
threadLocal.remove();
刪除之後再次獲取就是為null。
(1)從設定值看底層結構
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類,繼續點進去ThreadLocalMap,其結構如下
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private static final int INITIAL_CAPACITY = 16;
//初始值是10
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//計算index
int i = key.threadLocalHashCode & (len-1);
//從下標為i的一直往下,如果不為空,則替換值
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)
//重新hash,裡面有擴容的方法
rehash();
}
private void rehash() {
expungeStaleEntries();
// 當元素數量大於(threshold - threshold / 4),就會進行擴容
//,當threadhold為10的時候就是8,
if (size >= threshold - threshold / 4)
resize();
}
//擴容
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; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//重新設定threadhold的值
setThreshold(newLen);
size = count;
table = newTab;
}
底層是一個Entry陣列,初始化容量是16,擴容是兩倍,那Entry是什麼呢?
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
entry是弱引用的key-value物件,其用ThreadLocal的引用作為key,將私有變數值設定為value。之前的垃圾收集機制一篇說過弱引用物件只能存活到下一次垃圾回收前。但是注意,它被回收只會被回收掉key,也就是ThreadLocal,但是如果value在別的地方引用的話,就不會被回收,這樣會造成記憶體溢位的情況。
例如:
jvm變數設定-Xmx10m -Xms10m
value在list中保持引用,記憶體溢位
value沒在list中保持引用,一直執行
(2)get方法
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;
}
}
//不存在,則先將值設為NULL,然後返回
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;
}
(3)remove方法
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;
}
}
}
//將entry的引用置為null
public void clear() {
this.referent = null;
}
=======================================================
我是Liusy,一個喜歡健身的程式設計師。
歡迎關注【Liusy01】,一起交流Java技術及健身,獲取更多幹貨。