天天看點

線程局部變量 ThreadLocal線程局部變量 ThreadLocal

文章目錄

  • 線程局部變量 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;
    }
}

           

繼續閱讀