1. 程式人生 > >java多執行緒、FutureTask的用法及兩種常用的使用場景

java多執行緒、FutureTask的用法及兩種常用的使用場景

Java多執行緒實現的方式有四種

  • 1.繼承Thread類,重寫run方法
  • 2.實現Runnable介面,重寫run方法,實現Runnable介面的實現類的例項物件作為Thread建構函式的target
  • 3.通過Callable和FutureTask建立執行緒
  • 4.通過執行緒池建立執行緒

前面兩種可以歸結為一類:無返回值,原因很簡單,通過重寫run方法,run方式的返回值是void,所以沒有辦法返回結果 

後面兩種可以歸結成一類:有返回值,通過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;
}

經過這樣的改造,可以避免由於併發帶來的多次建立連線及鎖的出現。