Java並發06---ThreadLocal類以及應用技巧
上一節總結了一下,線程範圍內的資料共用問題,即定義一個Map,將當前線程名稱和線程中的資料以索引值對的形式存到Map中,然後在當前線程中使用資料的時候就可以根據當前線程名稱從Map中拿到當前線程中的資料,這樣就可以做到不同線程之間資料互不干擾。其實ThreadLocal類就是給我們提供了這個解決方案,所以我們完全可以用ThreadLocal來完成線程範圍內資料的共用。
public class ThreadScopeShareData { //定義一個ThreadLocal private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); public static void main(String[] args) { for(int i = 0; i < 2; i ++) { new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put a data: " + data); threadLocal.set(data);//直接往threadLocal裡面裡面扔資料即可 new TestA().getData(); new TestB().getData(); } }).start(); } } static class TestA { public void getData() { System.out.println("A get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什麼關鍵字,它直接從當前線程中取 } } static class TestB { public void getData() { System.out.println("B get data from " + Thread.currentThread().getName() + ": " + threadLocal.get());//直接取,不用什麼關鍵字,它直接從當前線程中取 } }} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
結合上一節的代碼,可以看出,其實ThreadLocal就相當於一個Map,只不過我們不需要設定key了,它預設就是當前的Thread,往裡面放資料,直接set即可,取資料,直接get即可,很方便,就不用Map一個個存了,但是問題來了,ThreadLocal雖然存取方便,但是get()方法中根本沒有參數,也就是說我們只能往ThreadLocal中放一個資料,多了就不行了,那麼該如何解決這個問題呢。
很明顯,ThreadLocal是個容器,且只能存一下,那麼如果有多個資料,我們可以定義一個類,把資料都封裝到這個類中,然後扔到ThreadLocal中,用的時候取這個類,再從類中去我們想要的資料即可。
好,現在有兩個線程,每個線程都要操作各自的資料,而且資料有兩個:名字和年齡。根據上面的思路,寫一個demo,如下:
public class ThreadScopeShareData { private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); public static void main(String[] args) { for(int i = 0; i < 2; i ++) {//開啟兩個線程 new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put a data: " + data); //每個線程中維護一個User,User中儲存了name和age User user = new User(); user.setName("name" + data); user.setAge(data); threadLocal.set(user); //向當前線程中存入user對象 new TestA().getData(); new TestB().getData(); } }).start(); } } static class TestA { public void getData() { User user = threadLocal.get();//從當前線程中取出user對象 System.out.println("A get data from " + Thread.currentThread().getName() + ": " + user.getName() + "," + user.getAge()); } } static class TestB { public void getData() { User user = threadLocal.get();//從當前線程中取出user對象 System.out.println("B get data from " + Thread.currentThread().getName() + ": " + user.getName() + "," + user.getAge()); } }}//定義一個User類來儲存姓名和年齡class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
這樣進行一下封裝就可以實現多個資料的儲存了,但是上面這個程式是不太好的,原因很明顯,線上程中,我要自己new一個對象,然後對其進行操作,最後還得把這個對象扔到當前線程中。這不太符合設計的思路,設計的思路應該是這樣的,不能讓使用者自己去new啊,如果有個類似於getThreadInstance()的方法,使用者想要從ThreadLocal中拿什麼對象就用該對象去調用這個getThreadInstance()方法多好,這樣拿到的永遠都是本線程範圍內的對象了。
這讓我想到了學習JDBC的時候,從ThreadLocal中拿connection時的做法了,如果當前ThreadLocal中有就拿出來,沒有就產生一個,這跟這裡的需求是一樣的,我想要一個User,那我應該用User去調用getThreadLInstance()方法擷取本線程中的一個User對象,如果有就拿,如果沒有就產生一個。完全一樣的思路。這個設計跟單例的模式有點像,這裡說有點像不是本質上像,是代碼結構很像。先看一下簡單的單例模式代碼結構:
public class Singleton { private static Singleton instance = null; private Singleton() {//私人構造方法阻止外界new } public static synchronized Singleton getInstance() { //提供一個公用方法返回給外界一個單例的執行個體 if (instance == null) { //如果沒有執行個體 instance = new Singleton(); //就新new一個 } return instance; //返回該執行個體 } } 1 2 3 4 5 6 7 8 9 10 11
這是懶漢式單例模式的代碼結構,我門完全可以效仿該思路去設計一個從當前線程中拿User的辦法,所以將程式修改如下:
public class ThreadScopeShareData {//不需要在外面定義threadLocal了,放到User類中了// private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); public static void main(String[] args) { for(int i = 0; i < 2; i ++) { new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() + " has put a data: " + data); //這裡直接用User去調用getThreadLocal這個靜態方法擷取本線程範圍內的一個User對象 //這裡就優雅多了,我完全不用關心如何去拿該線程中的對象,如何把對象放到threadLocal中 //我只要拿就行,而且拿出來的肯定就是當前線程中的對象,原因看下面User類中的設計 User.getThreadInstance().setName("name" + data); User.getThreadInstance().setAge(data); new TestA().getData(); new TestB().getData(); } }).start(); } } static class TestA { public void getData() { //還是調用這個靜態方法拿,因為剛剛已經拿過一次了,threadLocal中已經有了 User user = User.getThreadInstance(); System.out.println("A get data from " + Thread.currentThread().getName() + ": " + user.getName() + "," + user.getAge()); } } static class TestB { public void getData() { User user = User.getThreadInstance(); System.out.println("A get data from " + Thread.currentThread().getName() + ": " + user.getName() + "," + user.getAge()); } }}class User { private User() {} private static ThreadLocal<User> threadLocal = new ThreadLocal<User>(); //注意,這不是單例,每個線程都可以new,所以不用synchronized, //但是每個threadLocal中是單例的,因為有了的話就不會再new了 public static /*synchronized*/ User getThreadInstance() { User instance = threadLocal.get(); //先從當前threadLocal中拿 if(instance == null) { instance = new User(); threadLocal.set(instance);//如果沒有就新new一個放到threadLocal中 } return instance; //向外返回該User } private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) {