編寫高質量代碼:改善Java程序的151個建議(第9章:多線程和併發___建議125~131)

建議125:優先選擇線程池java

建議126:適時選擇不一樣的線程池來實現安全

建議127:lock與synchronized是不同的多線程

建議128:預防線程死鎖併發

建議129:適當設置阻塞隊列的長度app

建議130:使用CountDownLatch協調子線程dom

建議131:CyclicBarrier 讓多線程齊步走ide

建議125:優先選擇線程池函數

建議126:適時選擇不一樣的線程池來實現工具

Java線程池原理及實現<最通俗易懂的講解>性能

建議127:lock與synchronized是不同的

直接上代碼:

package OSChina.Multithread;

import java.util.Calendar;

public class Task {
    public void doSomething() {
        try {
            // 每一個線程等待2秒鐘,注意此時線程的狀態轉變爲Warning狀態
            Thread.sleep(2000);
        } catch (Exception e) {
            // 異常處理
        }
        StringBuffer sb = new StringBuffer();
        // 線程名稱
        sb.append("線程名稱:" + Thread.currentThread().getName());
        // 運行時間戳
        sb.append(",執行時間: " + Calendar.getInstance().get(Calendar.SECOND) + "s");
        System.out.println(sb);
    }
}
package OSChina.Multithread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TaskWithLock extends Task implements Runnable{
    // 聲明顯示鎖
    private final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            // 開始鎖定
            lock.lock();
            doSomething();
        } finally {
            // 釋放鎖
           lock.unlock();
        }
    }
}
package OSChina.Multithread;

public class TaskWithSync extends Task implements Runnable{
    @Override
    public void run() {
        synchronized ("A"){
            doSomething();
        }
    }
}
package OSChina.Multithread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Client {
    public static void runTasks(Class<? extends Runnable> clz) throws Exception{
        ExecutorService es = Executors.newCachedThreadPool();
        System.out.println("***開始執行 " + clz.getSimpleName() + " 任務***");
        // 啓動3個線程
        for (int i = 0; i < 3; i++) {
            es.submit(clz.newInstance());
        }
        // 等待足夠長的時間,而後關閉執行器
        TimeUnit.SECONDS.sleep(10);
        System.out.println("---" + clz.getSimpleName() + "  任務執行完畢---\n");
        // 關閉執行器
        es.shutdown();
    }

    public static void main(String[] args) throws Exception{
        // 運行顯示任務
        runTasks(TaskWithLock.class);
        // 運行內部鎖任務
        runTasks(TaskWithSync.class);
    }
}

顯示鎖是同時運行的,很顯然pool-1-thread-1線程執行到sleep時,其它兩個線程也會運行到這裏,一塊兒等待,而後一塊兒輸出,這還具備線程互斥的概念嗎?

而內部鎖的輸出則是咱們預期的結果,pool-2-thread-1線程在運行時其它線程處於等待狀態,pool-2-threda-1執行完畢後,JVM從等待線程池中隨機獲的一個線程pool-2-thread-2執行,最後執行pool-2-thread-3,這正是咱們但願的。

如今問題來了:Lock鎖爲何不出現互斥狀況呢?

這是由於對於同步資源來講顯示鎖是對象級別的鎖,內部鎖是類級別的鎖,lock定義爲多線程類的私有屬性是起不到互斥做用的,除非把lock定義爲全部線程的共享變量。

改一下代碼,將lock定義在測試類中

// 聲明顯示鎖
public static final Lock lock = new ReentrantLock();

除了這一點不一樣以外,顯示鎖和內部鎖還有什麼區別呢?還有如下4點不一樣:

一、Lock支持更細精度的鎖控制:

假設讀寫鎖分離,寫操做時不容許有讀寫操做存在,而讀操做時讀寫能夠併發執行,這一點內部鎖就很難實現。顯示鎖的示例代碼以下: 

package OSChina.Multithread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Foo {
    // 可重入的讀寫鎖
    private static final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    // 讀鎖
    private static final Lock r = rwl.readLock();
    // 寫鎖
    private static final Lock w = rwl.writeLock();

    // 多操做,可併發執行
    public static void read() {
        try {
            r.lock();
            Thread.sleep(1000);
            System.out.println("read......");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            r.unlock();
        }
    }

    // 寫操做,同時只容許一個寫操做
    public static void write() {
        try {
            w.lock();
            Thread.sleep(1000);
            System.out.println("write.....");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            w.unlock();
        }
    }
}

能夠編寫一個Runnable實現類,把Foo類做爲資源進行調用(注意多線程是共享這個資源的),而後就會發現這樣的現象:讀寫鎖容許同時有多個讀操做但只容許一個寫操做,也就是當有一個寫線程在執行時,全部的讀線程都會阻塞,直到寫線程釋放鎖資源爲止,而讀鎖則能夠有多個線程同時執行。

二、Lock鎖是無阻塞的,synchronized是阻塞的

三、Lock可實現公平鎖,synchronized只能是非公平鎖

四、Lock是代碼級的,synchronized是JVM級的

Lock是經過編碼實現的,synchronized是在運行期由JVM釋放的,相對來講synchronized的優化可能性高,畢竟是在最核心的部分支持的,Lock的優化須要用戶自行考慮。

顯示鎖和內部鎖的功能各不相同,在性能上也稍有差異,但隨着JDK的不斷推動,相對來講,顯示鎖使用起來更加便利和強大,在實際開發中選擇哪一種類型的鎖就須要根據實際狀況考慮了:靈活、強大選擇lock,快捷、安全選擇synchronized。

建議128:預防線程死鎖

一、死鎖的概念

死鎖是指多個進程在運行過程當中因爭奪資源而形成的一種僵局。當進程處於僵持狀態時,若無外力做用,它們都將沒法再向前推動。

二、產生死鎖的緣由

① 競爭資源

可剝奪資源和非剝奪性資源:

進程在得到這類資源後,該資源能夠再被其它線程剝奪,CPU和主存均屬於可剝奪性資源。另外一類資源是不可剝奪性資源,當系統把這類資源分配給某進程後,再不能強行回收,只能在進程用完後自行釋放,如磁帶機、打印機等。 

競爭非剝奪性資源:

在系統中所配置的非剝奪性資源,因爲它們的數量不能知足諸進程運行的須要,會使進程在運行過程當中,因爭奪這些資源而陷入僵局。

競爭臨時資源:

臨時資源是指由一個進程產生,被另外一個進程使用短暫時間後變無用的資源,它也可能產生死鎖。

② 進程間推動順序非法

進程在運行過程當中,請求和釋放資源的順序不當,一樣會產生死鎖。

三、死鎖的一些經常使用概念:

① 互斥條件:

指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求該資源,則請求者只能等待,直至佔有該資源的進程用畢釋放。

② 請求和保持條件:

指進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源又被其它進程佔有,此時請求進程阻塞,但又對本身得到的其它資源保持不放。

③ 不剝奪資源:

指進程已得到資源,在使用完以前,不能被剝奪,只能在使用完時由本身釋放。

④ 環路等待條件:

指在發生死鎖時,必然存在一個進程—資源的環形鏈,即進程集合(P0,P1,P2,…,Pn)中的P0正在等待一個P1佔用的資源;P1正在等待一個P2佔用的資源,……,Pn正在等待已被P0佔用的資源。

建議129:適當設置阻塞隊列的長度

ArrayBlockingQueue類最經常使用的add方法

若是直接調用offer方法插入元素, 在超出容量的狀況下, 它除了返回false外, 不會提供任何其餘信息, 若是代碼不作插入判斷, 那就會形成數據的「默默」丟失, 這就是它與非阻塞隊列的不一樣之處。

若是應用指望不管等待多長時間都要運行該任務, 不但願返回異常就須要用BlockingQueue接口定義的put方法了, 它的做用也是把元素加入到隊列中, 但它與add、offer方法不一樣, 它會等待隊列空出元素, 再讓本身加入進去, 通俗地講, put方法提供的是一種「無賴」式的插入, 不管等待多長時間都要把該元素插入到隊列中。
與插入元素相對應, 取出元素也有不一樣的實現, 例如remove、poll、take等方法, 對於此類方法的理解要創建在阻塞隊列的長度固定的基礎上, 而後根據是否阻塞、阻塞是否超時等實際狀況選用不一樣的插入和提取方法。

建議130:使用CountDownLatch協調子線程

CountDownLatch是一個很是實用的多線程控制工具類。經常使用的就下面幾個方法:

CountDownLatch(int count) //實例化一個倒計數器,count指定計數個數
countDown() // 計數減一
await() //等待,當計數減到0時,全部線程並行執行

對於倒計數器,一種典型的場景就是火箭發射。在火箭發射前,爲了保證萬無一失,每每還要進行各項設備、儀器的檢測。只有等到全部的檢查完畢後,引擎才能點火。那麼在檢測環節固然是多個檢測項能夠同時進行的。代碼實現:

public class CountDownLatchDemo implements Runnable{

    static final CountDownLatch latch = new CountDownLatch(10);
    static final CountDownLatchDemo demo = new CountDownLatchDemo();

    @Override
    public void run() {
        // 模擬檢查任務
        try {
            Thread.sleep(new Random().nextInt(10) * 1000);
            System.out.println("check complete");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //計數減一
            //放在finally避免任務執行過程出現異常,致使countDown()不能被執行
            latch.countDown();
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for (int i=0; i<10; i++){
            exec.submit(demo);
        }

        // 等待檢查
        latch.await();

        // 發射火箭
        System.out.println("Fire!");
        // 關閉線程池
        exec.shutdown();
    }
}

上述代碼中咱們先生成了一個CountDownLatch實例。計數數量爲10,這表示須要有10個線程來完成任務,等待在CountDownLatch上的線程才能繼續執行。latch.countDown();方法做用是通知CountDownLatch有一個線程已經準備完畢,倒計數器能夠減一了。latch.await()方法要求主線程等待全部10個檢查任務所有準備好才一塊兒並行執行。

建議131:CyclicBarrier 讓多線程齊步走

CyclicBarrier中文意思是「循環柵欄」

一、構造函數:

public CyclicBarrier(int parties)//parties 是參與線程的個數
public CyclicBarrier(int parties, Runnable barrierAction)//barrierAction是最後一個到達線程要作的任務

二、重要方法:

public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
  • 線程調用 await() 表示本身已經到達柵欄
  • BrokenBarrierException 表示柵欄已經被破壞,破壞的緣由多是其中一個線程 await() 時被中斷或者超時

一個線程組的線程須要等待全部線程完成任務後再繼續執行下一次任務,代碼實例:

package OSChina.Multithread;

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    static class TaskThread extends Thread {

        CyclicBarrier barrier;

        public TaskThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(getName() + " 到達柵欄 A");
                barrier.await();
                System.out.println(getName() + " 衝破柵欄 A");

                Thread.sleep(2000);
                System.out.println(getName() + " 到達柵欄 B");
                barrier.await();
                System.out.println(getName() + " 衝破柵欄 B");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int threadNum = 5;
        CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 完成最後任務");
            }
        });

        for(int i = 0; i < threadNum; i++) {
            new TaskThread(barrier).start();
        }
    }
}

從打印結果能夠看出,全部線程會等待所有線程到達柵欄以後纔會繼續執行,而且最後到達的線程會完成 Runnable 的任務。

三、CyclicBarrier 使用場景

能夠用於多線程計算數據,最後合併計算結果的場景。

四、CyclicBarrier 與 CountDownLatch 區別

① CyclicBarrier 是可循環利用的,CountDownLatch 是一次性的

② CyclicBarrier 參與的線程職責是同樣的,CountDownLatch 參與的線程的職責是不同的,有的在倒計時,有的在等待倒計時結束。

 

編寫高質量代碼:改善Java程序的151個建議@目錄

相關文章
相關標籤/搜索