線程內的數據共享與對象獨立,舉例:張三給李四轉錢,開啟A線程去執行轉錢這個動作,剛好同時王五給趙六轉錢,開啟B線程去執行轉錢,因為是調用的同樣一個動作或者說對象,所以如果不能保證線程間的對象獨立,那么很有可能發生,張三給李四轉錢時把王五轉給趙六的轉錢一塊提交了,而王五轉錢整個動作還未完成,那么就造成了轉錢錯誤, 所以線程間一方面要保證數據的共享,另一方面要保證對象的對立.
1.用Map封裝對象以數據實現共享
package com.amos.concurrent; import java.util.HashMap; import java.util.Map; import java.util.Random; /** * @ClassName: ThreadScopeShareData * @Description: 下面的例子用的是Map對象將數據實現共享 * @author: amosli * @email:hi_amos@outlook.com * @date Apr 20, 2014 6:19:02 PM */ public class ThreadScopeShareData { public static Map<Object, Integer> map = new HashMap<Object, Integer>(); public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Runnable() { public void run() { int data = new Random().nextInt();//給data設值, System.out.println(Thread.currentThread().getName() + " set data:" + data); map.put(Thread.currentThread(), data);//將值按照Thread去設值,取的時候也按Thread去取,以保證數據的共享,但又保證了對象的獨立. new A().get(); new B().get(); } }).start(); } } static class A {//這里A和B的方法雖然是一樣的,這里是想表示有可能調用不同的對象去執行數據操作 public int get() { data = map.get(Thread.currentThread()); System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data); return data; } } static class B { public int get() { int data = map.get(Thread.currentThread()); System.out.println("b from thread:" + Thread.currentThread().getName() + " is " + data); return data; } } }
運行效果:
2.使用ThreadLocal實現數據共享
創建ThreadLocal,可以直接new出來,其設值支技泛型,new ThreadLocal<T>,如下將上面代碼改寫:
public class ThreadLocalShareData { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Runnable() { public void run() { int data = new Random().nextInt();//給data設值, System.out.println(Thread.currentThread().getName() + " set data:" + data); threadLocal.set(data);//使用ThreadLocal來設值 new A().get(); new B().get(); } }).start(); } } static class A {//這里A和B的方法雖然是一樣的,這里是想表示有可能調用不同的對象去執行數據操作 public int get() { int data = threadLocal.get(); System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data); return data; } } class B.... ... }
下面是ThreadLocal set(T value)方法的源碼:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
這里同樣是用Map方式的設值,只不過又封裝了一層ThreadLocalMap.
查看其ThreadLocal get()方法的源碼:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
同樣是通過與線程綁定,取值的.
3.實例測試
package com.amos.concurrent; class Account { /* * 定義一個ThreadLocal類型的變量,該變量是一個線程局部變量 */ private ThreadLocal<String> name = new ThreadLocal<String>(); // 定義一個初始化name屬性的構造器 public Account(String str) { this.name.set(str); // 下面的代碼用於訪問當前線程的name副本的值 System.out.println("------" + this.name.get()); } // name的getter,setter方法 public String getName() { return name.get(); } public void setName(String str) { this.name.set(str); } } class MyTest extends Thread { // 定義一個Account屬性 private Account account; public MyTest(Account account, String name) { super(name);// 設置thread的名稱 this.account = account; } @Override public void run() { // 循環 for (int i = 0; i < 10; i++) { if (i == 6) {// 當i=6時,將name名稱更改為當前的線程名 account.setName(getName()); } System.out.println(account.getName() + " 賬戶i的值:" + i); } } } public class ThreadLocalTest { public static void main(String[] args) { Account account = new Account("初始名稱"); // 啟動兩個線程,兩人個線程共享同一個賬戶,即只有一個賬戶名. /* * 雖然丙個線程共享同一個賬戶,即只有一個賬戶名.但由於賬戶名是ThradLocal類型的,所以每個線程都完全擁有各自的賬戶名副本, * 因此在i=6以后,將看到兩人個線程訪問同一個賬戶時出現不同的賬戶名 */ new MyTest(account, "張三").start(); new MyTest(account, "李四").start(); } }
效果如下:
4.關於ThreadLocal的幾點說明
1).ThreadLoca原理:
Thread Local Variable(線程局部變量)的意思,其功能其實非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,使每一個線程都可以獨立地改變自己的副本,而不會和D他線程的副本沖突,從線程的角度來看,就好像每個線程都完全擁有該變量一樣.
2).常用的方法:
>>T get():返回此線程局部變量中當前線程的值.
>>void remove():刪除此線程局部變量中當前線程的值.
>>void set(T value):設置此線程局部變量中當前線程副本中的值.
3).ThradLocal和線程同步機制的區別:
實現機制不同:和線程同步機制一樣,都是為了解決多線程中,對同一變量的訪問沖突,在普通的同步機制中,是通過對象加鎖來實現多個線程對同一個變量的安全訪問的.而ThreadLocal是將需要並發訪問的資源復制多分,每個線程擁有一份資源,每個線程擁有自己的資源副本,從而也變沒有必要對該變量進行同步了.
面向問題的領域不同: ThreadLocal 並不能替代同步機制,同步機制是為了同步多個線程對相同資源的並發訪問,是多個線程之間進行通信的有效方式;而ThradLocal是為了隔離多個線程的數據共享,從根本上避免了多個線程之間對共享資源(變量)的競爭,也就不需要對多個線程進行同步了.
4)何時使用?
如果多個線程之間需要共享資源,以達到線程之間的通信功能,就使用同步機制.
如果僅僅需要隔離多個線程之間的共享沖突,則可以使用ThreadLocal
5.擴展---封裝復雜數據對象

package com.amos.concurrent; import java.util.Random; /** * @ClassName: ThreadLocalShareData * @Description: 下面的例子用的是ThreadLocal對象將數據實現共享,封裝復雜數據對象 * @author: amosli * @email:hi_amos@outlook.com * @date Apr 20, 2014 6:19:02 PM */ public class ThreadLocalShareDataTest { public static void main(String[] args) { for (int i = 0; i < 3; i++) { new Thread(new Runnable() { public void run() { int data = new Random().nextInt();//給data設值, System.out.println(Thread.currentThread().getName() + " set data:" + data); MyThreadData.getMyThreadData().setName("name"+data); MyThreadData.getMyThreadData().setAge(data); new A().get(); new B().get(); } }).start(); } } static class A {//這里A和B中的方法是一樣的,可以只看一個 public void get() { MyThreadData myThreadData = MyThreadData.getMyThreadData(); int data =myThreadData.getAge(); System.out.println("a from thread:" + Thread.currentThread().getName() + " age: " + data+" name:"+myThreadData.getName()); } } static class B{ public void get() { MyThreadData myThreadData = MyThreadData.getMyThreadData(); int data =myThreadData.getAge(); System.out.println("b from thread:" + Thread.currentThread().getName() + " age: " + data+" name:"+myThreadData.getName()); } } //自定義對象 static class MyThreadData { private static ThreadLocal<MyThreadData> mapLocal = new ThreadLocal<MyThreadData>(); private MyThreadData(){} //單例模式,獲取數值 public static MyThreadData getMyThreadData(){ MyThreadData instance = mapLocal.get(); if(instance==null){ instance = new MyThreadData(); mapLocal.set(instance); } return instance; } //name,age setter/getter private String name ; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } } }
上面例子都是封裝基本類型的數據,這里是封裝String name,Integer age,封裝了復雜數據對象.
運行效果: