Java併發編程(十一)經常使用工具

Java爲開發提供了不少有用的工具類,這些工具類能夠幫助咱們更加高效的編寫併發程序,本篇咱們將介紹這些實用工具的用法。安全

ThreadLocal

ThreadLocal類用於解決多線程共享一個變量的問題,當多線程訪問同一個變量時可能會致使結果的錯誤,防止這種錯誤第一種辦法就是使用鎖來保護對象;第二種方法就是完全根除共享,即每一個線程訪問本身私有的變量。有的同窗會以爲第二種方法就會有一些侷限性,由於有些時候不得不共享同一個變量。是的確實有侷限性,可是在不少狀況下是能夠不共享變量就能達到一樣的效果,ThreadLocal就是爲了解決這一問題而設計的。多線程

ThreadLocal使用方法以下:併發

class IncreaseThread implements Runnable {
    public void run() {
        for(int i=0; i< 10000; i++) {
            TLTest.number.set(TLTest.number.get() + 1);
        }
        //如下爲彙總代碼
        synchronized(TLTest.result) {
            TLTest.result += TLTest.number.get();
        }
    }
}
public class TLTest {
    public static ThreadLocal<Integer> number;
    public static Integer result = 0;
    public static void main(String[] args) throws Exception {
        number = new ThreadLocal<Integer>() {
            public Integer initialValue() {
                return 0;
            }
        };
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new IncreaseThread());
        exec.execute(new IncreaseThread());
        exec.shutdown();
        Thread.sleep(500);
        System.out.println("result: " + result);
    }
}

輸出結果以下:dom

result: 20000工具

在TLTest類中咱們定義了ThreadLocal變量和Integer變量,ThreadLocal變量須要爲其建立一個匿名內部類來實現爲其指定初始值,咱們將初始值指定爲0。咱們定義了一個線程類,這個線程負責將ThreadLocal的值加10000,最後線程會將本身的計算結果彙總到TLTest.result變量中。這個過程當中雖然咱們建立的兩個線程都對同一個ThreadLocal變量進行操做,可是沒有致使計算結果出錯。由於ThreadLocal爲每個線程分配了不一樣的存儲空間,咱們能夠簡單的將其理解爲一個線程對象和值的Map<Thread,Integer>(注意:只是能夠這麼理解,但實際上不是)。測試

CountDownLatch

CountDownLatch用於線程間的合做,其使用方法和wait()/notify()相似,CountDownLatch類有兩個方法:countDown()和await()方法,在建立CountDownLatch的對象時爲其指定countDown()方法調用的次數,當調用await()方法時當前線程會一直被阻塞,直到countDown()方法被調用了指定的次數。設想一種狀況,一個工頭在接到任務時會把任務分發給不一樣的工人,只有當全部的工人都完成本身的工做的時候,工頭才能夠交工。this

咱們用代碼模擬一下這種狀況:spa

class Worker implements Runnable {
    private int id;
    public Worker(int id) {
                this.id = id;
    }
    public void run() {
        Random rand = new Random();
        int workTime = rand.nextInt(1000);
        System.out.println(id + ": 開始幹活");
        try { Thread.sleep(workTime); } catch (Exception e) {}
        System.out.println(id + ": 完成了");
        CDLTest.cdl.countDown();
    }
}
public class CDLTest {
    private static int numberOfWorker = 3;
    public static CountDownLatch cdl = new CountDownLatch(numberOfWorker);
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i=0; i<numberOfWorker; i++) {
            exec.execute(new Worker(i));
        }
        exec.shutdown();
        cdl.await();
        System.out.println("工頭:交工");
    }
}

輸出結果以下:線程

1: 開始幹活設計

2: 開始幹活

0: 開始幹活

1: 完成了

2: 完成了

0: 完成了

工頭:交工

在本例中主線程承擔工頭的角色,調用await()方法等待工人線程完成工做。咱們還經過線程池建立了3個工人線程,咱們使用隨機數讓每一個線程隨機睡眠0-1000毫秒,用來模擬工人工做。

每一個工人完成本身的任務後調用countDown()方法,當全部的工人線程都作完本身的工做後主線程就能夠「交工」了。

PriorityBlockingQueue

PriorityBlockingQueue和前面講過的LinkedBlockingQueue、ArrayBlockingQueue類似,他們都實現了BlockingQueue接口,可是PriorityBlockingQueue和它們最大的區別是這個隊列每次取出的都是「優先級」最高的,而不是最早進入的。所以要想實現它的優先級的特性,容器中的元素必須實現了Comparable接口,不然容器將拋ClassCastException異常。此外PriorityBlockingQueue也是線程安全的,所以使用的時候不用加鎖。因爲以前咱們測試過LinkedBlockingQueue的阻塞性,所以PriorityBlockingQueue的阻塞性咱們就不測試了,簡單的測試一下它的「優先級」的性質:

public class PBQTest {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> pbq = new PriorityBlockingQueue<String>();
        String[] strs = new String[]{"C", "A", "B"};
        for(int i=0;i <strs.length;i++) {
            pbq.add(strs[i]);
        }
        while(!pbq.isEmpty()) {
            System.out.println(pbq.take());
        }
    }
}

輸出結果以下:

A

B

C

String類實現了Comparable接口,根據字母順序比較字符串的大小。咱們向隊列中添加元素的順序是"C", "A", "B",而取出順序是"A", "B", "C",此因能夠看出其「優先級」的性質。

總結

本篇介紹了三個經常使用的工具類,ThreadLocal用於解決多線程共享同一個變量的問題,它至關於建立了一個以線程對象爲key以目標對象爲value的一個Map,但實際上和Map是有區別的,好比Map對象不會在某個線程退出後對相應的value作垃圾回收,而ThreadLocal會對其進行回收。CountDownLatch用於同步多個任務,讓某些任務等待其它任務執行的一組操做,須要注意的是能夠有多個線程調用await()方法,當調用countDown()的次數到達指定數量的時候全部調用await()方法的線程都會從阻塞狀態變爲運行狀態。PriorityBlockingQueue的用法和其它實現BlockingQueue接口的用法類似,只是PriorityBlockingQueue中的元素的取出順序是按照優先級排序的。

 公衆號:今日說碼。關注個人公衆號,可查看連載文章。遇到不理解的問題,直接在公衆號留言便可。

相關文章
相關標籤/搜索