1. 程式人生 > >Spring-利用ThreadLocal解決執行緒安全問題(多執行緒併發登入)

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();
        }
    }