一個錯誤使用單例模式的場景及ThreadLocal簡析


近來參與一個Java的web辦公系統,碰到一個bug,開始猜測是線程池管理的問題,最后發現是單例模式的問題。

即,當同時發起兩個事務請求時,當一個事務完成后,另一個事務會拋出session is closed異常。具體見下圖:

1

至於,下面這種情況,當時也測試過,但問題情形忘了,手上沒有數據庫環境,無法進行測試:

image

最開始,個人認為是session管理的問題,比如,在關閉session的時候,會同時關閉先前打開的session。由於下面采用的是其他公司的框架,所以就反饋給了技術總監。后來,反饋給我,竟然是單例的問題。

簡單看了一下本系統,其在框架基礎上又封裝了一層,涉及這個bug的類關系如下:

image

發現原來設想復雜了,本框架並沒有一個session的線程池管理,僅僅是對每個請求新建一個ThreadLocal對象(在DaoFactoryClass中實現),其中的initValue方法中新建了一個session對象。

問題出現在自己封裝的DaoBaseClass類中,此類實現了一個單例模式,需要一個Dao參數,這個參數是通過ActionFrameClass的方法getDao()獲得的,於是乎,原來實現的每個線程一個session變量,現在又被單例模式給破壞了。

附注:

ThreadLocal和Synchonized都用於解決多線程並發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通信時能夠獲得數據共享。

Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。

一、ThreadLocal使用一般步驟:

1、在多線程的類(如ThreadDemo類)中,創建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。

2、在ThreadDemo類中,創建一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,並強制轉換為要應用的類型。

3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。

二、Hibernate中的使用:

private static final ThreadLocal threadSession = new ThreadLocal();

public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}

三、ThreadLocal實現原理(JDK1.5中)

public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread hash maps attached to each thread
* (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
* objects act as keys, searched via threadLocalHashCode. This is a
* custom hash code (useful only within ThreadLocalMaps) that eliminates
* collisions in the common case where consecutively constructed
* ThreadLocals are used by the same threads, while remaining well-behaved
* in less common cases.
*/
private final int threadLocalHashCode = nextHashCode();

/**
* The next hash code to be given out. Accessed only by like-named method.
*/
private static int nextHashCode = 0;

/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;

/**
* Compute the next hash code. The static synchronization used here
* should not be a performance bottleneck. When ThreadLocals are
* generated in different threads at a fast enough rate to regularly
* contend on this lock, memory contention is by far a more serious
* problem than lock contention.
*/
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}

/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}

/**
* Returns the value in the current thread's copy of this thread-local
* variable. Creates and initializes the copy if this is the first time
* the thread has called this method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
return (T)map.get(this);

// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Many applications will have no need for
* this functionality, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current threads' copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

.......

/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {

........

}

}
 
public class Thread implements Runnable {
......

/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
......
}
參考:
0x1.深入研究java.lang.ThreadLocal類 http://lavasoft.blog.51cto.com/62575/51926/
0x2.正確理解ThreadLocal http://www.iteye.com/topic/103804

- by 一個農夫 -


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM