Java併發編程---ThreadLocal

1、什麼是ThreadLocal?

多線程共享變量的維護是很是頭痛的問題,採用樂觀悲觀策略,悲觀策略簡單地作法咱們能夠對共享變量加鎖實現,可是鎖的開銷是比較大的,所以咱們也能夠經過樂觀策略,採用相似CAS(Compare And Set)的方法進行維護,固然,在讀多寫少的狀況下,咱們還能夠採用Copy-On-Write寫時複製的來控制共享變量,其中最經典的實現那就是JDK的CopyOnWriteArrayList了。java

好了,說了這麼多,下面正式進入正題。咱們能夠想這麼一個問題,在多線程環境中如何對每一條線程的生命週期進行跟蹤,跟具體地說,在web項目中,咱們一般會對某一個請求從進入系統到退出系統的整個生命週期進行日誌記錄,又或者說是對一個事務的全過程進行跟蹤記錄,一般的作法是採用爲每一個線程創建一個叫transactionId的值,用於標識每一個線程並進程跟蹤,下圖紅圈即爲transactionId的值。web

那麼你可能會說,我也能夠不使用transactionId來跟蹤啊,我採用線程名不就行了嗎?道理是這樣,可是當你想對改值進行自定義呢?好比獲取客戶端的ip地址、被調用的接口名呢???數據庫

好了,假設咱們採用transactionId來跟蹤線程,那麼若是這個transactionId是個共享變量的話,那咱們不就是得對其作相關線程同步的操做,線程那麼多,那不煩死,笨死!嗯,TheadLocal就是在這種狀況下而生了,一個TheadLocal表明一個變量,你千萬不要覺得它表明一個線程,否則我會暈死!這個變量天生就是線程安全的。爲何呢?由於它的工做原理是會爲每條線程作一份變量的拷貝,各個線程的變量不會相互影響,天然就不會有線程安全問題咯!安全

醬紫,我畫張圖吧!bash

TheadLocal的底層實現原理是經過ThreadLocalMap實現,時間匆忙,其原理不在本文講解範圍內,讀者自行學習源碼!還有一點思想值得學習,多線程通常的同步機制採用了「以時間換空間」的方式,好比定義一個static變量,同步訪問,而ThreadLocal則採用了「以空間換時間」的方式。session

2、實例解析

package com.wokao66.concurrent;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalTest {
	//線程本地變量
	private static ThreadLocal<Integer> sessionId = new ThreadLocal<>();
	public static void main(String[] args) {
		//線程1
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我對ThreadLocal的值進行修改,自定義
				sessionId.set(1);
				try {
					Thread.sleep(1000);
					//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		//線程2
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我對ThreadLocal的值進行修改,自定義
				sessionId.set(2);
				try {
					Thread.sleep(1000);
					//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		//線程1
		Thread thread3 = new Thread(new Runnable() {
			@Override
			public void run() {
				//我對ThreadLocal的值進行修改,自定義
				sessionId.set(3);
				try {
					Thread.sleep(1000);
					//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
					for (int i = 0; i < 5; i++) {
						System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
					}
				} catch (InterruptedException e) {}
			}
		});
		ExecutorService service = Executors.newFixedThreadPool(30);
		service.execute(thread1);
		service.execute(thread2);
		service.execute(thread3);
	}
}
複製代碼

控制檯輸出多線程

[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
複製代碼

能夠看到每條線程對應的ThreadLocal是同樣的,證實了確實是有作拷貝操做!ide

3、ThreadLocal的應用場景

使用ThreadLocal首先一點那就是對變量的訪問不須要同步控制,經常使用的場景以下:性能

  • 一、對多線程進行跟蹤,經常使用於接口訪問日誌記錄,能夠參考MDC機制
  • 二、用來解決數據庫鏈接、Session管理(其實也是一種跟蹤用途)

須要注意的是,既然TreadLocal是以採用空間換取時間的思想,因此能夠想象TreadLocal並不適合來定義大對象,由於大對象的話每一個線程都有拷貝,線程一多,性能一定受到牽連,甚至JVM拋出ERROR學習

相關文章
相關標籤/搜索