自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

正確理解Thread Local的原理與適用場(chǎng)景

開發(fā) 開發(fā)工具
寫這篇文章的一個(gè)原因在于,網(wǎng)上很多博客關(guān)于 ThreadLocal 的適用場(chǎng)景以及解決的問題,描述的并不清楚,甚至是錯(cuò)的。本文結(jié)合實(shí)例介紹了Thread Local的原理與實(shí)現(xiàn)方法,并分析了其適用場(chǎng)景。

一、ThreadLocal解決什么問題

由于 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,為表述方便,后文用 變量 代表 ThreadLocal 本身,而用 實(shí)例 代表具體類型(如 StringBuidler )的實(shí)例。

1. 不恰當(dāng)?shù)睦斫?/strong>

寫這篇文章的一個(gè)原因在于,網(wǎng)上很多博客關(guān)于 ThreadLocal 的適用場(chǎng)景以及解決的問題,描述的并不清楚,甚至是錯(cuò)的。下面是常見的對(duì)于 ThreadLocal的介紹

  • ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路
  • ThreadLocal的目的是為了解決多線程訪問資源時(shí)的共享問題

還有很多文章在對(duì)比 ThreadLocal 與 synchronize 的異同。既然是作比較,那應(yīng)該是認(rèn)為這兩者解決相同或類似的問題。

上面的描述,問題在于,ThreadLocal 并不解決多線程 共享 變量的問題。既然變量不共享,那就更談不上同步的問題。

2. 合理的理解

ThreadLoal 變量,它的基本原理是,同一個(gè) ThreadLocal 所包含的對(duì)象(對(duì)ThreadLocal< String >而言即為 String 類型變量),在不同的 Thread 中有不同的副本(實(shí)際是不同的實(shí)例,后文會(huì)詳細(xì)闡述)。這里有幾點(diǎn)需要注意

  • 因?yàn)槊總€(gè) Thread 內(nèi)有自己的實(shí)例副本,且該副本只能由當(dāng)前 Thread 使用。這是也是 ThreadLocal 命名的由來
  • 既然每個(gè) Thread 有自己的實(shí)例副本,且其它 Thread 不可訪問,那就不存在多線程間共享的問題
  • 既無共享,何來同步問題,又何來解決同步問題一說?

那 ThreadLocal 到底解決了什么問題,又適用于什么樣的場(chǎng)景?

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

核心意思是

ThreadLocal 提供了線程本地的實(shí)例。它與普通變量的區(qū)別在于,每個(gè)使用該變量的線程都會(huì)初始化一個(gè)完全獨(dú)立的實(shí)例副本。ThreadLocal 變量通常被private static修飾。當(dāng)一個(gè)線程結(jié)束時(shí),它所使用的所有 ThreadLocal 相對(duì)的實(shí)例副本都可被回收。

總的來說,ThreadLocal 適用于每個(gè)線程需要自己獨(dú)立的實(shí)例且該實(shí)例需要在多個(gè)方法中被使用,也即變量在線程間隔離而在方法或類間共享的場(chǎng)景。后文會(huì)通過實(shí)例詳細(xì)闡述該觀點(diǎn)。另外,該場(chǎng)景下,并非必須使用 ThreadLocal ,其它方式完全可以實(shí)現(xiàn)同樣的效果,只是 ThreadLocal 使得實(shí)現(xiàn)更簡(jiǎn)潔。

二、ThreadLocal用法

1. 實(shí)例代碼

下面通過如下代碼說明 ThreadLocal 的使用方式

  1. public class ThreadLocalDemo { 
  2.   public static void main(String[] args) throws InterruptedException { 
  3.     int threads = 3
  4.     CountDownLatch countDownLatch = new CountDownLatch(threads); 
  5.     InnerClass innerClass = new InnerClass(); 
  6.     for(int i = 1; i <= threads; i++) { 
  7.       new Thread(() -> { 
  8.         for(int j = 0; j < 4; j++) { 
  9.           innerClass.add(String.valueOf(j)); 
  10.           innerClass.print(); 
  11.         } 
  12.         innerClass.set("hello world"); 
  13.         countDownLatch.countDown(); 
  14.       }, "thread - " + i).start(); 
  15.     } 
  16.     countDownLatch.await(); 
  17.   } 
  18.   private static class InnerClass { 
  19.     public void add(String newStr) { 
  20.       StringBuilder str = Counter.counter.get(); 
  21.       Counter.counter.set(str.append(newStr)); 
  22.     } 
  23.     public void print() { 
  24.       System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", 
  25.       Thread.currentThread().getName(), 
  26.       Counter.counter.hashCode(), 
  27.       Counter.counter.get().hashCode(), 
  28.       Counter.counter.get().toString()); 
  29.     } 
  30.     public void set(String words) { 
  31.       Counter.counter.set(new StringBuilder(words)); 
  32.       System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s,  Instance hashcode:%s, Value:%s\n", 
  33.       Thread.currentThread().getName(), 
  34.       Counter.counter.hashCode(), 
  35.       Counter.counter.get().hashCode(), 
  36.       Counter.counter.get().toString()); 
  37.     } 
  38.   } 
  39.   private static class Counter { 
  40.     private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() { 
  41.       @Override 
  42.       protected StringBuilder initialValue() { 
  43.         return new StringBuilder(); 
  44.       } 
  45.     }; 
  46.   } 

2. 實(shí)例分析

ThreadLocal本身支持范型。該例使用了 StringBuilder 類型的 ThreadLocal 變量??赏ㄟ^ ThreadLocal 的 get() 方法讀取 StringBuidler 實(shí)例,也可通過 set(T t) 方法設(shè)置 StringBuilder。

上述代碼執(zhí)行結(jié)果如下

  1. Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0 
  2. Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0 
  3. Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0 
  4. Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:01 
  5. Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:01 
  6. Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:012 
  7. Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0123 
  8. Set, Thread name:thread - 3 , ThreadLocal hashcode:372282300,  Instance hashcode:1362597339, Value:hello world 
  9. Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:01 
  10. Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:012 
  11. Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:012 
  12. Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0123 
  13. Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0123 
  14. Set, Thread name:thread - 1 , ThreadLocal hashcode:372282300,  Instance hashcode:482932940, Value:hello world 
  15. Set, Thread name:thread - 2 , ThreadLocal hashcode:372282300,  Instance hashcode:1691922941, Value:hello world 

從上面的輸出可看出

  • 從第1-3行輸出可見,每個(gè)線程通過 ThreadLocal 的 get() 方法拿到的是不同的 StringBuilder 實(shí)例
  • 第1-3行輸出表明,每個(gè)線程所訪問到的是同一個(gè) ThreadLocal 變量
  • 從7、12、13行輸出以及第30行代碼可見,雖然從代碼上都是對(duì) Counter 類的靜態(tài) counter 字段進(jìn)行 get() 得到 StringBuilder 實(shí)例并追加字符串,但是這并不會(huì)將所有線程追加的字符串都放進(jìn)同一個(gè) StringBuilder 中,而是每個(gè)線程將字符串追加進(jìn)各自的 StringBuidler 實(shí)例內(nèi)
  • 對(duì)比第1行與第15行輸出并結(jié)合第38行代碼可知,使用 set(T t) 方法后,ThreadLocal 變量所指向的 StringBuilder 實(shí)例被替換

三、ThreadLocal原理

1. ThreadLocal維護(hù)線程與實(shí)例的映射

既然每個(gè)訪問 ThreadLocal 變量的線程都有自己的一個(gè)“本地”實(shí)例副本。一個(gè)可能的方案是 ThreadLocal 維護(hù)一個(gè) Map,鍵是 Thread,值是它在該 Thread 內(nèi)的實(shí)例。線程通過該 ThreadLocal 的 get() 方案獲取實(shí)例時(shí),只需要以線程為鍵,從 Map 中找出對(duì)應(yīng)的實(shí)例即可。該方案如下圖所示

該方案可滿足上文提到的每個(gè)線程內(nèi)一個(gè)獨(dú)立備份的要求。每個(gè)新線程訪問該 ThreadLocal 時(shí),需要向 Map 中添加一個(gè)映射,而每個(gè)線程結(jié)束時(shí),應(yīng)該清除該映射。這里就有兩個(gè)問題:

  • 增加線程與減少線程均需要寫 Map,故需保證該 Map 線程安全。雖然從ConcurrentHashMap的演進(jìn)看Java多線程核心技術(shù)一文介紹了幾種實(shí)現(xiàn)線程安全 Map 的方式,但它或多或少都需要鎖來保證線程的安全性
  • 線程結(jié)束時(shí),需要保證它所訪問的所有 ThreadLocal 中對(duì)應(yīng)的映射均刪除,否則可能會(huì)引起內(nèi)存泄漏。(后文會(huì)介紹避免內(nèi)存泄漏的方法)

其中鎖的問題,是 JDK 未采用該方案的一個(gè)原因。

2. Thread維護(hù)ThreadLocal與實(shí)例的映射

上述方案中,出現(xiàn)鎖的問題,原因在于多線程訪問同一個(gè) Map。如果該 Map 由 Thread 維護(hù),從而使得每個(gè) Thread 只訪問自己的 Map,那就不存在多線程寫的問題,也就不需要鎖。該方案如下圖所示。

ThreadLocal side Map

該方案雖然沒有鎖的問題,但是由于每個(gè)線程訪問某 ThreadLocal 變量后,都會(huì)在自己的 Map 內(nèi)維護(hù)該 ThreadLocal 變量與具體實(shí)例的映射,如果不刪除這些引用(映射),則這些 ThreadLocal 不能被回收,可能會(huì)造成內(nèi)存泄漏。后文會(huì)介紹 JDK 如何解決該問題。

四、ThreadLocal 在 JDK 8 中的實(shí)現(xiàn)

1. ThreadLocalMap與內(nèi)存泄漏

該方案中,Map 由 ThreadLocal 類的靜態(tài)內(nèi)部類 ThreadLocalMap 提供。該類的實(shí)例維護(hù)某個(gè) ThreadLocal 與具體實(shí)例的映射。與 HashMap 不同的是,ThreadLocalMap 的每個(gè) Entry 都是一個(gè)對(duì) 鍵 的弱引用,這一點(diǎn)從super(k)可看出。另外,每個(gè) Entry 都包含了一個(gè)對(duì) 值 的強(qiáng)引用。

  1. static class Entry extends WeakReference<ThreadLocal<?>> { 
  2.   /** The value associated with this ThreadLocal. */ 
  3.   Object value; 
  4.   Entry(ThreadLocal<?> k, Object v) { 
  5.     super(k); 
  6.     vvalue = v; 
  7.   } 

使用弱引用的原因在于,當(dāng)沒有強(qiáng)引用指向 ThreadLocal 變量時(shí),它可被回收,從而避免上文所述 ThreadLocal 不能被回收而造成的內(nèi)存泄漏的問題。

但是,這里又可能出現(xiàn)另外一種內(nèi)存泄漏的問題。ThreadLocalMap 維護(hù) ThreadLocal 變量與具體實(shí)例的映射,當(dāng) ThreadLocal 變量被回收后,該映射的鍵變?yōu)?null,該 Entry 無法被移除。從而使得實(shí)例被該 Entry 引用而無法被回收造成內(nèi)存泄漏。

注:Entry雖然是弱引用,但它是 ThreadLocal 類型的弱引用(也即上文所述它是對(duì) 鍵 的弱引用),而非具體實(shí)例的的弱引用,所以無法避免具體實(shí)例相關(guān)的內(nèi)存泄漏。

2. 讀取實(shí)例

讀取實(shí)例方法如下所示

  1. public T get() { 
  2.   Thread t = Thread.currentThread(); 
  3.   ThreadLocalMap map = getMap(t); 
  4.   if (map != null) { 
  5.     ThreadLocalMap.Entry e = map.getEntry(this); 
  6.     if (e != null) { 
  7.       @SuppressWarnings("unchecked") 
  8.       T result = (T)e.value; 
  9.       return result; 
  10.     } 
  11.   } 
  12.   return setInitialValue(); 

讀取實(shí)例時(shí),線程首先通過getMap(t)方法獲取自身的 ThreadLocalMap。從如下該方法的定義可見,該 ThreadLocalMap 的實(shí)例是 Thread 類的一個(gè)字段,即由 Thread 維護(hù) ThreadLocal 對(duì)象與具體實(shí)例的映射,這一點(diǎn)與上文分析一致。

  1. ThreadLocalMap getMap(Thread t) { 
  2.   return t.threadLocals; 

獲取到 ThreadLocalMap 后,通過map.getEntry(this)方法獲取該 ThreadLocal 在當(dāng)前線程的 ThreadLocalMap 中對(duì)應(yīng)的 Entry。該方法中的 this 即當(dāng)前訪問的 ThreadLocal 對(duì)象。

如果獲取到的 Entry 不為 null,從 Entry 中取出值即為所需訪問的本線程對(duì)應(yīng)的實(shí)例。如果獲取到的 Entry 為 null,則通過setInitialValue()方法設(shè)置該 ThreadLocal 變量在該線程中對(duì)應(yīng)的具體實(shí)例的初始值。

3. 設(shè)置初始值

設(shè)置初始值方法如下

  1. private T setInitialValue() { 
  2.   T value = initialValue(); 
  3.   Thread t = Thread.currentThread(); 
  4.   ThreadLocalMap map = getMap(t); 
  5.   if (map != null) 
  6.     map.set(this, value); 
  7.   else 
  8.     createMap(t, value); 
  9.   return value; 

該方法為 private 方法,無法被重載。

首先,通過initialValue()方法獲取初始值。該方法為 public 方法,且默認(rèn)返回 null。所以典型用法中常常重載該方法。上例中即在內(nèi)部匿名類中將其重載。

然后拿到該線程對(duì)應(yīng)的 ThreadLocalMap 對(duì)象,若該對(duì)象不為 null,則直接將該 ThreadLocal 對(duì)象與對(duì)應(yīng)實(shí)例初始值的映射添加進(jìn)該線程的 ThreadLocalMap中。若為 null,則先創(chuàng)建該 ThreadLocalMap 對(duì)象再將映射添加其中。

這里并不需要考慮 ThreadLocalMap 的線程安全問題。因?yàn)槊總€(gè)線程有且只有一個(gè) ThreadLocalMap 對(duì)象,并且只有該線程自己可以訪問它,其它線程不會(huì)訪問該 ThreadLocalMap,也即該對(duì)象不會(huì)在多個(gè)線程中共享,也就不存在線程安全的問題。

4. 設(shè)置實(shí)例

除了通過initialValue()方法設(shè)置實(shí)例的初始值,還可通過 set 方法設(shè)置線程內(nèi)實(shí)例的值,如下所示。

  1. public void set(T value) { 
  2.   Thread t = Thread.currentThread(); 
  3.   ThreadLocalMap map = getMap(t); 
  4.   if (map != null) 
  5.     map.set(this, value); 
  6.   else 
  7.     createMap(t, value); 

該方法先獲取該線程的 ThreadLocalMap 對(duì)象,然后直接將 ThreadLocal 對(duì)象(即代碼中的 this)與目標(biāo)實(shí)例的映射添加進(jìn) ThreadLocalMap 中。當(dāng)然,如果映射已經(jīng)存在,就直接覆蓋。另外,如果獲取到的 ThreadLocalMap 為 null,則先創(chuàng)建該 ThreadLocalMap 對(duì)象。

5. 防止內(nèi)存泄漏

對(duì)于已經(jīng)不再被使用且已被回收的 ThreadLocal 對(duì)象,它在每個(gè)線程內(nèi)對(duì)應(yīng)的實(shí)例由于被線程的 ThreadLocalMap 的 Entry 強(qiáng)引用,無法被回收,可能會(huì)造成內(nèi)存泄漏。

針對(duì)該問題,ThreadLocalMap 的 set 方法中,通過 replaceStaleEntry 方法將所有鍵為 null 的 Entry 的值設(shè)置為 null,從而使得該值可被回收。另外,會(huì)在 rehash 方法中通過 expungeStaleEntry 方法將鍵和值為 null 的 Entry 設(shè)置為 null 從而使得該 Entry 可被回收。通過這種方式,ThreadLocal 可防止內(nèi)存泄漏。

  1. private void set(ThreadLocal<?> key, Object value) { 
  2.   Entry[] tab = table
  3.   int len = tab.length; 
  4.   int i = key.threadLocalHashCode & (len-1); 
  5.   for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 
  6.     ThreadLocal<?> k = e.get(); 
  7.     if (k == key) { 
  8.       e.value = value; 
  9.       return; 
  10.     } 
  11.     if (k == null) { 
  12.       replaceStaleEntry(key, value, i); 
  13.       return; 
  14.     } 
  15.   } 
  16.   tab[i] = new Entry(key, value); 
  17.   int sz = ++size; 
  18.   if (!cleanSomeSlots(i, sz) && sz >= threshold) 
  19.     rehash(); 

五、適用場(chǎng)景

如上文所述,ThreadLocal 適用于如下兩種場(chǎng)景

  • 每個(gè)線程需要有自己?jiǎn)为?dú)的實(shí)例
  • 實(shí)例需要在多個(gè)方法中共享,但不希望被多線程共享

對(duì)于***點(diǎn),每個(gè)線程擁有自己實(shí)例,實(shí)現(xiàn)它的方式很多。例如可以在線程內(nèi)部構(gòu)建一個(gè)單獨(dú)的實(shí)例。ThreadLoca 可以以非常方便的形式滿足該需求。

對(duì)于第二點(diǎn),可以在滿足***點(diǎn)(每個(gè)線程有自己的實(shí)例)的條件下,通過方法間引用傳遞的形式實(shí)現(xiàn)。ThreadLocal 使得代碼耦合度更低,且實(shí)現(xiàn)更優(yōu)雅。

六、案例

對(duì)于 Java Web 應(yīng)用而言,Session 保存了很多信息。很多時(shí)候需要通過 Session 獲取信息,有些時(shí)候又需要修改 Session 的信息。一方面,需要保證每個(gè)線程有自己?jiǎn)为?dú)的 Session 實(shí)例。另一方面,由于很多地方都需要操作 Session,存在多方法共享 Session 的需求。如果不使用 ThreadLocal,可以在每個(gè)線程內(nèi)構(gòu)建一個(gè) Session實(shí)例,并將該實(shí)例在多個(gè)方法間傳遞,如下所示。

  1. public class SessionHandler { 
  2.   @Data 
  3.   public static class Session { 
  4.     private String id; 
  5.     private String user; 
  6.     private String status; 
  7.   } 
  8.   public Session createSession() { 
  9.     return new Session(); 
  10.   } 
  11.   public String getUser(Session session) { 
  12.     return session.getUser(); 
  13.   } 
  14.   public String getStatus(Session session) { 
  15.     return session.getStatus(); 
  16.   } 
  17.   public void setStatus(Session session, String status) { 
  18.     session.setStatus(status); 
  19.   } 
  20.   public static void main(String[] args) { 
  21.     new Thread(() -> { 
  22.       SessionHandler handler = new SessionHandler(); 
  23.       Session session = handler.createSession(); 
  24.       handler.getStatus(session); 
  25.       handler.getUser(session); 
  26.       handler.setStatus(session, "close"); 
  27.       handler.getStatus(session); 
  28.     }).start(); 
  29.   } 

該方法是可以實(shí)現(xiàn)需求的。但是每個(gè)需要使用 Session 的地方,都需要顯式傳遞 Session 對(duì)象,方法間耦合度較高。

這里使用 ThreadLocal 重新實(shí)現(xiàn)該功能如下所示。

  1. public class SessionHandler { 
  2.   public static ThreadLocal<Session> session = new ThreadLocal<Session>(); 
  3.   @Data 
  4.   public static class Session { 
  5.     private String id; 
  6.     private String user; 
  7.     private String status; 
  8.   } 
  9.   public void createSession() { 
  10.     session.set(new Session()); 
  11.   } 
  12.   public String getUser() { 
  13.     return session.get().getUser(); 
  14.   } 
  15.   public String getStatus() { 
  16.     return session.get().getStatus(); 
  17.   } 
  18.   public void setStatus(String status) { 
  19.     session.get().setStatus(status); 
  20.   } 
  21.   public static void main(String[] args) { 
  22.     new Thread(() -> { 
  23.       SessionHandler handler = new SessionHandler(); 
  24.       handler.getStatus(); 
  25.       handler.getUser(); 
  26.       handler.setStatus("close"); 
  27.       handler.getStatus(); 
  28.     }).start(); 
  29.   } 

使用 ThreadLocal 改造后的代碼,不再需要在各個(gè)方法間傳遞 Session 對(duì)象,并且也非常輕松的保證了每個(gè)線程擁有自己獨(dú)立的實(shí)例。

如果單看其中某一點(diǎn),替代方法很多。比如可通過在線程內(nèi)創(chuàng)建局部變量可實(shí)現(xiàn)每個(gè)線程有自己的實(shí)例,使用靜態(tài)變量可實(shí)現(xiàn)變量在方法間的共享。但如果要同時(shí)滿足變量在線程間的隔離與方法間的共享,ThreadLocal再合適不過。

七、總結(jié)

  • ThreadLocal 并不解決線程間共享數(shù)據(jù)的問題
  • ThreadLocal 通過隱式的在不同線程內(nèi)創(chuàng)建獨(dú)立實(shí)例副本避免了實(shí)例線程安全的問題
  • 每個(gè)線程持有一個(gè) Map 并維護(hù)了 ThreadLocal 對(duì)象與具體實(shí)例的映射,該 Map 由于只被持有它的線程訪問,故不存在線程安全以及鎖的問題
  • ThreadLocalMap 的 Entry 對(duì) ThreadLocal 的引用為弱引用,避免了 ThreadLocal 對(duì)象無法被回收的問題
  • ThreadLocalMap 的 set 方法通過調(diào)用 replaceStaleEntry 方法回收鍵為 null 的 Entry 對(duì)象的值(即為具體實(shí)例)以及 Entry 對(duì)象本身從而防止內(nèi)存泄漏
  • ThreadLocal 適用于變量在線程間隔離且在方法間共享的場(chǎng)景

【本文為51CTO專欄作者“郭俊”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】

戳這里,看該作者更多好文

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2020-01-07 15:10:32

Linuxinode命令

2009-06-18 10:29:24

Hibernate I

2009-12-14 17:48:46

Ruby String

2009-11-26 09:42:38

VS2003插件

2009-12-03 18:07:47

PHP轉(zhuǎn)義

2009-12-04 18:00:46

PHP開發(fā)MVC模型

2010-02-01 10:54:37

C++框架

2010-07-20 12:35:33

SQL Server索

2024-01-29 00:35:00

Go并發(fā)開發(fā)

2023-12-27 19:52:08

Go模塊命令

2010-02-04 15:05:00

C++ cpuid指令

2009-12-09 14:04:45

PHP include

2010-01-18 17:29:35

VB.NET函數(shù)調(diào)用

2010-08-05 09:14:29

DB2隔離級(jí)別

2009-12-07 14:53:13

PHP抽象類應(yīng)用

2009-12-16 17:00:43

Ruby on Rai

2009-12-16 10:33:31

Ruby更新文件

2013-08-06 10:40:38

大數(shù)據(jù)數(shù)據(jù)

2009-12-04 17:16:41

PHP析構(gòu)函數(shù)

2009-12-17 11:36:55

Ruby輸入輸出
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)