無論如何,要編寫一個多執行緒安全(thread-safe)的程式總是困難的,為了使用的共用資源,您必須小心
的對共用資源進行同步,同步帶來一定的效
能延遲,而另一方面,在處理同步的時候,又要注意物件的鎖定與釋放,避免產生死結,種種因素都使得編寫多執行緒程式變得困難。
Thread-Specific
Storage模式嘗試從另一個角度來解釋多執行緒共用資源的問題,其思考點很簡單,即然共用資源這麼困難,那麼就乾脆不要共用,何不為每個執行緒創造一
個資源的複本,將每一個執行緒存取資料的行為加以隔離,其實現的方法,就是給予每一個執行緒一個特定空間來保管該執行緒所獨享的資源,也因此而稱之為
Thread- Specific Storage模式。
在Java中可以使用java.lang.ThreadLocal來實現這個模式,這個類別是從1.2之後開始提供,不過先來看看,如何自行實
現一個簡單的ThreadLocal類別:
import java.util.*;
public class ThreadLocal { private Map storage = Collections.synchronizedMap(new HashMap());
public Object get() { Thread current = Thread.currentThread(); Object o = storage.get(current);
if(o == null && !storage.containsKey(current)) { o = initialValue(); storage.put(current, o); }
return o; }
public void set(Object o) { storage.put(Thread.currentThread(), o); }
public Object initialValue() { return null; } }
可以看到程式中使用執行緒本身作為key值,並將所獲得的資源放在Map物件中,如果第一次使用get(),也配置一個空間給執行緒,而
initialValue()可以用來設定什麼樣的初值要先儲存在這個空間中,在這邊先簡單的設定為null。
現在假設有一個原先在單執行緒環境下的資源SomeResource,現在考慮要該其在多執行緒環境下使用,若不想考慮複雜的執行緒共用互斥問題,此時可以使用ThreadLocal類別來使用SomeResource,例如:
public class Resource { private static final ThreadLocal threadLocal = new ThreadLocal();
public static SomeResource getResource() { SomeResource resource = (SomeResource) threadLocal.get();
if(resource == null) { resource = new SomeResource(); threadLocal.set(resource); }
return resource; } }
上面所實作的ThreadLocal類別只是一個簡單的示範,您可以使用java.lang.ThreadLocal來實
現Thread- Specific
Storage模式,以獲得更好的效能,在這邊簡單的示範一個Log程式,它可以記錄每個執行緒的活動,所使用的是
java.util.logging中的類別:
import java.io.*; import java.util.logging.*; public class SimpleThreadLogger { private static final ThreadLocal threadLocal = new ThreadLocal();
public static void log(String msg) { getThreadLogger().log(Level.INFO, msg); }
private static Logger getThreadLogger() { Logger logger = (Logger) threadLocal.get();
if(logger == null) { try { logger = Logger.getLogger( Thread.currentThread().getName()); // Logger 預設是在主控台輸出 // 我們加入一個檔案輸出的Handler // 它會輸出XML的記錄文件 logger.addHandler( new FileHandler( Thread.currentThread().getName() + ".log")); } catch(IOException e) {}
threadLocal.set(logger); }
return logger; } }
可以使用下面這個程式來測試:
public class LoggerTest { public static void main(String[] args) { new TestThread("thread1").start(); new TestThread("thread2").start(); new TestThread("thread3").start(); } }
class TestThread extends Thread { public TestThread(String name) { super(name); }
public void run() { for(int i = 0; i < 10; i++) { SimpleThreadLogger.log(getName() + ": message " + i); try { Thread.sleep(1000); } catch(Exception e) { SimpleThreadLogger.log(e.toString()); } } } }
執行LoggerTest可以在主控台上看到輸出,並可以在同一目錄下找到三個log檔,分別記錄了三個執行緒的活動,透過
ThreadLocal,不用撰寫複雜的執行緒共用互斥邏輯。
Thread-Specific
Storage模式的意義之一,就是「有時不共用是好的」,如果共用會產生危險,那就不要共用,當然,這種方式所犧牲掉的就是空間,您必須為每一個執行
緒保留它們獨立的空間,這是一種以空間換取時間與安全性的方法。
|