Java併發程式設計與技術內幕:ThreadFactory、ThreadLocal
摘要:本文主要講了ThreadFactory、ThreadLocal的例項和原始碼解析
一、ThreadFactory
1.1 原始碼解讀
ThreadFactory這個故名思義,就是一個執行緒工廠。用來建立執行緒。這裡為什麼要使用執行緒工廠呢?其實就是為了統一在建立執行緒時設定一些引數,如是否守護執行緒。執行緒一些特性等,如優先順序。通過這個TreadFactory創建出來的執行緒能保證有相同的特性。下面來看看它的原始碼吧
它首先是一個介面類,而且方法只有一個。就是建立一個執行緒。
在JDK中,有實現ThreadFactory就只有一個地方。而更多的時候,我們都是繼承它然後自己來寫這個執行緒工廠的。public interface ThreadFactory { Thread newThread(Runnable r); }
下面的程式碼中在類Executors當中。預設的 我們建立執行緒池時使用的就是這個執行緒工廠
static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1);//原子類,執行緒池編號 private final ThreadGroup group;//執行緒組 private final AtomicInteger threadNumber = new AtomicInteger(1);//執行緒數目 private final String namePrefix;//為每個建立的執行緒新增的字首 DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();//取得執行緒組 namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);//真正建立執行緒的地方,設定了執行緒的執行緒組及執行緒名 if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY)//預設是正常優先順序 t.setPriority(Thread.NORM_PRIORITY); return t; } }
在上面的程式碼中,可以看到執行緒池中預設的執行緒工廠實現是很簡單的,它做的事就是統一給執行緒池中的執行緒設定執行緒group、統一的執行緒字首名。以及統一的優先順序。
1.2 應用例項
下面來看看自己寫的一個執行緒工廠
輸出結果:package com.func.axc.threadfactory; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.concurrent.ThreadFactory; /** * 功能概要: * * @author linbingwen * @since 2016年6月18日 */ public class ThreadFactoryTest { static class MyThreadFactory implements ThreadFactory { private int counter; private String name; private List<String> stats; public MyThreadFactory(String name) { counter = 0; this.name = name; stats = new ArrayList<String>(); } @Override public Thread newThread(Runnable run) { Thread t = new Thread(run, name + "-Thread-" + counter); counter++; stats.add(String.format("Created thread %d with name %s on%s\n",t.getId(), t.getName(), new Date())); return t; } public String getStas() { StringBuffer buffer = new StringBuffer(); Iterator<String> it = stats.iterator(); while (it.hasNext()) { buffer.append(it.next()); buffer.append("\n"); } return buffer.toString(); } } static class MyTask implements Runnable { private int num; public MyTask(int num) { this.num = num; } @Override public void run() { System.out.println("Task "+ num+" is running"); try { Thread.sleep(2*10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * @author linbingwen * @since 2016年6月18日 * @param args */ public static void main(String[] args) { System.out.println("main thread beging"); MyThreadFactory factory = new MyThreadFactory("MyThreadFactory"); Thread thread = null; for(int i = 0; i < 10; i++) { thread = factory.newThread(new MyTask(i)); thread.start(); } System.out.printf("Factory stats:\n"); System.out.printf("%s\n",factory.getStas()); System.out.println("main thread end"); } }
這裡通過執行緒工廠統一設定了執行緒字首名,並將建立的執行緒放到一個list當中。
二、ThreadLocal原始碼與應用
2.1 應用例項
其實這個和上面的ThreadFactoy基本沒什麼關聯。ThreadFactory與ThreadGroup還有點關聯。ThreadLocal基本上和這兩個沒什麼聯絡的,但是在高併發場景,如果只考慮執行緒安全而不考慮延遲性、資料共享的話,那麼使用ThreadLocal會是一個非常不錯的選擇。
當使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。
應用例項:
package com.func.axc.threadlocal;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 功能概要:
*
* @author linbingwen
* @since 2016年6月18日
*/
public class ThreadLocalTest {
//建立一個Integer型的執行緒本地變數
static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
static class Task implements Runnable{
private int num;
public Task(int num) {
this.num = num;
}
@Override
public void run() {
//獲取當前執行緒的本地變數,然後累加10次
Integer i = local.get();
while(++i<10);
System.out.println("Task " + num + "local num resutl is " + i);
}
}
static void Test1(){
System.out.println("main thread begin");
ExecutorService executors = Executors.newCachedThreadPool();
for(int i =1;i<=5;i++) {
executors.execute(new Task(i));
}
executors.shutdown();
System.out.println("main thread end");
}
public static void main(String[] args){
Test1();
}
}
輸出結果:可以看到各個執行緒之間的變數是獨門的,不會相影響。
通過上面的一個例項,簡單的瞭解了ThreadLocal的用法,下面再來看下其原始碼實現。
2.2 原始碼解析
首先是其包含的方法:
它的建構函式不做什麼:
public ThreadLocal() {
}
其實主要的也就下面幾個方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
(1)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;
}
}
return setInitialValue();
}
這個上方法就是用來取得變數的副本的,注意到它先取得了當前執行緒物件,接下來使用了getMap返回一個ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
然後可以知道ThreadLocalMap這個竟然是從執行緒中取到的,好,再開啟執行緒類看看
發現Thread類中有這樣一個變數:
ThreadLocal.ThreadLocalMap threadLocals = null;
也變是說每一個執行緒都有自己一個ThreadLocalMap。在我們第一次呼叫get()函式 時,getMap函式返回的是一個null的map.接著就呼叫setInitialValue()
看看setInitialValue,它才是真正去初始化map的地方!
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;
}
其中initialValue這個方法就是我們要重寫的,一般我們在這裡通過一個new 方法返回一個新的變數例項
protected T initialValue() {
return null;
}
因為是第一次呼叫get(),所以getMap後的map還是為null。這時就呼叫到createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
終於建立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);
}
這裡就將Thread和我們的ThreadLocal通過一個map關聯起來。意思是每個Thread中都有一個ThreadLocal.ThreadLocalMap。其中Key為ThreadLocal這個例項,value為每次initialValue()得到的變數!
接下來如果我們第二次呼叫get()函式,這裡就會進入if方法中去!
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();
}
進入If方法中後。就會根據當前的thradLocal例項為Key,取得thread中對應map的vale.其中getEntry方法只是取得我們的key-value對。注意,這時的table其實就是在ThreadLocal例項中都會記錄著每個和它關聯的Thread類中的ThreadLocalMap變數 private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
是取得我們的key-value對之後就可取value了,然後就是返回result.如果這時取不到entry,那麼又會呼叫到setInitalValue()方法,過程又和上面的一樣了。這裡就不說了!(2)set
這個方法就是重新設定每一個執行緒的本地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);
}
這裡取得當前執行緒,然後根據當前的thradLocal例項取得其map。然後重新設定 map.set(this, value);這時這個執行緒的thradLocal裡的變數副本就被重新設定值了!(3)remove
就是清空ThreadLocalMap裡的value,這樣一來。下次再呼叫get時又會呼叫 到initialValue這個方法返回設定的初始值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
總結:
1、每個執行緒都有自己的區域性變數
每個執行緒都有一個獨立於其他執行緒的上下文來儲存這個變數,一個執行緒的本地變數對其他執行緒是不可見的(有前提,後面解釋)
2、獨立於變數的初始化副本
ThreadLocal可以給一個初始值,而每個執行緒都會獲得這個初始化值的一個副本,這樣才能保證不同的執行緒都有一份拷貝。
3、狀態與某一個執行緒相關聯
ThreadLocal 不是用於解決共享變數的問題的,不是為了協調執行緒同步而存在,而是為了方便每個執行緒處理自己的狀態而引入的一個機制,理解這點對正確使用ThreadLocal至關重要。