1. 程式人生 > >Java多執行緒學習筆記15之執行緒間通訊

Java多執行緒學習筆記15之執行緒間通訊

詳細程式碼見:github程式碼地址

 

本節內容:

1)ThreadLocal類的使用

    JDK文件及方法翻譯

    InheritableThreadLocal的使用

 

5.類ThreadLocal的使用
變數值的共享可以使用public static變數的形式,所有的執行緒都使用同一個public 
static 變數。但是如何讓每一個執行緒都有自己的共享變數該如何解決呢?JDK中提供了
ThreadLocal類來解決這樣的問題、
ThreadLocal主要作用: 

    每個執行緒繫結自己的值,可以將ThreadLocal類比喻成全域性
存放資料的盒子,盒子中可以儲存每個執行緒的私有資料。類Threadlocal解決的是變數
在不同執行緒間的隔離性,也就是不同執行緒擁有自己的值,不同執行緒中的值是可以放入
ThreadLocal類中進行儲存的。


(1)ThreadLocal類及其方法文件翻譯:

public class ThreadLocal<T>
extends Object
This class provides thread-local variables. These variables differ from 
their normal counterparts in that each thread that accesses one (via 
its get or set method) has its own, independently initialized copy of 
the variable. ThreadLocal instances are typically private static fields 
in classes that wish to associate state with a thread (e.g., a user ID 
or Transaction ID).
For example, the class below generates unique identifiers local to each 
thread. A thread's id is assigned the first time it invokes ThreadId.get() 
and remains unchanged on subsequent calls.
這個類提供執行緒區域性變數。每個執行緒通過get或set方法訪問他自己的變數。獨立的初始化變數
的拷貝。ThreadLocal例項通常是類中的私有的靜態欄位,在希望將狀態與執行緒關聯的類(例如,
使用者ID)
例如,下面的類為每一個執行緒生成唯一的本地識別符號。執行緒id在第一次呼叫ThreadId.get()
時被分配並在隨後的通話中保持不變

 import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     /*
     在這裡需要注意的是:
     final修飾的量視為常量
     當使用final修飾一個變數時,是指其引用的物件不變,而不是引用物件指定的內容。
     也是固定了棧記憶體的引用reference不變,不是修飾堆記憶體的內容。
     */
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }
 上面的程式碼很有實際意義,比如說你要為每個生成的會話生成不重複指定長度和格式的唯一ID,
 就可以參考上面程式碼
 每個執行緒呼叫此類的靜態方法get(),由於事先沒有呼叫set()方法,因此返回的就是initialValue()
 方法返回的值,多次呼叫get()方法返回的也是這個預設值(雖然nextId物件的value欄位屬性值在發生變化
 ,但是threadId包含對應執行緒的拷貝)

 
Each thread holds an implicit reference to its copy of a thread-local 
variable as long as the thread is alive and the ThreadLocal instance 
is accessible; after a thread goes away, all of its copies of thread-local 
instances are subject to garbage collection (unless other references to 
these copies exist).
每個執行緒都有一個對其執行緒本地變數的引用,只要執行緒是存活的,並且ThreadLocal例項可以
訪問;在一個執行緒死亡之後,所有的它的執行緒區域性例項拷貝由垃圾回收處理。除非這些拷貝還有
還有其他的引用存在。


T initialValue():
protected T initialValue​()
Returns the current thread's "initial value" for this thread-local
variable. This method will be invoked the first time a thread 
accesses the variable with the get() method, unless the thread 
previously invoked the set(T) method, in which case the initialValue 
method will not be invoked for the thread. Normally, this method is 
invoked at most once per thread, but it may be invoked again in case 
of subsequent invocations of remove() followed by get().This implementation 
simply returns null; if the programmer desires thread-local variables 
to have an initial value other than null, ThreadLocal must be subclassed, 
and this method overridden. Typically, an anonymous inner class will be used.

Returns:
the initial value for this thread-local
為這個執行緒區域性變數返回當前執行緒的"初始值"。這個方法將在第一個使用get()方法訪問
變數時呼叫,除非這個執行緒之前呼叫過set(T)方法,在這種情況下不會為執行緒呼叫
initialValue方法。通常,該方法在每個執行緒中最多呼叫一次,但在隨後呼叫remove()
和get()時,可能會再次呼叫該方法。這個實現只返回null;如果程式設計師希望執行緒區域性變
量有一個初始值而不是null,ThreadLocal類必須子類化,並且這個方法被重寫。通常,
使用匿名內部類來實現。


set(T value):
public void set​(T value)
Sets the current thread's copy of this thread-local variable to the 
specified value. Most subclasses will have no need to override this 
method, relying solely on the initialValue() method to set the values 
of thread-locals.
Parameters:
value - the value to be stored in the current thread's copy of this 
thread-local.
將當前執行緒的執行緒區域性變數的拷貝設定成特定的值。大多數子類都不需要重寫這個方法。
僅單獨的依賴於initialValue()方法來設定執行緒區域性變數的值


T get():
public T get​()
Returns the value in the current thread's copy of this thread-local 
variable. If the variable has no value for the current thread, it is 
first initialized to the value returned by an invocation of the 
initialValue() method.
Returns:
the current thread's value of this thread-local
返回當前執行緒的關於執行緒本地變數的拷貝值。如果變數沒有當前執行緒的值,它是呼叫
initialValue()方法返回的值。通過initialValue()可以設定預設值。


remove():
public void remove​()
Removes the current thread's value for this thread-local variable. 
If this thread-local variable is subsequently read by the current thread, 
its value will be reinitialized by invoking its initialValue() method, 
unless its value is set by the current thread in the interim. This may 
result in multiple invocations of the initialValue method in the current thread.
刪除當前執行緒的的執行緒本地變數的值。如果這個執行緒區域性變數隨後被當前執行緒讀取,它
的值將通過呼叫它的initialValue()方法重新初始化。除非他的值是當前執行緒在get之前
設定的。這可能導致在當前執行緒中多次呼叫initialValue方法。


(2) 方法get()與null

舉個例子:

package chapter03.section3.thread_3_3_1.project_1_ThreadLocal11;

public class Run {
	public static ThreadLocal<String> t1 = new ThreadLocal<>();
	public static void main(String[] args) {
		if(t1.get() == null) {
			System.out.println("從未放過值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
	}
	
}
/**
result:
從未放過值
我的值
我的值
*/

 

(3) 驗證執行緒變數的隔離性及設定get()返回的預設值
舉例1驗證隔離性:

package chapter03.section3.thread_3_3_2.project_1_ThreadLocalTest;

public class Tools {
	public static ThreadLocal<String> t1 = new ThreadLocal<>();
}


package chapter03.section3.thread_3_3_2.project_1_ThreadLocalTest;

public class ThreadA extends Thread {

	@Override
	public void run() {
		try {
			for(int i = 0; i < 100; i++) {
				if(Tools.t1.get() == null) {
					Tools.t1.set("ThreadA" + (i + 1));
				} else {
					System.out.println("ThreadA get Value=" + Tools.t1.get());
				}
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}


package chapter03.section3.thread_3_3_2.project_1_ThreadLocalTest;

public class ThreadB extends Thread {

	@Override
	public void run() {
		try {
			for (int i = 0; i < 100; i++) {
				if (Tools.t1.get() == null) {
					Tools.t1.set("ThreadB" + (i + 1));
				} else {
					System.out.println("ThreadB get Value=" + Tools.t1.get());
				}
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


package chapter03.section3.thread_3_3_2.project_1_ThreadLocalTest;

public class Run {
	public static void main(String[] args) {
		try {
			ThreadA a = new ThreadA();
			ThreadB b = new ThreadB();
			a.start();
			b.start();
			
			for(int i = 0; i < 100; i++) {
				if(Tools.t1.get() == null) {
					Tools.t1.set("Main" + (i + 1));
				} else {
					System.out.println("Main get Value=" + Tools.t1.get());
				}
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
/*
result:
..........................
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
Main get Value=Main1
ThreadA get Value=ThreadA1
ThreadB get Value=ThreadB1
..........................
*/

結果分析:
可以看到主執行緒、ThreadB、TheadA獲得的值都是他們自己設定的值
,並且只需要設定一次,不呼叫remove()方法,則總會得到相同的值
重新設定值,得到的值就會變更


舉例2改變get()獲得的預設值:

package chapter03.section3.thread_3_3_3.project_1_ThreadLocal22;

public class ThreadLocalExt extends ThreadLocal<String>{
	@Override
	protected String initialValue() {
		return "我是預設值, 第一次get不再為null";
	}
}


package chapter03.section3.thread_3_3_3.project_1_ThreadLocal22;

public class Run {
	public static ThreadLocalExt t1= new ThreadLocalExt();
	public static ThreadLocal<String> t2 = new ThreadLocal<>() {
		@Override 
		protected String initialValue() {
			return "t2";
		}
	};
	
	public static void main(String[] args) {
		if(t1.get() == null) {
			System.out.println("從未放過值");
			t1.set("我的值");
		}
		System.out.println(t1.get());
		System.out.println(t1.get());
		System.out.println(t2.get());
		System.out.println(t2.get());
	}
}
/*
result:
我是預設值, 第一次get不再為null
我是預設值, 第一次get不再為null
t2
t2
*/

結果分析:
我們介紹了兩種方法來設定預設值


舉例3再次驗證子執行緒和父執行緒變數的隔離性

package chapter03.section3.thread_3_3_4.project_1_ThreadLocal33;

import java.util.Date;

public class ThreadLocalExt extends ThreadLocal<Date> {
	@Override
	protected Date initialValue() {
		return new Date();
	}
}


package chapter03.section3.thread_3_3_4.project_1_ThreadLocal33;

import java.util.Date;

public class Tools {
	public static ThreadLocalExt t1 = new ThreadLocalExt();
}


package chapter03.section3.thread_3_3_4.project_1_ThreadLocal33;

public class ThreadA extends Thread {

	@Override
	public void run() {
		try {
			for (int i = 0; i < 3; i++) {
				System.out.println("在ThreadA執行緒中取值=" + Tools.t1.get().getTime());
				Thread.sleep(100);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}


package chapter03.section3.thread_3_3_4.project_1_ThreadLocal33;


public class Run {

	public static void main(String[] args) {
		try {
			for (int i = 0; i < 3; i++) {
				System.out.println("       在Main執行緒中取值=" + Tools.t1.get().getTime());
				Thread.sleep(100);
			}
			Thread.sleep(5000);
			ThreadA a = new ThreadA();
			a.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
/*
result:
       在Main執行緒中取值=1540643528940
       在Main執行緒中取值=1540643528940
       在Main執行緒中取值=1540643528940
在ThreadA執行緒中取值=1540643534260
在ThreadA執行緒中取值=1540643534260
在ThreadA執行緒中取值=1540643534260
*/

 

(4) 類InheritableThreadLocal的使用
使用類InheritableThreadLocal可以在子執行緒中取得父執行緒繼承下來的值

文件翻譯:

public class InheritableThreadLocal<T>
extends ThreadLocal<T>
This class extends ThreadLocal to provide inheritance of values from 
parent thread to child thread: when a child thread is created, the 
child receives initial values for all inheritable thread-local 
variables for which the parent has values. Normally the child's 
values will be identical to the parent's; however, the child's value
can be made an arbitrary function of the parent's by overriding 
the childValue method in this class.
Inheritable thread-local variables are used in preference to ordinary 
thread-local variables when the per-thread-attribute being maintained 
in the variable (e.g., User ID, Transaction ID) must be automatically 
transmitted to any child threads that are created.
這個類繼承了ThreadLocal類,以提供從父執行緒到子執行緒繼承的值: 當建立子執行緒時,
子執行緒接受從父執行緒繼承下來的所有執行緒區域性變數作為初始值。通常子執行緒的值將與
父執行緒相同;然而,子執行緒可以通過覆蓋類中的childValue()方法來設定。

Note: During the creation of a new thread, it is possible to opt out 
of receiving initial values for inheritable thread-local variables.


T childValue(T parentVaue):
protected T childValue​(T parentValue)
Computes the child's initial value for this inheritable thread-local 
variable as a function of the parent's value at the time the child 
thread is created. This method is called from within the parent thread 
before the child is started.
This method merely returns its input argument, and should be overridden 
if a different behavior is desired.

Parameters:
parentValue - the parent thread's value
Returns:
the child thread's initial value

 

舉例1值繼承:

package chapter03.section4.thread_3_4_1.project_1_InheritableThreadLocal1;

import java.util.Date;

public class InheritableThreadLocalExt extends InheritableThreadLocal<Date> {
	@Override
	protected Date initialValue() {
		return new Date();
	}
}


package chapter03.section4.thread_3_4_1.project_1_InheritableThreadLocal1;

public class Tools {
	public static InheritableThreadLocalExt t1 = new InheritableThreadLocalExt();
}


package chapter03.section4.thread_3_4_1.project_1_InheritableThreadLocal1;

public class ThreadA extends Thread {

	@Override
	public void run() {
		try {
			for (int i = 0; i < 3; i++) {
				System.out.println("在ThreadA執行緒中取值=" + Tools.t1.get().getTime());
				Thread.sleep(100);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


package chapter03.section4.thread_3_4_1.project_1_InheritableThreadLocal1;


public class Run {

	public static void main(String[] args) {
		try {
			for (int i = 0; i < 3; i++) {
				System.out.println("       在Main執行緒中取值=" + Tools.t1.get().getTime());
				Thread.sleep(100);
			}
			Thread.sleep(5000);
			ThreadA a = new ThreadA();
			a.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
/*
result:
       在Main執行緒中取值=1540644910398
       在Main執行緒中取值=1540644910398
       在Main執行緒中取值=1540644910398
在ThreadA執行緒中取值=1540644910398
在ThreadA執行緒中取值=1540644910398
在ThreadA執行緒中取值=1540644910398
*/

結果分析:
我們翻譯可以看到繼承父執行緒下來的變數優先,所以此處結果一樣。


舉例2值繼承再修改
如果在繼承的同時還可以對值進行進一步的處理那就更好了
更改類InheritableThreadLocalExt.java

package chapter03.section4.thread_3_4_2.project_1_InheritableThreadLocal2;

import java.util.Date;

public class InheritableThreadLocalExt extends InheritableThreadLocal<Date> {
	@Override
	protected Date initialValue() {
		return new Date();
	}
	
	@Override
	protected Date childValue(Date parentValue) {
		return new Date(); //重新改變時間
	}
}
/*
result:
       在Main執行緒中取值=1540645502906
       在Main執行緒中取值=1540645502906
       在Main執行緒中取值=1540645502906
在ThreadA執行緒中取值=1540645508211
在ThreadA執行緒中取值=1540645508211
在ThreadA執行緒中取值=1540645508211
*/