ThreadLocal和無鎖
執行緒安全
執行緒安全,一般指的是某個物件,變數在進行多執行緒操作的時候,得到的結果與期望的結果一致。當我們使用執行緒去訪問一個物件時,由於物件內部的某些操作原因,而導致的執行緒的最終結果不符合期望,就是執行緒不安全,例如:ArrayList是一個執行緒不安全的容器,如果在多執行緒中使用ArrayList,程式有可能出現隱藏的錯誤。例如:
package com.dong.testThread; import java.util.ArrayList; /** * thread1,thread2執行緒向ArrayList容器中共新增200,0個容器,得到的結果應該是20000,但是執行結果卻不是20000: * 有三種結果: * ⑴丟擲異常:Thread-0" java.lang.ArrayIndexOutOfBoundsException: * ArrayList在擴容的過程中,內部一致性被破壞,由於沒有鎖的保護,另外一個執行緒訪問到了不一致的內部狀態,導致出現越界問題 * ⑵不是期望的值,但也沒報錯 * 由於多執行緒訪問衝突,是的儲存容器大小的變數被多執行緒不正常訪問,同時兩個執行緒同時對ArrayList中的同一個位置進行賦值導致的 * ⑶正常結果: 20000 * * @author liuD * */ public class TestArrayList { static ArrayList<Integer> arrayList = new ArrayList<Integer>(66); public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new AddThread()); Thread thread2 = new Thread(new AddThread()); thread1.start();thread2.start(); thread1.join();thread2.join(); System.out.println(ar.size()); } public static class AddThread implements Runnable{ public void run() { for(int i = 0 ;i<10000;i++) { arrayList.add(i); } } } }
ThreadLocal:執行緒的區域性變數,即只有當前執行緒可以訪問這個物件,因此每個執行緒都有自己的ThreadLocal變數,當執行緒中有執行緒不安全的物件,操作時,可使用ThreadLocal來修飾該例項,變數,讓每個執行緒都擁有一個該例項,變數,這樣就不會因為競爭同一個資源而導致的執行緒不安全。
ThreadLocal的實現原理:
ThreadLocal的set()方法和get()方法:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //獲取執行緒的ThreadLocalMap,可以理解為一個hashMap if (map != null) map.set(this, value); else createMap(t, value); } getMap(t) -----》 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } t.threadLocals-----》 ThreadLocal.ThreadLocalMap threadLocals = null; map.set(this, value) -----》 private void set(ThreadLocal<?> key, Object value) { //key為ThreadLocal當前物件, value是需要的值 Entry[] tab = table; //Entry陣列物件,用於儲存map物件的陣列 int len = tab.length; int i = key.threadLocalHashCode & (len-1); //利用key的雜湊和陣列長度,來確定當前ThreadLocal在entry[]中的下標 for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //根據雜湊值獲取Entry物件 if (k == key) { //從entry陣列中獲取下標為i(i為當前ThreadLocal的雜湊值)的ThreadLocal,即當前的ThreadLocal,如果 k == 當前ThreadLocal; e.value = value; //將物件的新值賦給當前ThreadLocal物件 return; } if (k == null) { //如果 == null ,則替換原來的物件; replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } createMap(t, value) -----》 void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
get()方法比較簡單:
public T get() { Thread t = Thread.currentThread(); //獲取當前執行緒 ThreadLocalMap map = getMap(t); //獲取當前執行緒的ThreadLocalMap物件 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //如果不為null,則獲取Entry物件 if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; //獲取其ThreadLocal對應的值 return result; } } return setInitialValue(); }
ThreadLocal變數都是在其內部,如果執行緒不終止,就不會被回收,所以如果想要回收,可以使用ThreadLocal.remove()方法將變數再其內部移除;
總結:ThreadLocal的實現原理是將例項儲存在一個數組中,陣列的元素是一個個entry物件,陣列的下標是ThreadLocal物件雜湊和陣列長度的與運算,因此,可以確保每個執行緒的ThreadLocal變數都是獨立的。
無鎖
前面我們介紹了CAS比較交換技術,可以實現不用鎖機制就能保證安全的併發,接下來介紹無鎖的一些類:
AtomicInteger: package java.util.concurrent.atomic;
private volatile int value; //AtomicInteger的value值,被volatile修飾,執行緒之間對value的修改可知
public final int get() {//注意返回的是一個final值,證明返回值不可被修改,這也是防止多執行緒導致資料不一致的一種手段
return value;
}
public final void set(int newValue) { //同上
value = newValue;
}
public final int getAndSet(int newValue) {//設定新值返回舊值
return U.getAndSetInt(this, VALUE, newValue);
}
public final int getAndIncrement() {//讓value值增1,因為++不是執行緒安全的,同時AtomicInteger不使用鎖
return U.getAndAddInt(this, VALUE, 1);
}
//注意:在AtomicInteger中,有關的++,--,等賦值操作被方法替代,同時,value值為私有的,即只能使用公共方法獲取
無鎖的物件引用:AtomicReference
AtomicInteger是對整數的封裝,AtomicReference是對普通物件引用,保證你在修改物件引用時的執行緒安全。
AtomicReference的一個缺點是:使用比較交換技術,執行緒來判斷一個物件是否可以被修改,如果當前值和期望值一致,則執行緒可以寫入新值,如果當前值和期望值不一致,則證明有其他執行緒修改了值,故不寫入新值,但是如果其他執行緒修改多次,又恰好修改到當前值的期望值,這個時候,也符合寫入新值的條件,但是物件是否修改我們無法界定,對於一般問題,這個無所謂,例如我們做運算,不影響,但是對於業務,我們需要提防。因為做運算我們關注的是結果,但是對於業務,比較次數也有可能考慮在內。
Java引入AtomicStampedReference,即帶時間戳的物件引用,即不僅更新值,還得更新時間 戳,只有都滿足,才會修改資料;
普通變數也可以原子操作:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater分別可以對int,long,普通物件進行CAS修改,
什麼時候使用: 當代碼都完成差不多,其中存在普通變數導致的執行緒不安全操作,為了做到最少的修改,可以使用工具類AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater來實現原子操作。
package com.dong.testThread;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 使用AtomicIntegerFieldUpdater 來說普通變數具有原子操作
*
* @author liuD
*/
public class TestAtomicIntegerFieldUpdater {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<att> aifu =
AtomicIntegerFieldUpdater.newUpdater(att.class,"vote");
final att updateobj = new att();
int newvalue= aifu.incrementAndGet(updateobj);
System.out.println(newvalue);
}
}
class att{
volatile int vote = 1 ; //注意這裡不可以是static修飾;
}
其他操作同理
最後:內容來自《Java高併發程式設計》 作者葛一鳴 郭超 由衷感謝作者為我們提供書籍內容;