欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡單分析Java線程編程中ThreadLocal類的使用

 更新時間:2015年12月08日 18:26:33   作者:leizhimin  
這篇文章主要介紹了Java線程編程中ThreadLocal類的使用,包括使用其對共享變量的操作的分析,需要的朋友可以參考下

一、概述
 
ThreadLocal是什么呢?其實ThreadLocal并非是一個線程的本地實現(xiàn)版本,它并不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,是每一個線程都可以獨(dú)立地改變自己的副本,而不會和其它線程的副本沖突。
 
從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的并且 ThreadLocal 實例是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。
 
通過ThreadLocal存取的數(shù)據(jù),總是與當(dāng)前線程相關(guān),也就是說,JVM 為每個運(yùn)行的線程,綁定了私有的本地實例存取空間,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問問題提供了一種隔離機(jī)制。
 
ThreadLocal是如何做到為每一個線程維護(hù)變量的副本的呢?其實實現(xiàn)的思路很簡單,在ThreadLocal類中有一個Map,用于存儲每一個線程的變量的副本。
 
概括起來說,對于多線程資源共享的問題,同步機(jī)制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
 
二、API說明
 
ThreadLocal()
          創(chuàng)建一個線程本地變量。
 
T get()
          返回此線程局部變量的當(dāng)前線程副本中的值,如果這是線程第一次調(diào)用該方法,則創(chuàng)建并初始化此副本。
 
protected  T initialValue()
          返回此線程局部變量的當(dāng)前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調(diào)用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先于 get 方法調(diào)用 set(T) 方法,則不會在線程中再調(diào)用 initialValue 方法。
 
   若該實現(xiàn)只返回 null;如果程序員希望將線程局部變量初始化為 null 以外的某個值,則必須為 ThreadLocal 創(chuàng)建子類,并重寫此方法。通常,將使用匿名內(nèi)部類。initialValue 的典型實現(xiàn)將調(diào)用一個適當(dāng)?shù)臉?gòu)造方法,并返回新構(gòu)造的對象。
 
void remove()
          移除此線程局部變量的值。這可能有助于減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那么在默認(rèn)情況下它將擁有其 initialValue。
 
void set(T value)
          將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值。許多應(yīng)用程序不需要這項功能,它們只依賴于 initialValue() 方法來設(shè)置線程局部變量的值。
 
在程序中一般都重寫initialValue方法,以給定一個特定的初始值。

三、一.對ThreadLocal的理解

  ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多??赡芎芏嗯笥讯贾繲hreadLocal為變量在每個線程中都創(chuàng)建了一個副本,那么每個線程可以訪問自己內(nèi)部的副本變量。

  這句話從字面上看起來很容易理解,但是真正理解并不是那么容易。

  我們還是先來看一個例子:

class ConnectionManager {
   
  private static Connection connect = null;
   
  public static Connection openConnection() {
    if(connect == null){
      connect = DriverManager.getConnection();
    }
    return connect;
  }
   
  public static void closeConnection() {
    if(connect!=null)
      connect.close();
  }
}

   假設(shè)有這樣一個數(shù)據(jù)庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會存在線程安全問題:第一,這里面的2個方法都沒有進(jìn)行同步,很可能在openConnection方法中會多次創(chuàng)建connect;第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進(jìn)行數(shù)據(jù)庫操作,而另外一個線程調(diào)用closeConnection關(guān)閉鏈接。

  所以出于線程安全的考慮,必須將這段代碼的兩個方法進(jìn)行同步處理,并且在調(diào)用connect的地方需要進(jìn)行同步處理。

  這樣將會大大影響程序執(zhí)行效率,因為一個線程在使用connect進(jìn)行數(shù)據(jù)庫操作的時候,其他線程只有等待。

  那么大家來仔細(xì)分析一下這個問題,這地方到底需不需要將connect變量進(jìn)行共享?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關(guān)系的,即一個線程不需要關(guān)心其他線程是否對這個connect進(jìn)行了修改的。

  到這里,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數(shù)據(jù)庫連接的方法中具體使用時才創(chuàng)建數(shù)據(jù)庫鏈接,然后在方法調(diào)用完畢再釋放這個連接。比如下面這樣:

class ConnectionManager {
   
  private Connection connect = null;
   
  public Connection openConnection() {
    if(connect == null){
      connect = DriverManager.getConnection();
    }
    return connect;
  }
   
  public void closeConnection() {
    if(connect!=null)
      connect.close();
  }
}
 
 
class Dao{
  public void insert() {
    ConnectionManager connectionManager = new ConnectionManager();
    Connection connection = connectionManager.openConnection();
     
    //使用connection進(jìn)行操作
     
    connectionManager.closeConnection();
  }
}

   這樣處理確實也沒有任何問題,由于每次都是在方法內(nèi)部創(chuàng)建的連接,那么線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導(dǎo)致服務(wù)器壓力非常大,并且嚴(yán)重影響程序執(zhí)行性能。由于在方法中需要頻繁地開啟和關(guān)閉數(shù)據(jù)庫連接,這樣不盡嚴(yán)重影響程序執(zhí)行效率,還可能導(dǎo)致服務(wù)器壓力巨大。

  那么這種情況下使用ThreadLocal是再適合不過的了,因為ThreadLocal在每個線程中對該變量會創(chuàng)建一個副本,即每個線程內(nèi)部都會有一個該變量,且在線程內(nèi)部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴(yán)重影響程序執(zhí)行性能。

  但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由于在每個線程中都創(chuàng)建了副本,所以要考慮它對資源的消耗,比如內(nèi)存的占用會比不使用ThreadLocal要大。

四、實例

創(chuàng)建一個Bean,通過不同的線程對象設(shè)置Bean屬性,保證各個線程Bean對象的獨(dú)立性。
 

/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-11-23
 * Time: 10:45:02
 * 學(xué)生
 */
public class Student {
  private int age = 0;  //年齡
 
  public int getAge() {
    return this.age;
  }
 
  public void setAge(int age) {
    this.age = age;
  }
}
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-11-23
 * Time: 10:53:33
 * 多線程下測試程序
 */
public class ThreadLocalDemo implements Runnable {
  //創(chuàng)建線程局部變量studentLocal,在后面你會發(fā)現(xiàn)用來保存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();
  }
 
  /**
   * 示例業(yè)務(wù)方法,用來測試
   */
  public void accessStudent() {
    //獲取當(dāng)前線程的名字
    String currentThreadName = Thread.currentThread().getName();
    System.out.println(currentThreadName + " is running!");
    //產(chǎn)生一個隨機(jī)數(shù)并打印
    Random random = new Random();
    int age = random.nextInt(100);
    System.out.println("thread " + currentThreadName + " set age to:" + age);
    //獲取一個Student對象,并將隨機(jī)數(shù)年齡插入到對象屬性中
    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() {
    //獲取本地線程變量并強(qiáng)制轉(zhuǎn)換為Student類型
    Student student = (Student) studentLocal.get();
    //線程首次執(zhí)行此方法的時候,studentLocal.get()肯定為null
    if (student == null) {
      //創(chuàng)建一個Student對象,并保存到本地線程變量studentLocal中
      student = new Student();
      studentLocal.set(student);
    }
    return student;
  }
}

 
運(yùn)行結(jié)果:

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,既實現(xiàn)多線程并發(fā),游兼顧數(shù)據(jù)的安全性。

五、ThreadLocal使用的一般步驟
 
1、在多線程的類(如ThreadDemo類)中,創(chuàng)建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。
2、在ThreadDemo類中,創(chuàng)建一個獲取要隔離訪問的數(shù)據(jù)的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應(yīng)該new()一個隔離訪問類型的對象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類型。
3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數(shù)據(jù),這樣可以保證每個線程對應(yīng)一個數(shù)據(jù)對象,在任何時刻都操作的是這個對象。

  

相關(guān)文章

  • SpringBoot接受前臺參數(shù)的6種方式以及統(tǒng)一響應(yīng)代碼示例

    SpringBoot接受前臺參數(shù)的6種方式以及統(tǒng)一響應(yīng)代碼示例

    這篇文章主要給大家介紹了關(guān)于SpringBoot接受前臺參數(shù)的6種方式以及統(tǒng)一響應(yīng)的相關(guān)資料,前端負(fù)責(zé)展示頁面和用戶交互,而后端則負(fù)責(zé)處理業(yè)務(wù)邏輯和數(shù)據(jù)存儲,在這種架構(gòu)下前端需要將用戶輸入的數(shù)據(jù)發(fā)送給后端進(jìn)行處理,需要的朋友可以參考下
    2023-12-12
  • Java如何判斷字符串中是否包含某個字符

    Java如何判斷字符串中是否包含某個字符

    這篇文章主要介紹了Java如何判斷字符串中是否包含某個字符,可以使用String類的contains()方法,另一種方法使用String類的indexOf方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-02-02
  • 實例講解Java中的synchronized

    實例講解Java中的synchronized

    這篇文章主要介紹了Java中synchronized的使用方法,文中講解非常細(xì)致,代碼幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • IDEA使用入門小白操作教程

    IDEA使用入門小白操作教程

    IntelliJ IDEA,就是Java語言開發(fā)工具 (IDE) 功能與Eclipse同類!本文通過圖文并茂的形式給大家介紹IntelliJ IDEA使用入門教程,非常適合新手小白操作,感興趣的朋友一起看看吧
    2020-10-10
  • Java判斷閏年的2種方法示例

    Java判斷閏年的2種方法示例

    這篇文章主要給大家介紹了關(guān)于Java判斷閏年的2種方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • SpringBoot異步調(diào)用方法實現(xiàn)場景代碼實例

    SpringBoot異步調(diào)用方法實現(xiàn)場景代碼實例

    這篇文章主要介紹了SpringBoot異步調(diào)用方法實現(xiàn)場景代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • Java8 自定義CompletableFuture的原理解析

    Java8 自定義CompletableFuture的原理解析

    這篇文章主要介紹了Java8 自定義CompletableFuture的原理解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 淺談Mybatis獲取參數(shù)值的方式

    淺談Mybatis獲取參數(shù)值的方式

    本文主要介紹了Mybatis獲取參數(shù)值的方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • Java統(tǒng)計代碼的執(zhí)行時間的N種方法

    Java統(tǒng)計代碼的執(zhí)行時間的N種方法

    在日常開發(fā)中經(jīng)常需要測試一些代碼的執(zhí)行時間,但又不想使用向 JMH(Java?Microbenchmark Harness,Java 微基準(zhǔn)測試套件)這么重的測試框架,所以本文就匯總了一些 Java 中比較常用的執(zhí)行時間統(tǒng)計方法,總共包含以下 6 種,需要的朋友可以參考下
    2022-08-08
  • SpringBoot如何使用@RequestBody進(jìn)行數(shù)據(jù)校驗

    SpringBoot如何使用@RequestBody進(jìn)行數(shù)據(jù)校驗

    在Web開發(fā)中,前臺向后臺發(fā)送數(shù)據(jù)是非常常見的場景,而在SpringBoot框架中,我們通常使用@RequestBody注解來接收前臺發(fā)送的?JSON數(shù)據(jù),并將其轉(zhuǎn)化為Java對象,本文將介紹如何在?SpringBoot?中使用?@RequestBody?進(jìn)行數(shù)據(jù)校驗
    2023-06-06

最新評論