亚洲精品中文字幕无乱码_久久亚洲精品无码AV大片_最新国产免费Av网址_国产精品3级片

java語(yǔ)言

簡(jiǎn)單地分析Java線(xiàn)程編程中ThreadLocal類(lèi)的使用

時(shí)間:2024-08-10 16:46:11 java語(yǔ)言 我要投稿
  • 相關(guān)推薦

簡(jiǎn)單地分析Java線(xiàn)程編程中ThreadLocal類(lèi)的使用

  一、概述

  ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線(xiàn)程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是threadlocalvariable(線(xiàn)程局部變量)。也許把它命名為T(mén)hreadLocalVar更加合適。線(xiàn)程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線(xiàn)程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線(xiàn)程綁定機(jī)制,是每一個(gè)線(xiàn)程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線(xiàn)程的副本沖突。

  從線(xiàn)程的角度看,每個(gè)線(xiàn)程都保持一個(gè)對(duì)其線(xiàn)程局部變量副本的隱式引用,只要線(xiàn)程是活動(dòng)的并且 ThreadLocal 實(shí)例是可訪(fǎng)問(wèn)的;在線(xiàn)程消失之后,其線(xiàn)程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)。

  通過(guò)ThreadLocal存取的數(shù)據(jù),總是與當(dāng)前線(xiàn)程相關(guān),也就是說(shuō),JVM 為每個(gè)運(yùn)行的線(xiàn)程,綁定了私有的本地實(shí)例存取空間,從而為多線(xiàn)程環(huán)境常出現(xiàn)的并發(fā)訪(fǎng)問(wèn)問(wèn)題提供了一種隔離機(jī)制。

  ThreadLocal是如何做到為每一個(gè)線(xiàn)程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,在ThreadLocal類(lèi)中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線(xiàn)程的變量的副本。

  概括起來(lái)說(shuō),對(duì)于多線(xiàn)程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線(xiàn)程排隊(duì)訪(fǎng)問(wèn),而后者為每一個(gè)線(xiàn)程都提供了一份變量,因此可以同時(shí)訪(fǎng)問(wèn)而互不影響。

  二、API說(shuō)明

  ThreadLocal()

  創(chuàng)建一個(gè)線(xiàn)程本地變量。

  T get()

  返回此線(xiàn)程局部變量的當(dāng)前線(xiàn)程副本中的值,如果這是線(xiàn)程第一次調(diào)用該方法,則創(chuàng)建并初始化此副本。

  protected T initialValue()

  返回此線(xiàn)程局部變量的當(dāng)前線(xiàn)程的初始值。最多在每次訪(fǎng)問(wèn)線(xiàn)程來(lái)獲得每個(gè)線(xiàn)程局部變量時(shí)調(diào)用此方法一次,即線(xiàn)程第一次使用 get() 方法訪(fǎng)問(wèn)變量的時(shí)候。如果線(xiàn)程先于 get 方法調(diào)用 set(T) 方法,則不會(huì)在線(xiàn)程中再調(diào)用 initialValue 方法。

  若該實(shí)現(xiàn)只返回 null;如果程序員希望將線(xiàn)程局部變量初始化為 null 以外的某個(gè)值,則必須為 ThreadLocal 創(chuàng)建子類(lèi),并重寫(xiě)此方法。通常,將使用匿名內(nèi)部類(lèi)。initialValue 的典型實(shí)現(xiàn)將調(diào)用一個(gè)適當(dāng)?shù)臉?gòu)造方法,并返回新構(gòu)造的對(duì)象。

  void remove()

  移除此線(xiàn)程局部變量的值。這可能有助于減少線(xiàn)程局部變量的存儲(chǔ)需求。如果再次訪(fǎng)問(wèn)此線(xiàn)程局部變量,那么在默認(rèn)情況下它將擁有其 initialValue。

  void set(T value)

  將此線(xiàn)程局部變量的當(dāng)前線(xiàn)程副本中的值設(shè)置為指定值。許多應(yīng)用程序不需要這項(xiàng)功能,它們只依賴(lài)于 initialValue() 方法來(lái)設(shè)置線(xiàn)程局部變量的值。

  在程序中一般都重寫(xiě)initialValue方法,以給定一個(gè)特定的初始值。

  三、一.對(duì)ThreadLocal的理解

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

  這句話(huà)從字面上看起來(lái)很容易理解,但是真正理解并不是那么容易。

  我們還是先來(lái)看一個(gè)例子:

  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è)有這樣一個(gè)數(shù)據(jù)庫(kù)鏈接管理類(lèi),這段代碼在單線(xiàn)程中使用是沒(méi)有任何問(wèn)題的,但是如果在多線(xiàn)程中使用呢?很顯然,在多線(xiàn)程中使用會(huì)存在線(xiàn)程安全問(wèn)題:第一,這里面的2個(gè)方法都沒(méi)有進(jìn)行同步,很可能在openConnection方法中會(huì)多次創(chuàng)建connect;第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來(lái)保障線(xiàn)程安全,因?yàn)楹芸赡芤粋(gè)線(xiàn)程在使用connect進(jìn)行數(shù)據(jù)庫(kù)操作,而另外一個(gè)線(xiàn)程調(diào)用closeConnection關(guān)閉鏈接。

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

  這樣將會(huì)大大影響程序執(zhí)行效率,因?yàn)橐粋(gè)線(xiàn)程在使用connect進(jìn)行數(shù)據(jù)庫(kù)操作的時(shí)候,其他線(xiàn)程只有等待。

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

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

  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 () {    ConnectionManager connectionManager = new ConnectionManager();    Connection connection = connectionManager.openConnection();         //使用connection進(jìn)行操作         connectionManager.closeConnection();  }}

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

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

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

  四、實(shí)例

  創(chuàng)建一個(gè)Bean,通過(guò)不同的線(xiàn)程對(duì)象設(shè)置Bean屬性,保證各個(gè)線(xiàn)程Bean對(duì)象的獨(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 * 多線(xiàn)程下測(cè)試程序 */public class ThreadLocalDemo implements Runnable {  //創(chuàng)建線(xiàn)程局部變量studentLocal,在后面你會(huì)發(fā)現(xiàn)用來(lái)保存Student對(duì)象  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ù)方法,用來(lái)測(cè)試   */  public void accessStudent() {    //獲取當(dāng)前線(xiàn)程的名字    String currentThreadName = Thread.currentThread().getName();    System.out.println(currentThreadName + " is running!");    //產(chǎn)生一個(gè)隨機(jī)數(shù)并打印    Random random = new Random();    int age = random.nextInt(100);    System.out.println("thread " + currentThreadName + " set age to:" + age);    //獲取一個(gè)Student對(duì)象,并將隨機(jī)數(shù)年齡插入到對(duì)象屬性中    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() {    //獲取本地線(xiàn)程變量并強(qiáng)制轉(zhuǎn)換為Student類(lèi)型    Student student = (Student) studentLocal.get();    //線(xiàn)程首次執(zhí)行此方法的時(shí)候,studentLocal.get()肯定為null    if (student == null) {      //創(chuàng)建一個(gè)Student對(duì)象,并保存到本地線(xiàn)程變量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兩個(gè)線(xiàn)程age在不同時(shí)刻打印的值是完全相同的。這個(gè)程序通過(guò)妙用ThreadLocal,既實(shí)現(xiàn)多線(xiàn)程并發(fā),游兼顧數(shù)據(jù)的安全性。

  五、ThreadLocal使用的一般步驟

  1、在多線(xiàn)程的類(lèi)(如ThreadDemo類(lèi))中,創(chuàng)建一個(gè)ThreadLocal對(duì)象threadXxx,用來(lái)保存線(xiàn)程間需要隔離處理的對(duì)象xxx。

  2、在ThreadDemo類(lèi)中,創(chuàng)建一個(gè)獲取要隔離訪(fǎng)問(wèn)的數(shù)據(jù)的方法getXxx(),在方法中判斷,若ThreadLocal對(duì)象為null時(shí)候,應(yīng)該new()一個(gè)隔離訪(fǎng)問(wèn)類(lèi)型的對(duì)象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類(lèi)型。

  3、在ThreadDemo類(lèi)的run()方法中,通過(guò)getXxx()方法獲取要操作的數(shù)據(jù),這樣可以保證每個(gè)線(xiàn)程對(duì)應(yīng)一個(gè)數(shù)據(jù)對(duì)象,在任何時(shí)刻都操作的是這個(gè)對(duì)象。

  

【簡(jiǎn)單地分析Java線(xiàn)程編程中ThreadLocal類(lèi)的使用】相關(guān)文章:

Java線(xiàn)程編程中的主線(xiàn)程詳細(xì)介紹09-05

Java并發(fā)編程:深入剖析ThreadLocal09-20

Java中ArrayList類(lèi)的使用用法07-12

java中通用的線(xiàn)程池實(shí)例代碼08-27

java多線(xiàn)程08-31

java的多線(xiàn)程09-09

Java編程中throw和throws子句的使用方法08-26

舉例講解Java中的多線(xiàn)程范文欣賞06-16

java語(yǔ)言的多線(xiàn)程08-29

java線(xiàn)程的幾種狀態(tài)10-22