Java中的ThreadLocal的使用--学习笔记
ThreadLocal直譯為“線程本地”或“本地線程”,如果你真的這么認為,那就錯了!其實它就是一個容器,用于存放線程的局部變量,我認為應該叫做ThreadLocalVariable(ThreadLocalVariable)才對。
java中的ThreadLocal類允許我們創建只能被同一個線程讀寫的變量。因此,如果一段代碼含有一個ThreadLocal變量的引用,即使兩個線程同時執行這段代碼,它們也無法訪問到對方的ThreadLocal的值。
如何創建ThreadLocal變量
以下代碼展示如何創建一個ThreadLocal變量:
我們可以看到,通過這段代碼實例化一個ThreadLocal對象。我們只需要實例化對象一次,并且也不需要知道它是被哪個線程實例化。雖然所有的線程都能訪問到這個ThreadLocal實例,但是每個線程卻只能訪問到自己通過調用ThreadLocal的set()方法設置的值。即使是兩個不同的線程在同一個ThreadLocal對象設置了不同的值,他們依然無法訪問到對方的值。
如何訪問到ThreadLocal變量
一旦創建了一個ThreadLocal的變量,你可以通過如下代碼設置某個需要保存的值:
可以通過下面方法讀取保存在ThreadLocal變量中的值:
String threadLocalValue = (String)myThreadLocal.get();為ThreadLocal指定泛型類型:
我們可以創建一個指定泛型類型的ThreadLocal對象,這樣我們就不需要每次使用get()方法返回的值做強制類型轉換。
如何初始化ThreadLocal變量的值:
由于在ThreadLocal對象中設置的值只能被設置這個值得線程訪問到,線程無法在ThreadLocal對象使用set()方法保存一個初始值,并且這個初始值能被所有的線程訪問到。
我們可以通過創建一個ThreadLocal的子類并且重寫initialValue()方法,來為一個ThreadLocal對象指定一個初始值:
private ThreadLocal myThreadLocal = new ThreadLocal<String>(){protected String initialValue(){return "this is the initial value";} }一個完整的ThreadLocal的例子:
public class ThreadLocalExample{public static class MyRunnable implements Runnable{private ThreadLocal threadlocal=new ThreadLocal();@Overridepublic void run(){threadlocal.set((int)(Math.random()*1000));try{Thread.sleep(2000);}catch(InterrupedException e){}System.out.println(threadLocal.get());}}public static void main(String[] args){MyRunnable instance=new MyRunnable();Thread thread1=new Thread(instance);Thread thread2=new Thread(instance);thread1.start();thread2.start();} }上面的例子創建了一個MyRunnable實例,并將該實例作為參數傳遞給兩個線程。兩個線程分別執行run()方法,并且都在ThreadLocal實例上保存了不同的值。如果它們訪問的不是ThreadLocal對象并且調用的set()方法被同步了,則第二個線程會覆蓋掉第一個線程設置的值。但是,由于它們訪問的是一個ThreadLocal對象,因此這兩個線程都無法看到對方保存的值。也就是說,它們存取的是兩個不同的值。
再做一個實例:
先定義一個接口:
再做一個線程類:
public class ClientThread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stubfor(int i=0;i<3;i++){System.out.println(Thread.currentThread().getName()+"=>"+sequence.getNumber());}}/*** @param args*/private Sequence sequence;public ClientThread(Sequence sequence){ this.sequence=sequence;}}在線程中連續輸出三次線程名與其對應的序列號。
我們先不用ThreadLocal,來做一個實現類吧。
由于線程啟動的順序是隨機的,所以不是0、1、2這樣的順序,這個好理解。為什么當Thread-0輸出了1、2、3之后,而Thread-2卻輸出了4、5、6呢?線程之間竟然共享了static變量!這就是所謂的“非線程安全”問題了。
那么如何來保證”線程安全“呢?對應于這個案例,就是說不同的線程可擁有自己的static變量,如何實現呢?下面看看另外一個實現吧。
public class SequenceB implements Sequence {private static ThreadLocal<Integer> numberContainer=new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue(){return 0;}};public int getNumber(){numberContainer.set(numberContainer.get()+1);return numberContainer.get();}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubSequence sequence=new SequenceB();ClientThread thread1=new ClientThread(sequence);ClientThread thread2=new ClientThread(sequence);ClientThread thread3=new ClientThread(sequence);thread1.start();thread2.start();thread3.start();}} 運行值 Thread-0=>1 Thread-1=>1 Thread-0=>2 Thread-2=>1 Thread-0=>3 Thread-1=>2 Thread-1=>3 Thread-2=>2 Thread-2=>3每個線程相互獨立了,同樣是 static 變量,對于不同的線程而言,它沒有被共享,而是每個線程各一份,這樣也就保證了線程安全。 也就是說,TheadLocal 為每一個線程提供了一個獨立的副本!
通過ThreadLocal封裝了一個Integer類型的numberContainer靜態成員變量,并且初始值是0。再看getNumber()方法,首先從numberContainer中get出當前的值,加1,隨后set到numberContainer中,最后將numberContainer中的get出當前的值并返回。
搞清楚ThreadLocal的原理之后,有必要總結一下ThreadLocal中的API,其實很簡單。
總結
以上是生活随笔為你收集整理的Java中的ThreadLocal的使用--学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java中的堆和栈
- 下一篇: Java服务CPU飙到99%问题排查