文章目錄
- 線程局部變量 ThreadLocal
-
- ThreadLocal 的作用和目的
- ThreadLocal 的應用場景
- ThreadLocal 的使用方式
-
- (1) 在關聯資料類中建立 private static ThreadLocal
- 2. 在 Util 類中建立 ThreadLocal
- 3. 在 Runnable 中建立 ThreadLocal
線程局部變量 ThreadLocal
ThreadLocal 的作用和目的
- 用于實作線程内的資料共享,即對于相同的程式代碼,多個子產品在同一個 線程中運作時要共享一份資料,而在另外線程中運作時又共享另外一份資料。
-
每個線程調用全局 ThreadLocal 對象的 set 方法,在 set 方法中,首先根據目前線程擷取目前線程的
ThreadLocalMap 對象,然後往這個 map 中插入一條記錄,key 其實是 ThreadLocal 對象,value 是各自的 set方法傳進去的值。也就是每個線程其實都有一份自己獨享的 ThreadLocalMap對象,該對象的 Key 是 ThreadLocal對象,值是使用者設定的具體值。線上程結束時可以調用 ThreadLocal.remove()方法,這樣會更快釋放記憶體,不調用也可以,因為線程結束後也可以自動釋放相關的 ThreadLocal 變量。
ThreadLocal 的應用場景
-
訂單處理包含一系列操作:減少庫存量、增加一條流水台賬、修改總賬,這幾個操作要在同一個
事務中完成,通常也即同一個線程中進行處理,如果累加公司應收款的操作失敗了,則應該把前面
的操作復原,否則,送出所有操作,這要求這些操作使用相同的資料庫連接配接對象,而這些操作的代碼 分别位于不同的子產品類中。
- 銀行轉賬包含一系列操作: 把轉出帳戶的餘額減少,把轉入帳戶的餘額增加,這兩個操作要在同一個事務中完成,它們必須使用相同的資料庫連接配接對象,轉入和轉出操作的代碼分别是兩個不同 的帳戶對象的方法。
- 例如 Strut2 的 ActionContext,同一段代碼被不同的線程調用運作時,該代碼操作的資料是每個線程各自的狀态和資料,對于不同的線程來說,getContext 方法拿到的對象都不相同,對同一個 線程來說,不管調用 getContext 方法多少次和在哪個子產品中 getContext 方法,拿到的都是同一 個。
ThreadLocal 的使用方式
(1) 在關聯資料類中建立 private static ThreadLocal
在下面的類中,私有靜态 ThreadLocal 執行個體(serialNum)為調用該類的靜态 SerialNum.get() 方法的每個線程維護了一個“序列号”,該方法将傳回目前線程的序列号。(線程的序列号是在第一次調用 SerialNum.get() 時配置設定的,并在後續調用中不會更改。)
package com.wjl.test.mythreadLocal;
public class SerialNum {
//The next serial number to be assigned
private static int nextSerialNum=0;
private static ThreadLocal serialNum=new ThreadLocal(){
protected synchronized Object initialValue(){
return new Integer(nextSerialNum++);
}
};
public static int get(){
return ((Integer)(serialNum.get())).intValue();
}
public static void main(String[] args) {
System.out.println(get());//0
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"serialNum = "+get());
}
}).start(); //1
}
}
另一個例子,也是私有靜态 ThreadLocal 執行個體:
package com.wjl.test.mythreadLocal;
import java.util.Random;
public class ThreadContext {
private String userId;
private Long transactionId;
private static ThreadLocal threadLocal=new ThreadLocal(){
@Override
protected Object initialValue() {
// return super.initialValue();
return new ThreadContext();
}
};
public static ThreadContext get(){
return (ThreadContext) threadLocal.get();
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Long getTransactionId() {
return transactionId;
}
public void setTransactionId(Long transactionId) {
this.transactionId = transactionId;
}
@Override
public String toString() {
return "ThreadContext{" +
"userId='" + userId + '\'' +
", transactionId=" + transactionId +
'}';
}
public static void main(String[] args) {
for (int i=0;i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
ThreadContext threadContext=ThreadContext.get();
threadContext.setUserId( new Random().nextInt(100) +"");
threadContext.setTransactionId(new Random().nextLong());
System.out.println("Thread name:"+Thread.currentThread().getName() +","+threadContext);
}
}).start();
}
}
}
2. 在 Util 類中建立 ThreadLocal
import sun.rmi.runtime.Log;
import javax.security.auth.login.Configuration;
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定義 SessionFactory
static {
try {
// 通過預設配置檔案 hibernate.cfg.xml 建立 SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化 SessionFactory 失敗!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//建立線程局部變量 session,用來儲存 Hibernate 的 Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 擷取目前線程中的 Session
*
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果 Session 還沒有打開,則新開一個 Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //将新開的 Session 儲存到線程局部變量中
}
return s;
}
public static void closeSession() throws HibernateException {
//擷取線程局部變量,并強制轉換為 Session 類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
3. 在 Runnable 中建立 ThreadLocal
線上程類内部建立 ThreadLocal,基本步驟如下:
- ①、在多線程的類(如 ThreadDemo 類)中,建立一個 ThreadLocal 對象 threadXxx,用來儲存線程間需要隔離處理的對象 xxx。
- ②、在 ThreadDemo 類中,建立一個擷取要隔離通路的資料的方法 getXxx(),在方法中判斷,若ThreadLocal 對象為 null 時候,應該 new()一個隔離通路類型的對象,并強制轉換為要應用的類型
- ③、在 ThreadDemo 類的 run()方法中,通過調用 getXxx()方法擷取要操作的資料,這樣可以保證每個線程對應一個資料對象,在任何時刻都操作的是這個對象。
package com.wjl.test.mythreadLocal;
import java.util.Random;
public class ThreadLocalTest implements Runnable {
ThreadLocal<Student> studenThreadLocal = new ThreadLocal<Student>();
@Override
public void run() {
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " si running...");
Random random = new Random();
int age = random.nextInt(100);
System.out.println(currentThreadName + " is set age : " + age);
//通過這個方法,為每個線程都獨立的 new 一個 student 對象,
// 每個線程的的student 對象都可以設定不同的值
Student student = getStudent();
student.setAge(age);
System.out.println(currentThreadName + " is first get age: " + student.getAge());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(currentThreadName + " is second get age:" + student.getAge());
}
public static void main(String[] args) {
ThreadLocalTest t = new ThreadLocalTest();
Thread t1 = new Thread(t, "Thread A");
Thread t2 = new Thread(t, "Thread B");
t1.start();
t2.start();
}
private Student getStudent() {
Student student = studenThreadLocal.get();
if (null == student) {
student = new Student();
studenThreadLocal.set(student);
}
return student;
}
}
class Student {
int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}