java多執行緒、FutureTask的用法及兩種常用的使用場景
Java多執行緒實現的方式有四種
- 1.繼承Thread類,重寫run方法
- 2.實現Runnable介面,重寫run方法,實現Runnable介面的實現類的例項物件作為Thread建構函式的target
- 3.通過Callable和FutureTask建立執行緒
4.通過執行緒池建立執行緒
後面兩種可以歸結成一類:有返回值,通過Callable介面,就要實現call方法,這個方法的返回值是Object,所以返回的結果可以放在Object物件中
//繼承Thread類實現多執行緒 class MyThread extends Thread{ private int index; public MyThread(int index){ this.index = index; } public void run(){ System.out.println("子執行緒:" + index + ",開始!"); } } //開啟多執行緒 for(int i = 0;i < 10;i++){ final int index = i; //實現runnable介面實現多執行緒new Thread(new Runnable() { @Override public void run() { System.out.println("子執行緒:" + index + ",開始!"); } }).start(); new MyThread(index).start(); }
下面重點介紹FutureTask的用法:
FutureTask可用於非同步獲取執行結果或取消執行任務的場景。通過傳入Runnable或者Callable的任務給FutureTask,直接呼叫其run方法或者放入執行緒池執行,之後可以在外部通過FutureTask的get方法非同步獲取執行結果,因此,FutureTask非常適合用於耗時的計算,主執行緒可以在完成自己的任務後,再去獲取結果。另外,FutureTask還可以確保即使呼叫了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。
1. FutureTask執行多工計算的使用場景
利用FutureTask和ExecutorService,可以用多執行緒的方式提交計算任務,主執行緒繼續執行其他任務,當主執行緒需要子執行緒的計算結果時,在非同步獲取子執行緒的執行結果。
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); List<FutureTask<Integer>> futureTaskList = new ArrayList<FutureTask<Integer>>(); for(int i = 0;i < 10 ;i ++){ final int index = i; FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() { private Integer result = 0; @Override public Integer call() throws Exception { for(int j = 0;j < 100;j++){ result += j; } Thread.sleep(5000); System.out.println("子執行緒:" + index + ",執行完成!"); return result; } }); futureTaskList.add(ft); executorService.submit(ft); } System.out.println("子執行緒提交完畢,主執行緒繼續執行!"); try { Thread.sleep(1000 * 10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主執行緒執行完畢!"); Integer totalResult = 0; for(FutureTask<Integer> ft : futureTaskList){ try { totalResult =+ ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } System.out.println("子執行緒計算的結果為:" + totalResult);
2. FutureTask在高併發環境下確保任務只執行一次
在很多高併發的環境下,往往我們只需要某些任務只執行一次。這種使用情景FutureTask的特性恰能勝任。舉一個例子,假設有一個帶key的連線池,當key存在時,即直接返回key對應的物件;當key不存在時,則建立連線。對於這樣的應用場景,通常採用的方法為使用一個Map物件來儲存key和連線池對應的對應關係,典型的程式碼如下面所示:
private Map<String, Connection> connectionPool = new HashMap<String, Connection>(); private ReentrantLock lock = new ReentrantLock(); public Connection getConnection(String key){ try{ lock.lock(); if(connectionPool.containsKey(key)){ return connectionPool.get(key); } else{ //建立 Connection Connection conn = createConnection(); connectionPool.put(key, conn); return conn; } } finally{ lock.unlock(); } } //建立Connection private Connection createConnection(){ return null; }
在上面的例子中,我們通過加鎖確保高併發環境下的執行緒安全,也確保了connection只建立一次,然而確犧牲了效能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,效能大大提高,但是在高併發的情況下有可能出現Connection被建立多次的現象。這時最需要解決的問題就是當key不存在時,建立Connection的動作能放在connectionPool之後執行,這正是FutureTask發揮作用的時機,基於ConcurrentHashMap和FutureTask的改造程式碼如下:
private ConcurrentHashMap<String,FutureTask<Connection>>connectionPool = new ConcurrentHashMap<String, FutureTask<Connection>>(); public Connection getConnection(String key) throws Exception{ FutureTask<Connection>connectionTask=connectionPool.get(key); if(connectionTask!=null){ return connectionTask.get(); } else{ Callable<Connection> callable = new Callable<Connection>(){ @Override public Connection call() throws Exception { // TODO Auto-generated method stub return createConnection(); } }; FutureTask<Connection>newTask = new FutureTask<Connection>(callable); connectionTask = connectionPool.putIfAbsent(key, newTask); if(connectionTask==null){ connectionTask = newTask; connectionTask.run(); } return connectionTask.get(); } } //建立Connection private Connection createConnection(){ return null; }
經過這樣的改造,可以避免由於併發帶來的多次建立連線及鎖的出現。