線程基礎(二十九)

本文作者:王一飛,叩丁狼高級講師。原創文章,轉載請註明出處。

前面幾篇講完了併發環境下各種集合的使用, 今天來聊一聊多線程設計模式。進入正題前顯示說下,多線程的設計準則:

多線程的設計準則

1:安全性(safety)
程序正常運行的必要條件之一,對象進行邏輯操作時,對象結果狀態要和設計者原意一致。舉個簡單例子: 多線程對ArrayList對象操作時,會得到意想不到的的結果,因爲ArrayList是線程不安全的集合類,單純的多線程操作時,不具有安全性。

2:生存性/活性(liveness)
程序正常運行的必要條件之一,無論是什麼時候,必要執行的處理一定能夠執行,如果死活執行不到,這個代碼有問題。比如說,程序運行過程中,突然停止了,又不死機,後續代碼不執行了。常見的死循環,死鎖等。這種情況表示代碼失去活性,多線程代碼要必須強調生存性/活性。

3:可複用性(reusablility)
屬於代碼優化範疇,類設計成爲組件,可以重複使用。編寫多線程程序時,如果能巧妙的將線程的互斥機制/資源競爭機制隱藏到類中,這就是一個可複用性高的程序。

4:性能(performance)
也屬於代碼優化範疇,如果代碼能設計成可以快速,大批量的執行,這個代碼就是高性能代碼。這時候就得考慮,吞吐量: 單位時間處理數量, 響應時間:開始到結束的花費時間,容量:同時進行處理的數量。

總結: 安全性, 生存性/活性是多線程設計模式的必須條件, 在滿足這兩個前提下,再考慮如何提高線程的複用性跟性能。

Single Threaded Execution模式

居於上面準則, 來看下本篇的主角, Single Threaded Execution模式. 這個模式是多線程設計模式中最簡單,最基礎的一個. 按字面意思解釋 : 同一時刻某個資源只允許一個線程操作.
假設一個需求: 多個線程共同操作一個資源Resoure,先執行setData, 再執行showData方法

//競爭資源
public class Resource {
    private String data;
    public  void  setData(String data){
        this.data = data;
        try {
            Thread.sleep(1000); //放大問題
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public  void showData(){
        System.out.println(Thread.currentThread().getName() + "--:: data:" + data);
    }
}
public class App {
    public static void main(String[] args) {
        //共用同一個資源對象
        final  Resource resource = new Resource();
        new Thread(new Runnable() {
            public void run() {
                resource.setData("A");
                //期望:t1--:: data:A
                //結果:t1--:: data:B
                resource.showData();
            }
        }, "t1").start();
        new Thread(new Runnable() {
            public void run() {
                resource.setData("B");
                //期望:t2--:: data:B
                //結果:t2--:: data:B
                resource.showData();
            }
        }, "t2").start();
    }
}
t1--:: data:B
t2--:: data:B

看執行結果, t1線程打印出來並不是它setData進去的A, 原因: 當線程t1 通過setData方法改data=A 然後休眠1s. 緊接着線程t2進入setData方法修改data=B,並休眠. 這是t1線程休眠結束, 馬上執行showData, 此時的data已經被t2改爲data=B, 所以出現上面t1–:: data:B 的結果.

上述案例對resource資源不進行安全防護,就讓多個線程進行操作, 最終得到非期望的結果.這就是我們常說的線程不安全的操作. 要解決這問題最簡單做法就是將resource資源保護起來即可, 保證同一時刻只允許一個線程操作.

方案: synchronized 將 setData 跟 showData 方法包裹起來

public class App {
    public static void main(String[] args) {
        final  Resource resource = new Resource();
        new Thread(new Runnable() {
            public void run() {
                synchronized (resource) {
                    resource.setData("A");
                    resource.showData();
                }
            }
        }, "t1").start();
        new Thread(new Runnable() {
            public void run() {
                synchronized (resource) {
                    resource.setData("B");
                    resource.showData();
                }
            }
        }, "t2").start();
    }
}

線程t1操作前, 先獲取resource資源對象鎖,持有鎖之後,可以執行setData/showData方法, 線程t2則必須等待線程t1釋放資源對象鎖纔可以參與執行.

這種類型的多線程操作模式,我們稱之爲:Single Threaded Execution模式. 其核心點就是保證同一時刻某個資源只允許一個線程操作.

何時使用(使用場景)

1>多線程編程環境
2>多個線程同時訪問同一個共享資源;
3>共享資源的狀態會因線程的訪問而改變;

使用注意

Single Thread Execution 模式使用不當時,可能引起死鎖問題:
比如, 如果resource資源對象不是1個對象,而是多個對象呢?
假設場景: t1 先持有resource1, 再持有resource2 2個對象鎖之後,纔可以執行showData方法, t2更好相反.

public class App {
    public static void main(String[] args) {
        final  Resource resource1 = new Resource();
        final  Resource resource2 = new Resource();
        new Thread(new Runnable() {
            public void run() {
                synchronized (resource1) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (resource2) {
                        resource2.setData("A");
                        resource2.showData();
                    }
                }
            }
        }, "t1").start();
        new Thread(new Runnable() {
            public void run() {
                synchronized (resource2) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (resource1) {
                        resource1.setData("B");
                        resource1.showData();
                    }
                }
            }
        }, "t2").start();
    }
}

所以, 在使用Single Threaded Execution模式一定要看清楚使用場合.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章