守護線程,線程組,線程池,ThreadLocal

守護線程  

守護線程是一類特殊的線程,它和普通線程的區別在於它並不是應用程序的核心部分 ,當一個應用程序的所有非守護線程終止運行時,即使仍然有守護線程在運行,應用程序 也將終止,反之,只要有一個非守護線程在運行,應用程序就不會終止。守護線程一般被 用於在後臺爲其它線程提供服務。  

可以通過調用方法 isDaemon() 來判斷一個線程是否是守護線程,也可以調用方法 setDa emon() 來將一個線程設爲守護線程。

線程組

線程組是一個 Java 特有的概念,在 Java 中,線程組是類ThreadGroup 的對象,每 個線程都隸屬於唯一一個線程組,這個線程組在線程創建時指定並在線程的整個生命期內 都不能更改。你可以通過調用包含 ThreadGroup 類型參數的 Thread 類構造函數來指定線程屬的線程組,若沒有指定,則線程缺省地隸屬於名爲 system 的系統線程組。在 Java 中,除了預建的系統線程組外,所有線程組都必須顯式創建。在 Java 中, 除系統線程組外的每個線程組又隸屬於另一個線程組,你可以在創建線程組時指定其所隸 屬的線程組,若沒有指定,則缺省地隸屬於系統線程組。這樣,所有線程組組成了一棵以 系統線程組爲根的樹。

Java 允許我們對一個線程組中的所有線程同時進行操作,比如我們可以通過調用線程 組的相應方法來設置其中所有線程的優先級,也可以啓動或阻塞其中的所有線程。 Java 的線程組機制的另一個重要作用是線程安全。線程組機制允許我們通過分組來區分有 不同安全特性的線程,對不同組的線程進行不同的處理,還可以通過線程組的分層結構來 支持不對等安全措施的採用。Java 的 ThreadGroup 類提供了大量的方法來方便我們對線 程組樹中的每一個線程組以及線程組中的每一個線程進行操作。

線程池 

諸如 Web 服務器、數據庫服務器、文件服務器或郵件服務器之類的許多服務器應用程序都面向處理來自某些遠程來源的大量短小的任務。 服務器應用程序中經常出現的情況是:單個任務處理的時間很短而請求的數目卻是巨大的。

構建服務器應用程序的一個過於簡單的模型應該是:每當一個請求到達就創建一個新線程,然後在新線程中爲請求服務。

那麼這種方法的嚴重不足就很明顯。每個請求對應一個線程(thread-per-request)方法的不足之一是:爲每個請求創建一個新線程的開銷很大 ;在一個 JVM 裏創建太多的線程可能會導致系統由於過度消耗內存而用完內存或“切換過度”。爲了防止資源不足,服務器應用程序需要一些辦法來限制任何給定時刻處理的請求數目。 

我們可以實現一個線程池類,其中客戶機類等待一個可用線程、將任務傳遞給該線程以便執行、然後在任務完成時將線程歸還給池, 

一般一個簡單線程池至少包含下列組成部分: 

  • 線程池管理器(ThreadPoolManager):用於創建並管理線程池 
  • 工作線程(WorkThread): 線程池中線程 
  • 任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行。
  • 任務隊列:用於存放沒有處理的任務。提供一種緩衝機制。

ThreadLocal

概述

其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名爲ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是爲每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較爲特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。

從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的並且 ThreadLocal 實例是可訪問的;在線程消失之後,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。

通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM 爲每個運行的線程,綁定了私有的本地實例存取空間,從而爲多線程環境常出現的併發訪問問題提供了一種隔離機制。

ThreadLocal是如何做到爲每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每一個線程的變量的副本。

概括起來說,對於多線程資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而後者爲每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

API說明

ThreadLocal():創建一個線程本地變量。

T get():返回此線程局部變量的當前線程副本中的值,如果這是線程第一次調用該方法,則創建並初始化此副本。

protected T initialValue():返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先於 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。該實現只返回 null;如果程序員希望將線程局部變量初始化爲 null 以外的某個值,則必須爲 ThreadLocal 創建子類,並重寫此方法。通常,將使用匿名內部類。initialValue的典型實現將調用一個適當的構造方法,並返回新構造的對象。

void remove():移除此線程局部變量的值。這可能有助於減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那麼在默認情況下它將擁有其 initialValue。

void set(T value):將此線程局部變量的當前線程副本中的值設置爲指定值。許多應用程序不需要這項功能,它們只依賴於initialValue() 方法來設置線程局部變量的值。

在程序中一般都重寫initialValue方法,以給定一個特定的初始值。

典型實例

創建一個Bean,通過不同的線程對象設置Bean屬性,保證各個線程Bean對象的獨立性。

public class Student {
    private int age = 0;   //年齡
 
    public int getAge() {
        return this.age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
 
/**
 * 多線程下測試程序
 */
public class ThreadLocalDemo implements Runnable {
    //創建線程局部變量studentLocal,在後面你會發現用來保存Student對象
    private final static ThreadLocal studentLocal = new ThreadLocal();
 
    public static void main(String[] agrs) {
        ThreadLocalDemo td = new ThreadLocalDemo();
        Thread t1 = new Thread(td, "a");
        Thread t2 = new Thread(td, "b");
        t1.start();
        t2.start();
    }
 
    public void run() {
        accessStudent();
    }
 
    /**
     * 示例業務方法,用來測試
     */
    public void accessStudent() {
        //獲取當前線程的名字
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");
        //產生一個隨機數並打印
        Random random = new Random();
        int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);
        //獲取一個Student對象,並將隨機數年齡插入到對象屬性中
        Student student = getStudent();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
        try {
            Thread.sleep(500);
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }
 
    protected Student getStudent() {
        //獲取本地線程變量並強制轉換爲Student類型
        Student student = (Student) studentLocal.get();
        //線程首次執行此方法的時候,studentLocal.get()肯定爲null
        if (student == null) {
            //創建一個Student對象,並保存到本地線程變量studentLocal中
            student = new Student();
            studentLocal.set(student);
        }
        return student;
    }
}

運行結果:

a is running! 

thread a set age to:76 

b is running! 

thread b set age to:27 

thread a first read age is:76 

thread b first read age is:27 

thread a second read age is:76 

thread b second read age is:27 

可以看到a、b兩個線程age在不同時刻打印的值是完全相同的。這個程序通過妙用ThreadLocal,既實現多線程併發,又兼顧數據的安全性。

總結

ThreadLocal使用場合主要解決多線程中數據數據因併發產生不一致問題。ThreadLocal爲每個線程的中併發訪問的數據提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了內存,單大大減少了線程同步所帶來性能消耗,也減少了線程併發控制的複雜度。

ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。

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

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

當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。

ThreadLocal使用的一般步驟

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

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

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


以上內容整理自互聯網


發佈了8 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章