在Java中似乎沒有提供帶運行參數的線程實現類,在第三方類庫中也沒有找到。網上有大量的文章在討論這個問題,但都沒有提供很好的代碼封裝解決方案,這令我很吃驚。如果讀者知道有官方或者第三方的實現方式,歡迎留言說明。本文最后給出了一種實現帶運行參數的線程實現類。
在C#的基礎類庫中早就提供了相關的解決方案,如下是C#幾種常見的帶參數子線程創建方法:
Thread th = new Thread((param) => { Console.WriteLine(param); }); th.Start(i); Task.Factory.StartNew((param) => { Console.WriteLine(param); }, i); ThreadPool.QueueUserWorkItem((param) => { Console.WriteLine(param); }, i);
讓我們從一個實際編碼問題開始講起,主線程循環一個集合元素,並創建子線程中做相應的處理(可能比較耗時)。下面是最初的一段實現代碼,請問這段代碼存在什么問題?
for (int i = 0; i < 100; i++) { Thread th = new Thread(new Runnable() { @Override public void run() { System.out.println(i + ""); } }); th.start(); }
(1)首先這段程序是無法通過編譯的,在intellij idea中提示“Variable 'i' is accessed from within inner class,needs to be final or effectively final”,在Eclipse中提示"Local variable i defined in an enclosing scope must be final or effectively final",意思是說在內部類中無法訪問外部類中不是final修飾的成員變量。那么我們很容易想通過下面的方式解決:
for (int i = 0; i < 100; i++) { int p = i; Thread th = new Thread(new Runnable() { @Override public void run() { System.out.println(p + ""); } }); th.start(); }
這段代碼能夠通過編譯,而且似乎運行良好。但是不是線程安全的,父線程中的循環變量不斷被修改,子線程得到的父線程成員變量可能是不正確的。
(2)其次上面的代碼在循環體內創建了大量的子線程,線程的創建和銷毀會造成系統資源的開銷,一般推薦使用線程池的方式創建線程,比如ThreadPoolExecutor。
ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); for (int i = 0; i < 100; i++) { int p=i; executor.execute(()->{ System.out.println(p + ""); }); }
那么有什么辦法使得子線程能夠安全的獲取到父線程的變量呢,我們可以編寫如下的線程實現類:
public class MyRunnable implements Runnable { Object param; public MyRunnable(Object parameter) { this.param = parameter; } @Override public void run() { System.out.println(param.toString()); } }
這里的問題是我們必須針對不同的情形,編寫不同的子線程實現類,在各個工程中分散了很多類似的腳手架代碼,聞到這種“味道”,我們應該想到需要進行代碼抽象和封裝,以便於重復使用。據此,筆者用Java封裝了一個帶參數的線程類:
/** * ParameterizedThreadStart defines the start method for starting a thread. * @author wadexmy * @param <T> */ public interface ParameterizedThreadStart<T>{ /** * a method with parameter * @param context */ void run(T context); }
/** * ParameterizedThread defines a thread with a generic parameter * @author wadexmy * @param <T> */ public class ParameterizedThread<T> implements Runnable{ private T context; private ParameterizedThreadStart<T> parameterStart; /** * Constructor * @param context */ public ParameterizedThread(T context,ParameterizedThreadStart<T> parameterStart){ this.context=context; this.parameterStart=parameterStart; } /** * getContext returns the context of current thread. * @return */ public T getContext(){ return context; } /** * run method to be called in that separately executing thread. */ @Override public void run() { parameterStart.run(context); } }
類ParameterizedThread實現了 Runnable,在構造方法中傳遞了一個參數和需要執行的方法。可以通過下面的代碼測試這個類:
ThreadPoolExecutor executor = new ThreadPoolExecutor(6, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); for (int i = 0; i < 100; i++) { executor.execute(new ParameterizedThread<>(i, (p) -> { System.out.println(p.toString()); })); }