天天看點

Spring-利用ThreadLocal解決線程安全問題(多線程并發登入)

ThreadLocal是什麼

ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。當工作于多線程中的對象使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程配置設定一個獨立的變量副本。是以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中“Local”所要表達的意思。

ThreadLocal的方法很簡單,主要的就是4個方法

1
2
3
4
5
6
7
8
9
10
11
      
// 設定目前線程的線程局部變量的值
void set(T value)

// 傳回目前線程所對應的線程局部變量
public T get()

// 将目前線程局部變量的值删除,目的是為了減少記憶體的占用,加快記憶體回收的速度
public void remove()

// 傳回該線程局部變量的初始值,讓子類繼承而設計的。這個方法是一個延遲調用方法,線上程第一次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的預設實作直接傳回一個null。
protected T initialValue()
      

ThreadLocal的實作思路

那麼ThreadLocal是如何做到為每一個線程維護一份獨立的變量副本呢?其實實作思路很簡單:在ThreadLocal類中有一個Map,用于存儲沒一個線程的變量副本,Map中元素的鍵為線程對象,而值對應線程的變量副本。我們自己也可以提供一個簡單的實作版本:

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
      
public class SimpleThreadLocal {
	private Map<Thread, Object> valueMap = Collections.synchronizedMap(new HashMap<>());
	
	public void set(Object newValue) {
		valueMap.set(Thread.currentThread(), newValue);
	}
	
	public Object get() {
		Thread currentThread = Thread.currentThread();
		Object o = valueMap.get(currentThread);
		if (o == null && !valueMap.containsKey(currentThread)) {
			o = initialValue();
			valueMap.put(currentThread, o);
		}
		return o;
	}
	
	public void remove() {
		valueMap.remove(Thread.currentThread());
	}
	
	public Object initialValue() {
		return null;
	}
}
      

ThreadLocal的運用

下面是我在項目中運用ThreadLocal來實作多線程情況下,使用者資訊的管理Session:

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
      
package com.benjamin.common.session;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by piqiu on 2/27/16.
 * 使用者Session資訊保管類
 */
public class UserSession {

    /** 儲存使用者的資訊 **/
    private static final ThreadLocal<Map<Object, Object>> userThreadLocal = new ThreadLocal<>();

    protected UserSession(){}

    public static Object get(String attribute) {
        Map<Object, Object> userInfo = userThreadLocal.get();
        Object o = null;
        if (userInfo != null) {
            o = userInfo.get(attribute);
        }
        return o;
    }

    public static <T> T get(String attribute, Class<T> clazz) {
        return (T)get(attribute);
    }

    public static void set(Object attribute, Object value) {
        Map<Object, Object> userInfo = userThreadLocal.get();
        if (userInfo == null) {
            userInfo = new HashMap<>();
            userThreadLocal.set(userInfo);
        }
        userInfo.put(attribute, value);
    }
}
      

ThreadLocal與Thread同步機制的比較

ThreadLocal和線程同步機制都是為了解決多線程中相同變量的通路沖突問題,那麼,ThreadLocal和線程同步機制相比有什麼優勢呢?

在同步機制中,通過對象的鎖機制保證同一時間隻有一個線程通路變量。這時該變量是多個線程共享的,使用同步機制要求程式缜密的分析什麼時候對變量進行讀寫,什麼時候需要鎖定某個對象,什麼時候釋放對象鎖等繁雜的問題,程式設計和編寫難度相對較大

而ThreadLocal則從另一個角度來解決多線程的并發通路。ThreadLocal為每一個線程提供一個獨特的變量副本,進而隔離了多個線程對通路資料的沖突。因為每一個線程都擁有自己的變量副本,進而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的對象封裝,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。

概括起來說,對于多線程資源共享的問題,同步機制采用了“以時間換空間”的方式:通路串行化,對象共享化。而ThreadLocal采用了“以空間換時間”的方式:通路并行化,對象獨享化。前者僅提供一份變量,讓不同的線程排隊通路,而後者為每一個線程都提供了一份變量,是以可以同時通路而互不影響。

Spring利用ThreadLocal解決線程安全問題

我們知道在一般情況下,隻有無狀态的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder)中非線程安全的“狀态性對象”采用ThreadLocal進行封裝,讓它們也成為線程安全的“狀态性對象”,因為有狀态的Bean就能夠以singleton方式在多線程中正常工作了。

通過檢視TransactionSynchronizationManager源碼可以發現Spring事務的工作機制,我們可以自己利用ThreadLocal來實作自定義的Session和Transaction管理:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
      
package com.benjamin.common;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;

/**
 * Created by piqiu on 2/27/16.
 */
public class HibernateUtil {

    private static final Logger logger = LoggerFactory.getLogger(HibernateUtil.class);

    /** 存儲hibernate session的ThreadLocal **/
    private static final ThreadLocal<Session> sessionThreadLocal = new ThreadLocal<>();

    /** 存儲事務n的ThreadLocal **/
    private static final ThreadLocal<Transaction> transactionThreadLocal = new ThreadLocal<>();

    /** session工廠 **/
    private static SessionFactory sessionFactory;

    /**
     * 初始化sessionFactory
     * @param file
     */
    public static void initSessionFactory(File file) {
        Configuration configuration = new Configuration();
        configuration.configure(file);
        sessionFactory = configuration.buildSessionFactory();
    }

    /**
     * 取得目前線程綁定的session
     * @return
     */
    public static Session currentSession() {
        Session session = sessionThreadLocal.get();
        if (session == null) {
            session = sessionFactory.openSession();
            sessionThreadLocal.set(session);
        }
        return session;
    }

    /**
     * 關閉session
     */
    public static void closeSession() {
        Session session = sessionThreadLocal.get();
        if (session != null) {
            sessionThreadLocal.set(null);
            session.clear();
            session.close();
        }
    }

    /**
     * 取得目前session的事務
     * @return
     */
    public static Transaction transaction() {
        Transaction transaction = transactionThreadLocal.get();
        if (transaction == null) {
            transaction = currentSession().beginTransaction();
            transactionThreadLocal.set(transaction);
        }
        return transaction;
    }

    /**
     * 送出事務
     */
    public static void commitTransaction() {
        Transaction transaction = transactionThreadLocal.get();
        transactionThreadLocal.set(null);
        if (transaction != null) {
            transaction.commit();
        }
    }

    /**
     * 復原事務
     */
    public static void rollbackTransaction() {
        Transaction transaction = transactionThreadLocal.get();
        transactionThreadLocal.set(null);
        if (transaction != null) {
            transaction.rollback();
        }
    }      

繼續閱讀