久久久精品一区ed2k-女人被男人叉到高潮的视频-中文字幕乱码一区久久麻豆樱花-俄罗斯熟妇真实视频

ThreadLocal怎么在Java中使用

今天就跟大家聊聊有關(guān)ThreadLocal怎么在Java中使用 ,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

創(chuàng)新互聯(lián)公司是一家專注于網(wǎng)站制作、成都網(wǎng)站制作與策劃設(shè)計(jì),邊壩網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)公司做網(wǎng)站,專注于網(wǎng)站建設(shè)十年,網(wǎng)設(shè)計(jì)領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:邊壩等地區(qū)。邊壩做網(wǎng)站價(jià)格咨詢:18982081108

一.對(duì)ThreadLocal的理解

ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲(chǔ),其實(shí)意思差不多??赡芎芏嗯笥讯贾繲hreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,那么每個(gè)線程可以訪問自己內(nè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ù)庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會(huì)存在線程安全問題:

第一,這里面的2個(gè)方法都沒有進(jìn)行同步,很可能在openConnection方法中會(huì)多次創(chuàng)建connect;

第二,由于connect是共享變量,那么必然在調(diào)用connect的地方需要使用到同步來保障線程安全,因?yàn)楹芸赡芤粋€(gè)線程在使用connect進(jìn)行數(shù)據(jù)庫操作,而另外一個(gè)線程調(diào)用closeConnection關(guān)閉鏈接。

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

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

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

到這里,可能會(huì)有朋友想到,既然不需要在線程之間共享這個(gè)變量,可以直接這樣處理,在每個(gè)需要使用數(shù)據(jù)庫連接的方法中具體使用時(shí)才創(chuàng)建數(shù)據(jù)庫鏈接,然后在方法調(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 insert() {
    ConnectionManager connectionManager = new ConnectionManager();
    Connection connection = connectionManager.openConnection();
     
    //使用connection進(jìn)行操作
     
    connectionManager.closeConnection();
  }
}

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

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

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

二.深入解析ThreadLocal類

在上面談到了對(duì)ThreadLocal的一些理解,那我們下面來看一下具體ThreadLocal是如何實(shí)現(xiàn)的。

先了解一下ThreadLocal類提供的幾個(gè)方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

get()方法是用來獲取ThreadLocal在當(dāng)前線程中保存的變量副本,set()用來設(shè)置當(dāng)前線程中變量的副本,remove()用來移除當(dāng)前線程中變量的副本,initialValue()是一個(gè)protected方法,一般是用來在使用時(shí)進(jìn)行重寫的,它是一個(gè)延遲加載方法,下面會(huì)詳細(xì)說明。

首先我們來看一下ThreadLocal類是如何為每個(gè)線程創(chuàng)建一個(gè)變量的副本的。

先看下get方法的實(shí)現(xiàn):

ThreadLocal怎么在Java中使用

第一句是取得當(dāng)前線程,然后通過getMap(t)方法獲取到一個(gè)map,map的類型為ThreadLocalMap。然后接著下面獲取到<key,value>鍵值對(duì),注意這里獲取鍵值對(duì)傳進(jìn)去的是  this,而不是當(dāng)前線程t。

如果獲取成功,則返回value值。

如果map為空,則調(diào)用setInitialValue方法返回value。

我們上面的每一句來仔細(xì)分析:

首先看一下getMap方法中做了什么:

ThreadLocal怎么在Java中使用

可能大家沒有想到的是,在getMap中,是調(diào)用當(dāng)期線程t,返回當(dāng)前線程t中的一個(gè)成員變量threadLocals。

那么我們繼續(xù)取Thread類中取看一下成員變量threadLocals是什么:

ThreadLocal怎么在Java中使用

實(shí)際上就是一個(gè)ThreadLocalMap,這個(gè)類型是ThreadLocal類的一個(gè)內(nèi)部類,我們繼續(xù)取看ThreadLocalMap的實(shí)現(xiàn):

ThreadLocal怎么在Java中使用

可以看到ThreadLocalMap的Entry繼承了WeakReference,并且使用ThreadLocal作為鍵值。

然后再繼續(xù)看setInitialValue方法的具體實(shí)現(xiàn):

ThreadLocal怎么在Java中使用

很容易了解,就是如果map不為空,就設(shè)置鍵值對(duì),為空,再創(chuàng)建Map,看一下createMap的實(shí)現(xiàn):

ThreadLocal怎么在Java中使用

至此,可能大部分朋友已經(jīng)明白了ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:

首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個(gè)threadLocals就是用來存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類型的變量)。

初始時(shí),在Thread里面,threadLocals為空,當(dāng)通過ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。

然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過get方法在threadLocals里面查找。

下面通過一個(gè)例子來證明通過ThreadLocal能達(dá)到在每個(gè)線程中創(chuàng)建變量副本的效果:

public class Test {
  ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
  ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
   
  public void set() {
    longLocal.set(Thread.currentThread().getId());
    stringLocal.set(Thread.currentThread().getName());
  }
   
  public long getLong() {
    return longLocal.get();
  }
   
  public String getString() {
    return stringLocal.get();
  }
   
  public static void main(String[] args) throws InterruptedException {
    final Test test = new Test();
     
     
    test.set();
    System.out.println(test.getLong());
    System.out.println(test.getString());
   
     
    Thread thread1 = new Thread(){
      public void run() {
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
      };
    };
    thread1.start();
    thread1.join();
     
    System.out.println(test.getLong());
    System.out.println(test.getString());
  }
}

這段代碼的輸出結(jié)果為:

ThreadLocal怎么在Java中使用

從這段代碼的輸出結(jié)果可以看出,在main線程中和thread1線程中,longLocal保存的副本值和stringLocal保存的副本值都不一樣。最后一次在main線程再次打印副本值是為了證明在main線程中和thread1線程中的副本值確實(shí)是不同的。

總結(jié)一下:
1)實(shí)際的通過ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的;

2)為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量,就像上面代碼中的longLocal和stringLocal;

3)在進(jìn)行g(shù)et之前,必須先set,否則會(huì)報(bào)空指針異常;

如果想在get之前不需要調(diào)用set就能正常訪問的話,必須重寫initialValue()方法。

因?yàn)樵谏厦娴拇a分析過程中,我們發(fā)現(xiàn)如果沒有先set的話,即在map中查找不到對(duì)應(yīng)的存儲(chǔ),則會(huì)通過調(diào)用setInitialValue方法返回i,而在setInitialValue方法中,有一個(gè)語句是T value = initialValue(), 而默認(rèn)情況下,initialValue方法返回的是null。

ThreadLocal怎么在Java中使用

看下面這個(gè)例子:

public class Test {
  ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
  ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
  public void set() {
    longLocal.set(Thread.currentThread().getId());
    stringLocal.set(Thread.currentThread().getName());
  }
   
  public long getLong() {
    return longLocal.get();
  }
   
  public String getString() {
    return stringLocal.get();
  }
   
  public static void main(String[] args) throws InterruptedException {
    final Test test = new Test();
     
    System.out.println(test.getLong());
    System.out.println(test.getString());
 
    Thread thread1 = new Thread(){
      public void run() {
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
      };
    };
    thread1.start();
    thread1.join();
     
    System.out.println(test.getLong());
    System.out.println(test.getString());
  }
}

在main線程中,沒有先set,直接get的話,運(yùn)行時(shí)會(huì)報(bào)空指針異常。

但是如果改成下面這段代碼,即重寫了initialValue方法:

public class Test {
  ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
    protected Long initialValue() {
      return Thread.currentThread().getId();
    };
  };
  ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;
    protected String initialValue() {
      return Thread.currentThread().getName();
    };
  };
 
   
  public void set() {
    longLocal.set(Thread.currentThread().getId());
    stringLocal.set(Thread.currentThread().getName());
  }
   
  public long getLong() {
    return longLocal.get();
  }
   
  public String getString() {
    return stringLocal.get();
  }
   
  public static void main(String[] args) throws InterruptedException {
    final Test test = new Test();
 
    test.set();
    System.out.println(test.getLong());
    System.out.println(test.getString());
   
     
    Thread thread1 = new Thread(){
      public void run() {
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
      };
    };
    thread1.start();
    thread1.join();
     
    System.out.println(test.getLong());
    System.out.println(test.getString());
  }
}

就可以直接不用先set而直接調(diào)用get了。

三.ThreadLocal的應(yīng)用場(chǎng)景

最常見的ThreadLocal使用場(chǎng)景為 用來解決 數(shù)據(jù)庫連接、Session管理等。

如:

private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
  return DriverManager.getConnection(DB_URL);
}
};
 
public static Connection getConnection() {
return connectionHolder.get();
}
private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
  Session s = (Session) threadSession.get();
  try {
    if (s == null) {
      s = getSessionFactory().openSession();
      threadSession.set(s);
    }
  } catch (HibernateException ex) {
    throw new InfrastructureException(ex);
  }
  return s;
}

看完上述內(nèi)容,你們對(duì)ThreadLocal怎么在Java中使用 有進(jìn)一步的了解嗎?如果還想了解更多知識(shí)或者相關(guān)內(nèi)容,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。

網(wǎng)站標(biāo)題:ThreadLocal怎么在Java中使用
轉(zhuǎn)載來于:http://sd-ha.com/article36/jososg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、服務(wù)器托管、網(wǎng)站制作外貿(mào)網(wǎng)站建設(shè)、手機(jī)網(wǎng)站建設(shè)面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)