在這裏插入圖片描述java
在這裏插入圖片描述web
ThreadLocal提升一個線程的局部變量,訪問某個線程擁有本身局部變量。咱們經常使用使用ThreadLocal 用來存儲用戶信息,可是發現ThreadLocal 有時獲取到的用戶信息是別人的,數據庫
咱們知道,ThreadLocal適用於變量在線程間隔離,而在方法或類間共享的場景。若是用戶信息的獲取比較昂貴(好比從數據庫查詢用戶信息),那麼在 ThreadLocal中緩存數據是比較合適的作法。但,這麼作爲何會出現用戶信息錯亂的 Bug ?編程
private ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
@ApiOperation(value = "test2")
@GetMapping("/test2")
public ResponseMessage test2(@ApiParam(value = "id")@RequestParam(required = false) Integer id) {
//設置用戶信息以前先查詢一次ThreadLocal中的用戶信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
currentUser.set(id);
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//彙總輸出兩次查詢結果
Map result = new HashMap();
result.put("before", before);
result.put("after", after);
return ResultBody.success(result);
在這裏插入圖片描述數組
在設置用戶信息以前第一次獲取的值始終應該是 null,但咱們要意識到,程序運行在 Tomcat 中,執行程序的線程是 Tomcat 的工做線程,而 Tomcat 的工做線程是基於線程池的。緩存
由於線程的建立比較昂貴,因此web服務器每每會使用線程池來處理請求,就意味着線程會被重用。這是,使用相似ThreadLocal工具來存放一些數據時,須要特別注意在代碼運行完後,顯式的去清空設置的睡覺。若是在代碼中使用來自定義線程池,也一樣會遇到這樣的問題安全
@ApiOperation(value = "test2")
@GetMapping("/test2")
public ResponseMessage test2(@ApiParam(value = "id")@RequestParam(required = false) Integer id) {
//設置用戶信息以前先查詢一次ThreadLocal中的用戶信息
String before = Thread.currentThread().getName() + ":" + currentUser.get();
currentUser.set(id);
Map result = new HashMap();
try {
String after = Thread.currentThread().getName() + ":" + currentUser.get();
//彙總輸出兩次查詢結果
result.put("before", before);
result.put("after", after);
}finally {
//在finally代碼塊中刪除ThreadLocal中的數據,確保數據不串
currentUser.remove();
}
return ResultBody.success(result);
}
JDK 1.5 後推出的 ConcurrentHashMap,是一個高性能的線程安全的哈希表容器。「線程安全」這四個字特別容易讓人誤解,由於 ConcurrentHashMap 只能保證提供的原子性讀寫操做是線程安全的。服務器
public class Test {
private ConcurrentHashMap<String, Integer> map=new ConcurrentHashMap<String, Integer>();
public static void main(String[] args) {
final Test t=new Test();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
t.add("key");
}
}).start();
}
}
public void add(String key){
Integer value=map.get(key);
if(value==null)
map.put(key, 1);
else
map.put(key, value+1);
System.out.println(map.get(key));
}
}
在這裏插入圖片描述多線程
解決:併發
public class Test {
private ConcurrentHashMap<String, Integer> map=new ConcurrentHashMap<String, Integer>();
public static void main(String[] args) {
final Test t=new Test();
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
t.add("key");
}
}).start();
}
}
public synchronized void add(String key){
Integer value=map.get(key);
if(value==null)
map.put(key, 1);
else
map.put(key, value+1);
System.out.println(map.get(key));
}
}
若是隻是調用put或者get方法,ConcurrentHashMap是線程安全的,可是若是調用了get後在調用map.put(key, value+1)以前有另外的線程去調用了put,而後你再去執行put,就有可能將結果覆蓋掉,但這個其實也不能算ConcurrentHashMap線程不安全,ConcurrentHashMap內部操做是線程安全的,可是外部操做仍是要靠本身來保證同步,即便在線程安全的狀況下,也是可能違反原子操做規則。。。
除了 ConcurrentHashMap 這樣通用的併發工具類以外,咱們的工具包中還有些針對特殊場景實現的生面孔。通常來講,針對通用場景的通用解決方案,在全部場景下性能都還能夠,屬於「萬金油」;而針對特殊場景的特殊實現,會有比通用解決方案更高的性能,但必定要在它針對的場景下使用,不然可能會產生性能問題甚至是 Bug。
CopyOnWrite 是一個時髦的技術,不論是 Linux 仍是 Redis 都會用到。在 Java 中, CopyOnWriteArrayList 雖然是一個線程安全的 ArrayList,但由於其實現方式是,每次 修改數據時都會複製一份數據出來,因此有明顯的適用場景,即讀多寫少或者說但願無鎖讀的場景。
測試寫的性能
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> cowal = new CopyOnWriteArrayList<Integer>();
ArrayList<Integer> list = new ArrayList<Integer>();
int count = 500;
long time1 = System.currentTimeMillis();
while (System.currentTimeMillis() - time1 < count) {
cowal.add(1);
}
time1 = System.currentTimeMillis();
while (System.currentTimeMillis() - time1 < count) {
list.add(1);
}
System.out.println("CopyOnWriteArrayList在" + count + "毫秒時間內添加元素個數爲: "
+ cowal.size());
System.out.println("ArrayList在" + count + "毫秒時間內添加元素個數爲: "
+ list.size());
}
在這裏插入圖片描述
讀性能比較
public static void main(String[] args) throws InterruptedException {
// create object of CopyOnWriteArrayList
List<Integer> ArrLis = new ArrayList<>();
List<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
System.gc();
for (int i = 0; i < 100000; i++) {
ArrLis.add(i);
}
for (int i = 0; i < 100000; i++) {
copyOnWriteArrayList.add(i);
}
Thread.sleep(500);
long startTime = System.currentTimeMillis(); //獲取開始時間
// print CopyOnWriteArrayList
System.out.println("ArrayList: "
+ ArrLis);
// 2nd index in the arraylist
System.out.println(" index: "
+ ArrLis.get(5000));
long endTime = System.currentTimeMillis(); //獲取結束時間
System.out.println(" ArrayList : 程序運行時間:" + (endTime - startTime) + "ms"); //輸出程序運行時間
Thread.sleep(500);
long startTime2 = System.currentTimeMillis(); //獲取開始時間
// print CopyOnWriteArrayList
System.out.println("copyOnWriteArrayList: "
+ copyOnWriteArrayList);
// 2nd index in the arraylist
System.out.println(" index: "
+ copyOnWriteArrayList.get(5000));
long endTime2 = System.currentTimeMillis(); //獲取結束時間
System.out.println(" copyOnWriteArrayList : 程序運行時間:" + (endTime2 - startTime2) + "ms"); //輸出程序運行時間
System.gc();
}
在這裏插入圖片描述