1. 程式人生 > >Java併發程式設計與技術內幕:ThreadFactory、ThreadLocal

Java併發程式設計與技術內幕:ThreadFactory、ThreadLocal

       摘要:本文主要講了ThreadFactory、ThreadLocal的例項和原始碼解析

一、ThreadFactory

1.1 原始碼解讀

ThreadFactory這個故名思義,就是一個執行緒工廠。用來建立執行緒。這裡為什麼要使用執行緒工廠呢?其實就是為了統一在建立執行緒時設定一些引數,如是否守護執行緒。執行緒一些特性等,如優先順序。通過這個TreadFactory創建出來的執行緒能保證有相同的特性。下面來看看它的原始碼吧

它首先是一個介面類,而且方法只有一個。就是建立一個執行緒。

public interface ThreadFactory {

    Thread newThread(Runnable r);
}
在JDK中,有實現ThreadFactory就只有一個地方。而更多的時候,我們都是繼承它然後自己來寫這個執行緒工廠的。

下面的程式碼中在類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至關重要。