java.util.concurrent包

來源:互聯網
上載者:User

講到Java多線程,大多數人腦海中跳出來的是Thread、Runnable、synchronized……這些是最基本的東西,雖然已經足夠強大,但想要用好還真不容易。從JDK1.5開始,增加了java.util.concurrent包,它的引入大大簡化了多線程程式的開發(要感謝一下大牛Doug Lee)。 

java.util.concurrent包分成了三個部分,分別是java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.lock。內容涵蓋了並發集合類、線程池機制、同步互斥機制、安全執行緒的變數更新工具類、鎖等等常用工具。 

為了便於理解,本文使用一個例子來做說明,交代一下它的情境:

假設要對一套10個節點群組成的環境進行檢查,這個環境有兩個進入點,通過節點間的依賴關係可以遍曆到整個環境。依賴關係可以構成一張有向圖,可能存在環。為了提高檢查的效率,考慮使用多線程。 

1、Executors

通過這個類能夠獲得多種線程池的執行個體,例如可以調用newSingleThreadExecutor()獲得單線程的ExecutorService,調用newFixedThreadPool()獲得固定大小線程池的ExecutorService。拿到ExecutorService可以做的事情就比較多了,最簡單的是用它來執行Runnable對象,也可以執行一些實現了Callable<T>的對象。用Thread的start()方法沒有傳回值,如果該線程執行的方法有傳回值那用ExecutorService就再好不過了,可以選擇submit()、invokeAll()或者invokeAny(),根據具體情況選擇合適的方法即可。

import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;import java.util.concurrent.TimeUnit;/** * 線程池服務類 *  * @author DigitalSonic */public class ThreadPoolService {    /**     * 預設線程池大小     */    public static final int  DEFAULT_POOL_SIZE    = 5;    /**     * 預設一個任務的逾時時間,單位為毫秒     */    public static final long DEFAULT_TASK_TIMEOUT = 1000;    private int              poolSize             = DEFAULT_POOL_SIZE;    private ExecutorService  executorService;    /**     * 根據給定大小建立線程池     */    public ThreadPoolService(int poolSize) {        setPoolSize(poolSize);    }    /**     * 使用線程池中的線程來執行任務     */    public void execute(Runnable task) {        executorService.execute(task);    }    /**     * 線上程池中執行所有給定的任務並取回運行結果,使用預設逾時時間     *      * @see #invokeAll(List, long)     */    public List<Node> invokeAll(List<ValidationTask> tasks) {        return invokeAll(tasks, DEFAULT_TASK_TIMEOUT * tasks.size());    }    /**     * 線上程池中執行所有給定的任務並取回運行結果     *      * @param timeout 以毫秒為單位的逾時時間,小於0表示不設定逾時     * @see java.util.concurrent.ExecutorService#invokeAll(java.util.Collection)     */    public List<Node> invokeAll(List<ValidationTask> tasks, long timeout) {        List<Node> nodes = new ArrayList<Node>(tasks.size());        try {            List<Future<Node>> futures = null;            if (timeout < 0) {                futures = executorService.invokeAll(tasks);            } else {                futures = executorService.invokeAll(tasks, timeout, TimeUnit.MILLISECONDS);            }            for (Future<Node> future : futures) {                try {                    nodes.add(future.get());                } catch (ExecutionException e) {                    e.printStackTrace();                }            }        } catch (InterruptedException e) {            e.printStackTrace();        }        return nodes;    }    /**     * 關閉當前ExecutorService     *      * @param timeout 以毫秒為單位的逾時時間     */    public void destoryExecutorService(long timeout) {        if (executorService != null && !executorService.isShutdown()) {            try {                executorService.awaitTermination(timeout, TimeUnit.MILLISECONDS);            } catch (InterruptedException e) {                e.printStackTrace();            }            executorService.shutdown();        }    }    /**     * 關閉當前ExecutorService,隨後根據poolSize建立新的ExecutorService     */    public void createExecutorService() {        destoryExecutorService(1000);        executorService = Executors.newFixedThreadPool(poolSize);    }    /**     * 調整線程池大小     * @see #createExecutorService()     */    public void setPoolSize(int poolSize) {        this.poolSize = poolSize;        createExecutorService();    }}

invokeAll()和invokeAny()方法。前者會執行給定的所有Callable<T>對象,等所有任務完成後返回一個包含了執行結果的List<Future<T>>,每個Future.isDone()都是true,可以用Future.get()拿到結果;後者只要完成了列表中的任意一個任務就立刻返回,傳回值就是執行結果。

和其他資源一樣,線程池在使用完畢後也需要釋放,用shutdown()方法可以關閉線程池,如果當時池裡還有沒有被執行的任務,它會等待任務執行完畢,在等待期間試圖進入線程池的任務將被拒絕。也可以用shutdownNow()來關閉線程池,它會立刻關閉線程池,沒有執行的任務作為傳回值返回。 

2、Lock

多線程編程中常常要鎖定某個對象,之前會用synchronized來實現,現在又多了另一種選擇,那就是java.util.concurrent.locks。通過Lock能夠實現更靈活的鎖定機制,它還提供了很多synchronized所沒有的功能,例如嘗試獲得鎖(tryLock())。 

使用Lock時需要自己獲得鎖並在使用後手動釋放,這一點與synchronized有所不同,所以通常Lock的使用方式是這樣的

Lock l = ...; l.lock();try {// 執行操作} finally {l.unlock();}

 java.util.concurrent.locks中提供了幾個Lock介面的實作類別,比較常用的應該是ReentrantLock。

講到Lock,就不能不講Conditon,前者代替了synchronized,而後者則代替了Object對象上的wait()、notify()和notifyAll()方法(Condition中提供了await()、signal()和signalAll()方法),當滿足運行條件前掛起線程。Condition是與Lock結合使用的,通過Lock.newCondition()方法能夠建立與Lock綁定的Condition執行個體。JDK的JavaDoc中有一個例子能夠很好地說明Condition的用途及用法:

class BoundedBuffer {   final Lock lock = new ReentrantLock();   final Condition notFull  = lock.newCondition();    final Condition notEmpty = lock.newCondition();    final Object[] items = new Object[100];   int putptr, takeptr, count;   public void put(Object x) throws InterruptedException {     lock.lock();     try {       while (count == items.length)          notFull.await();       items[putptr] = x;        if (++putptr == items.length) putptr = 0;       ++count;       notEmpty.signal();     } finally {       lock.unlock();     }   }   public Object take() throws InterruptedException {     lock.lock();     try {       while (count == 0)          notEmpty.await();       Object x = items[takeptr];        if (++takeptr == items.length) takeptr = 0;       --count;       notFull.signal();       return x;     } finally {       lock.unlock();     }   }  }

3、並發集合類

java.util包中的集合類有的是安全執行緒的,有的則不是,在編寫多線程的程式時使用安全執行緒的類能省去很多麻煩,但這些類的效能如何呢?java.util.concurrent包中提供了幾個並髮結合類,例如ConcurrentHashMap、ConcurrentLinkedQueue和CopyOnWriteArrayList等等,根據不同的使用情境,開發人員可以用它們替換java.util包中的相應集合類。

CopyOnWriteArrayList是ArrayList的一個變體,比較適合用在讀取比較頻繁、修改較少的情況下,因為每次修改都要複製整個底層數組。ConcurrentHashMap中為Map介面增加了一些方法(例如putIfAbsenct()),同時做了些最佳化,下面的代碼中使用ConcurrentHashMap來作為全域節點表,完全無需考慮並發問題。

import java.util.ArrayList;import java.util.List;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * 執行驗證的服務類 *  * @author DigitalSonic */public class ValidationService {/** * 全域節點表 */public static final Map<String, Node> NODE_MAP = new ConcurrentHashMap<String, Node>();private ThreadPoolService threadPoolService;public ValidationService(ThreadPoolService threadPoolService) {this.threadPoolService = threadPoolService;}/** * 給出一個入口節點的WSDL,通過廣度遍曆的方式驗證與其相關的各個節點 *  * @param wsdl 入口節點WSDL */public void validate(List<String> wsdl) {List<String> visitedNodes = new ArrayList<String>();List<String> nextRoundNodes = new ArrayList<String>();nextRoundNodes.addAll(wsdl);while (nextRoundNodes.size() > 0) {List<ValidationTask> tasks = getTasks(nextRoundNodes);List<Node> nodes = threadPoolService.invokeAll(tasks);visitedNodes.addAll(nextRoundNodes);nextRoundNodes.clear();getNextRoundNodes(nodes, visitedNodes, nextRoundNodes);}}private List<String> getNextRoundNodes(List<Node> nodes,List<String> visitedNodes, List<String> nextRoundNodes) {for (Node node : nodes) {for (String wsdl : node.getDependencies()) {if (!visitedNodes.contains(wsdl)) {nextRoundNodes.add(wsdl);}}}return nextRoundNodes;}private List<ValidationTask> getTasks(List<String> nodes) {List<ValidationTask> tasks = new ArrayList<ValidationTask>(nodes.size());for (String wsdl : nodes) {tasks.add(new ValidationTask(wsdl));}return tasks;}}

4、AtomicInteger

對變數的讀寫操作都是原子操作(除了long或者double的變數),但像數實值型別的++--操作不是原子操作,像i++中包含了獲得i的原始值、加1、寫回i、返回原始值,在進行類似i++這樣的操作時如果不進行同步問題就大了。好在java.util.concurrent.atomic為我們提供了很多工具類,可以以原子方式更新變數。 

以AtomicInteger為例,提供了代替++--的getAndIncrement()、incrementAndGet()、getAndDecrement()和decrementAndGet()方法,還有加減給定值的方法、當前值等於預期值時更新的compareAndSet()方法。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.