日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java 多线程设计模式

發(fā)布時(shí)間:2023/12/16 java 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 多线程设计模式 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

多線程程序的評(píng)量標(biāo)準(zhǔn)

  • 安全性:不損壞對(duì)象
    不安全是指,對(duì)象的狀態(tài)處于非預(yù)期狀態(tài),比如賬戶(hù)余額變成了負(fù)值

  • 生存性:進(jìn)行必要的處理
    生存性是指:程序能正常運(yùn)行,可進(jìn)行必要的處理,影響生存性的典型問(wèn)題有出現(xiàn)死鎖

  • 復(fù)用性:可再利用類(lèi)
    復(fù)用性是指代碼重用,若復(fù)用性好,可減少大量重復(fù)代碼

  • 性能:能快速,大量進(jìn)行處理
    性能有兩個(gè)方面的考慮因素:吞吐量和響應(yīng)性,客戶(hù)端程序比較重視響應(yīng)性,服務(wù)端程序更重視吞吐量,吞吐量是指單位時(shí)間內(nèi)完成的任務(wù),響應(yīng)性是指提交任務(wù)后多長(zhǎng)時(shí)間內(nèi)能收到程序的反饋。比如說(shuō)我們?cè)赒Q時(shí),經(jīng)常感覺(jué)QQ卡,這便是響應(yīng)性問(wèn)題。

  • 其中安全性和生存性是必要的,如果安全性和生存性都沒(méi)有保證,就無(wú)所謂別的考量了。復(fù)用性和性能決定了程序的質(zhì)量

    《多線程設(shè)計(jì)模式》一共講了12個(gè)設(shè)計(jì)模式,列舉如下。

    1. Single Threaded Execution

    只允許單個(gè)線程執(zhí)行對(duì)象的某個(gè)方法,以保護(hù)對(duì)象的多個(gè)狀態(tài)。
    實(shí)現(xiàn)時(shí)需用synchronized修飾引用受保護(hù)的狀態(tài)的方法,這樣就只能有單個(gè)線程訪問(wèn)該方法,其它線程由于不能獲取鎖而等待,因?yàn)橹挥幸粋€(gè)線程去訪問(wèn)受保護(hù)狀態(tài)變量,故此不需要擔(dān)心該狀態(tài)變量被別的線程修改。
    也可以用synchronized修飾代碼塊來(lái)保護(hù)狀態(tài)字段。

    示例程序:

    public class Gate {private String _name = "NoBody";private String _where = "NoBody"; public synchronized void pass(String name, String where) {_name = name;_where = where; check();}private void check() {if (_name.charAt(0) != _where.charAt(0)) {System.out.println("*****************Broken**************");}}}

    如果這里不用synchronized修飾pass方法,多線程環(huán)境下會(huì)有多個(gè)線程同時(shí)執(zhí)行pass方法,容易造成狀態(tài)不一致,引入安全性問(wèn)題。

    適用場(chǎng)景:

    多線程環(huán)境下如果狀態(tài)變量(可能有多個(gè)狀態(tài)變量,并且它們之間是相關(guān)的)被多個(gè)線程訪問(wèn),并且可能會(huì)發(fā)生變化,此時(shí)需要將狀態(tài)變量封裝起來(lái)(可以用類(lèi)進(jìn)行封裝),并將訪問(wèn)這些狀態(tài)變量的方法用synchronized進(jìn)行保護(hù)??梢杂胹ynchronized修飾方法,也可以修飾代碼塊。

    注意事項(xiàng):

    一定要注意synchronized是通過(guò)獲取哪個(gè)鎖來(lái)保護(hù)狀態(tài)變量,如果保護(hù)狀態(tài)變量時(shí)使用不同的鎖對(duì)象,那么多個(gè)線程仍然可以同時(shí)訪問(wèn)被保護(hù)的狀態(tài)變量,尤其是保護(hù)多個(gè)相關(guān)狀態(tài)變量時(shí)一定要記得用同一個(gè)鎖對(duì)象。synchronized修飾方法時(shí),獲取的鎖對(duì)象是synchronied方法所在類(lèi)的實(shí)例,synchorized修飾this時(shí),獲取的鎖對(duì)象也是當(dāng)前類(lèi)的實(shí)例。
    synchronized修飾符不會(huì)被繼承,也就是說(shuō)我們覆蓋父類(lèi)的synchronized方法時(shí),如果不添加synchronized修飾符,就不能保護(hù)狀態(tài)變量,因此覆蓋父類(lèi)方法時(shí),如果想保護(hù)某些狀態(tài)變量,記得添加synchronized修飾符。

    2. Immutable

    在single threaded executetion這個(gè)模式里我們使用了synchronized來(lái)保護(hù)需要保護(hù)的狀態(tài)變量,因?yàn)檫@些狀態(tài)可能會(huì)變化,如果不保護(hù)的話,可能會(huì)破壞對(duì)象。但是用synchronized保護(hù)變量也帶來(lái)了性能問(wèn)題,因?yàn)楂@取鎖需要時(shí)間,并且如果多個(gè)線程競(jìng)爭(zhēng)鎖的話,會(huì)讓某些線程進(jìn)入這個(gè)鎖的條件隊(duì)列,暫停執(zhí)行,這樣會(huì)降低性能。
    如果狀態(tài)根本不會(huì)發(fā)生變化,就不需要用鎖保護(hù),這就是Immutable模式。

    示例程序:

    public final class Person {private final String _name;private final String _address;public Person(String name, String address) {_name = name;_address = address;}public String getName() {return _name;}public String getAddress() {return _address;}@Overridepublic String toString() {return "Person [_name=" + _name + ", _address=" + _address + "]";}}

    Person類(lèi)用final修飾,防止被繼承。
    _name和_address都用final修飾,防止被修改,只能在定義時(shí)初始化,或者在構(gòu)造器里初始化,Person類(lèi)也只提供了對(duì)這些狀態(tài)字段的get方法,故此外界調(diào)用該類(lèi)的實(shí)例時(shí)無(wú)法修改這些狀態(tài)。

    適用場(chǎng)景:

    對(duì)于那些不會(huì)變化的狀態(tài)可用Immutable類(lèi)進(jìn)行封裝,這樣可避免用鎖同步,從而提高性能。

    注意事項(xiàng):

    String就是一個(gè)Immutable類(lèi),與之相對(duì)應(yīng)的StringBuilder或者StringBuffer是muttable類(lèi)。我們?cè)谠O(shè)計(jì)類(lèi)時(shí),針對(duì)那些需要共享并且訪問(wèn)很頻繁的實(shí)例,可將其設(shè)置為Immutalbe類(lèi),如果在少數(shù)情況下它的狀態(tài)也可能會(huì)變化,可為之設(shè)計(jì)相對(duì)應(yīng)的muttable類(lèi),像String和StringBuffer的關(guān)系一樣。
    StringBuilder是非線程安全的,StringBuffer是線程安全的,String也是線程安全的,因?yàn)樗莍mmutable類(lèi)。
    java里的包裝器類(lèi)全是immutable類(lèi)。

    3. Guarded Suspension

    當(dāng)我們調(diào)用對(duì)象某個(gè)的某個(gè)方法時(shí),可能對(duì)象當(dāng)前狀態(tài)并不滿足執(zhí)行的條件,于是需要等待,這就是Guarded Suspension模式。只有當(dāng)警戒條件滿足時(shí),才執(zhí)行,否則等待,另外對(duì)象必須有改變其狀態(tài)的方法。

    示例程序:

    public class RequestQueue {private final LinkedList<Request> _queue = new LinkedList<Request>();public synchronized Request getRequest() {while (_queue.size() <= 0) {try {wait();} catch (InterruptedException e) {}}return _queue.removeFirst();}public synchronized void putRequest(Request request) {_queue.add(request);notifyAll();}}

    _queue.size()>0 便是警戒條件,只有當(dāng)_queue.size()>0才能調(diào)用_queue.removeFirst(),當(dāng)警戒條件不滿足時(shí),需要wait。
    putRequest方法可以改變RequestQueue的狀態(tài),使getRequest方法里的警戒條件滿足。

    適用場(chǎng)景:

    某個(gè)調(diào)用者的方法在執(zhí)行時(shí)如果希望當(dāng)狀態(tài)不滿足時(shí)等待狀態(tài)滿足后再執(zhí)行,如果狀態(tài)滿足,則立即執(zhí)行,可考慮使用Guarded Suspension模式。

    注意事項(xiàng):

    Guarded Suspension里的警戒方法(等待狀態(tài)成立才執(zhí)行的方法)是同步阻塞的,狀態(tài)不滿足時(shí),調(diào)用該方法的線程會(huì)阻塞。
    Guarded Suspension里的狀態(tài)變更方法里須記得在狀態(tài)變更后,調(diào)用notifyAll,使得調(diào)用警戒方法的線程可恢復(fù)執(zhí)行。

    4. Balking

    Balking模式與Guarded Suspension模式相似,都是在對(duì)象狀態(tài)不符合要求時(shí)需要進(jìn)行一些處理,不過(guò)Guared Suspension在狀態(tài)不滿足要求時(shí),會(huì)等待并阻塞線程,而B(niǎo)alking模式是直接返回,并不等待。調(diào)用者可暫時(shí)先做別的工作,稍后再來(lái)調(diào)用該對(duì)象的方法。

    示例程序:

    public class Data {private final String _file_name;private String _content;private boolean _changed;public Data(String filename, String conetent) {_file_name = filename;_content = conetent;_changed = false;}public synchronized void change(String newContent) {_content = newContent;_changed = true;}public synchronized void save() throws IOException {if (!_changed)return;doSave();_changed = false;}private void doSave() throws IOException {Writer writer = new FileWriter(_file_name);writer.write(_content);writer.close();}}

    save方法里首先檢測(cè)字符串是否有變化,如果沒(méi)有變化則立即返回,否則才保存字符串,這樣可避免不必要的IO,提高性能。
    上述實(shí)例中的警戒條件是_changed為true

    適用場(chǎng)景:

    不想等待警戒條件成立時(shí),適合使用Balking模式。
    警戒條件只有第一次成立時(shí),適合使用Balking模式。

    注意事項(xiàng):

    該模式并不會(huì)等待警戒條件成立,當(dāng)警戒條件不成立時(shí)直接返回了,故此改變狀態(tài)的方法也就不需要調(diào)用notifyAll方法。
    另外注意不管是警戒條件方法還是改變狀態(tài)的方法都需要用synchronized同步,因?yàn)檫@里封裝了多個(gè)數(shù)據(jù),一個(gè)用于判斷警戒條件的狀態(tài),還有真實(shí)數(shù)據(jù)。

    5. Producer-Consumer

    生產(chǎn)者消費(fèi)者問(wèn)題是操作系統(tǒng)里非常經(jīng)典的同步問(wèn)題,生產(chǎn)者生產(chǎn)好數(shù)據(jù)后,放到緩沖區(qū),消費(fèi)者從緩沖區(qū)取出數(shù)據(jù)。但是當(dāng)緩沖區(qū)滿了的時(shí)候,生產(chǎn)者不可再將生產(chǎn)好的數(shù)據(jù)放到緩沖區(qū),當(dāng)緩沖區(qū)沒(méi)有數(shù)據(jù)的時(shí)候消費(fèi)者不可再?gòu)木彌_區(qū)里取出數(shù)據(jù)。
    解決生產(chǎn)者消費(fèi)者問(wèn)題的方案稱(chēng)之為生產(chǎn)者消費(fèi)者模式,在該模式里可能有多個(gè)生產(chǎn)者,多個(gè)消費(fèi)者,生產(chǎn)者和消費(fèi)者都有獨(dú)立的線程。其中最關(guān)鍵的是放置數(shù)據(jù)的緩沖區(qū),生產(chǎn)者和消費(fèi)者在操作緩沖區(qū)時(shí)都必須同步,生產(chǎn)者往緩沖區(qū)放置數(shù)據(jù)時(shí),如果發(fā)現(xiàn)緩沖區(qū)已滿則等待,消費(fèi)者從緩沖區(qū)取數(shù)據(jù)時(shí)如果發(fā)現(xiàn)緩沖區(qū)沒(méi)有數(shù)據(jù),也必須等待。

    示例程序:

    public class Table {private final String[] _buffer;private int _tail;private int _head;private int _count;public Table(int count) {_buffer = new String[count];_head = 0;_tail = 0;_count = 0;}public synchronized void put(String cake) throws InterruptedException {while (_count >= _buffer.length) {wait();}_buffer[_tail] = cake;_tail = (_tail + 1) % _count;_count++;notifyAll();}public synchronized String take() throws InterruptedException {while (_count <= 0) {wait();}String cake = _buffer[_head];_head = (_head + 1) % _count;_count--;notifyAll();return cake;} }

    這里table扮演的便是數(shù)據(jù)緩沖區(qū)的角色,當(dāng)消費(fèi)者調(diào)用take取數(shù)據(jù)時(shí),如果發(fā)現(xiàn)數(shù)據(jù)數(shù)目少于0時(shí),便會(huì)等待,當(dāng)生產(chǎn)者調(diào)用put放數(shù)據(jù)時(shí),如果發(fā)現(xiàn)數(shù)據(jù)數(shù)目大于緩沖區(qū)大小時(shí),也會(huì)等待。

    適用場(chǎng)景:

    當(dāng)程序里有多個(gè)生產(chǎn)者角色或者多個(gè)消費(fèi)者角色操作同一個(gè)共享數(shù)據(jù)時(shí),適合用生產(chǎn)者消費(fèi)者模式。比如下載模塊,通常會(huì)有多個(gè)下載任務(wù)線程(消費(fèi)者角色),用戶(hù)點(diǎn)擊下載按鈕時(shí)產(chǎn)生下載任務(wù)(生產(chǎn)者角色),它們會(huì)共享任務(wù)隊(duì)列。

    注意事項(xiàng):

    不管是生產(chǎn)方法還是消費(fèi)方法,當(dāng)警戒條件不滿足時(shí),一定要等待,警戒條件滿足后執(zhí)行完放置數(shù)據(jù)邏輯或者取出數(shù)據(jù)邏輯后一定要調(diào)用notifyAll方法,使得其它線程恢復(fù)運(yùn)行。

    6. Read-Write Lock

    先前的幾個(gè)多線程設(shè)計(jì)模式里,操作共享數(shù)據(jù)時(shí),不管如何操作數(shù)據(jù)一律采取互斥的策略(除了Immutable模式),即只允許一個(gè)線程執(zhí)行同步方法,其它線程在共享數(shù)據(jù)的條件隊(duì)列里等待,只有執(zhí)行同步方法的線程執(zhí)行完同步方法后被阻塞的線程才可在獲得同步鎖后繼續(xù)執(zhí)行。
    這樣效率其實(shí)有點(diǎn)低,因?yàn)樽x操作和讀操作之間并不需要互斥,兩個(gè)讀線程可以同時(shí)操作共享數(shù)據(jù),讀線程和寫(xiě)線程同時(shí)操作共享數(shù)據(jù)會(huì)有沖突,兩個(gè)寫(xiě)線程同時(shí)操作數(shù)據(jù)也會(huì)有沖突。

    示例程序:

    Data類(lèi)

    public class Data {private final char[] _buffer;private final ReadWriteLock _lock = new ReadWriteLock();public Data(int size) {_buffer = new char[size];for (int i = 0; i < size; i++)_buffer[i] = '*';}public char[] read() throws InterruptedException {_lock.readLock();try {return doRead();} finally {_lock.readUnlock();}}public void write(char c) throws InterruptedException {_lock.writeLock();try {doWrite(c);} finally {_lock.writeUnock();}}private char[] doRead() {char[] newbuf = new char[_buffer.length];for (int i = 0; i < newbuf.length; i++)newbuf[i] = _buffer[i];slowly();return newbuf;}private void doWrite(char c) {for (int i = 0; i < _buffer.length; i++) {_buffer[i] = c;slowly();}}private void slowly() {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}

    ReadWriteLock類(lèi)

    public class ReadWriteLock {private int _reading_readers = 0;private int _waiting_writers = 0;private int _writing_writers = 0;private boolean _prefer_writer = true;public synchronized void readLock() throws InterruptedException {while (_writing_writers > 0 || (_prefer_writer && _waiting_writers > 0)) {wait();}_reading_readers++;}public synchronized void readUnlock() {_reading_readers--;_prefer_writer = true;notifyAll();}public synchronized void writeLock() throws InterruptedException {_waiting_writers++;try {while (_reading_readers > 0 || _writing_writers > 0)wait();} finally {_waiting_writers--;}_writing_writers++;}public synchronized void writeUnock() {_writing_writers--;_prefer_writer = false;notifyAll();}}

    這里為讀寫(xiě)鎖設(shè)置了單獨(dú)的類(lèi)ReadWriteLock,ReadWriteLock提供了4個(gè)方法readLock,readUnlock,writeLock,writeUnlock。
    讀線程在讀取共享數(shù)據(jù)時(shí),先調(diào)用readLock方法獲取讀鎖,然后使用try塊讀取共享數(shù)據(jù)并在finnally塊中調(diào)用readUnlock釋放讀鎖。寫(xiě)線程在寫(xiě)入共享數(shù)據(jù)時(shí),先調(diào)用writeLock方法獲取寫(xiě)鎖,然后使用try塊寫(xiě)入共享數(shù)據(jù)并在finnally塊中調(diào)用writeUnlock方法釋放寫(xiě)鎖。
    實(shí)現(xiàn)ReadWriteLock時(shí)使用了_waiting_writers和_prefer_writer,其實(shí)如果不采用這兩個(gè)字段也能實(shí)現(xiàn)讀寫(xiě)鎖,但是使用了_prefer_writer后可以讓讀線程以及寫(xiě)線程不致于饑餓。每次讀線程調(diào)用完readUnlock后設(shè)置_prefer_writer為true,此時(shí)如果有寫(xiě)線程等待寫(xiě)入,便可恢復(fù)執(zhí)行,而不是由其它讀線程繼續(xù)執(zhí)行。每次寫(xiě)線程調(diào)用完writeUnlock后,_prefer_writer為false,此時(shí)等待讀取的線程可恢復(fù)執(zhí)行。

    適用場(chǎng)景:

    操作共享數(shù)據(jù)的讀線程明顯多于寫(xiě)線程時(shí)可采用讀寫(xiě)鎖模式提高程序性能。

    注意事項(xiàng):

    Java 5的concurrent包里已經(jīng)有ReadWriteLock接口,對(duì)應(yīng)的類(lèi)有ReentrantReadWriteLock,沒(méi)必要自己實(shí)現(xiàn)ReadWriteLock類(lèi)。并發(fā)庫(kù)里的類(lèi)都是經(jīng)過(guò)測(cè)試的穩(wěn)定的類(lèi),并且性能也會(huì)比自己寫(xiě)的類(lèi)要高,因此我們應(yīng)該優(yōu)先選擇并發(fā)庫(kù)里的類(lèi)。

    7. Thread-Per-Message

    實(shí)現(xiàn)某個(gè)方法時(shí)創(chuàng)建新線程去完成任務(wù),而不是在本方法里完成任務(wù),這樣可提高響應(yīng)性,因?yàn)橛行┤蝿?wù)比較耗時(shí)。

    示例程序:

    public class Host {private final Handler _handler=new Handler();public void request(final int count, final char c){new Thread(){public void run(){_handler.handle(count, c);}}.start();} }

    實(shí)現(xiàn)Host類(lèi)的方法時(shí),新建了一個(gè)線程調(diào)用Handler對(duì)象處理request請(qǐng)求。
    每次調(diào)用Host對(duì)象的request方法時(shí)都會(huì)創(chuàng)建并啟動(dòng)新線程,這些新線程的啟動(dòng)順序不是確定的。

    適用場(chǎng)景:

    適合在操作順序無(wú)所謂時(shí)使用,因?yàn)檎?qǐng)求的方法里新建的線程的啟動(dòng)順序不是確定的。
    在不需要返回值的時(shí)候才能使用,因?yàn)閞equest方法不會(huì)等待線程結(jié)束才返回,而是會(huì)立即返回,這樣得不到請(qǐng)求處理后的結(jié)果。

    注意事項(xiàng):

    每次調(diào)用都會(huì)創(chuàng)建并啟動(dòng)一個(gè)新線程,對(duì)新建線程沒(méi)有控制權(quán),實(shí)際應(yīng)用中只有很簡(jiǎn)單的請(qǐng)求才會(huì)用Thread-Per-Message這個(gè)模式,因?yàn)橥ǔN覀儠?huì)關(guān)注返回結(jié)果,也會(huì)控制創(chuàng)建的線程數(shù)量,否則系統(tǒng)會(huì)吃不消。

    8. Worker Thread

    在Thread-Per-Message模式里,每次函數(shù)調(diào)用都會(huì)啟動(dòng)一個(gè)新線程,但是啟動(dòng)新線程的操作其實(shí)是比較繁重的,需要比較多時(shí)間,系統(tǒng)對(duì)創(chuàng)建的線程數(shù)量也會(huì)有限制。我們可以預(yù)先啟動(dòng)一定數(shù)量的線程,組成線程池,每次函數(shù)調(diào)用時(shí)新建一個(gè)任務(wù)放到任務(wù)池,預(yù)先啟動(dòng)的線程從任務(wù)池里取出任務(wù)并執(zhí)行。這樣便可以控制線程的數(shù)量,也避免了每次啟動(dòng)新線程的高昂代價(jià),實(shí)現(xiàn)了資源重復(fù)利用。

    示例程序:

    Channel類(lèi):

    public class Channel {private static final int MAX_REQUEST = 100;private final Request[] _request_queue;private int tail;private int head;private int count;private WorkerThread[] _thread_pool;public Channel(int threads) {_request_queue = new Request[MAX_REQUEST];tail = 0;head = 0;count = 0;_thread_pool = new WorkerThread[threads];for (int i = 0; i < threads; i++) {_thread_pool[i] = new WorkerThread("Worker-" + i, this);}}public void startWorkers() {for (int i = 0; i < _thread_pool.length; i++)_thread_pool[i].start();}public synchronized Request takeRequest() throws InterruptedException {while (count <= 0) {wait();}Request request = _request_queue[head];head = (head + 1) % _request_queue.length;count--;notifyAll();return request;}public synchronized void putRequest(Request request)throws InterruptedException {while (count >= _request_queue.length) {wait();}_request_queue[tail] = request;tail = (tail + 1) % _request_queue.length;count++;notifyAll();}}

    WorkerThread類(lèi):

    public class WorkerThread extends Thread {private final Channel _channel;public WorkerThread(String name, Channel channel) {super(name);_channel = channel;}@Overridepublic void run() {while (true) {Request request;try {request = _channel.takeRequest();request.execute();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

    channel類(lèi)集成了線程池和任務(wù)池,對(duì)外提供了startWorkers方法,外界可調(diào)用該方法啟動(dòng)所有工作線程,然后通過(guò)putRequest方法向任務(wù)池添加任務(wù),工作者線程會(huì)自動(dòng)從任務(wù)池里取出任務(wù)并執(zhí)行。

    適用場(chǎng)景:

    和Thread-Per-Message模式一樣,Worker Thread模式實(shí)現(xiàn)了invocation和exectution的分離,即調(diào)用和執(zhí)行分離,調(diào)用者調(diào)用方法運(yùn)行在一個(gè)線程,任務(wù)的執(zhí)行在另一個(gè)線程。調(diào)用者調(diào)用方法后可立即返回,提高了程序的響應(yīng)性。另外也正是因?yàn)檎{(diào)用和執(zhí)行分離了,我們可以控制任務(wù)的執(zhí)行順序,還可以取消任務(wù),還能分散處理,將任務(wù)交給不同的機(jī)器執(zhí)行,如果沒(méi)有將調(diào)用和執(zhí)行分離,這些特性是無(wú)法實(shí)現(xiàn)的。
    適合有大量任務(wù)并且還需要將任務(wù)執(zhí)行分離的程序,比如象應(yīng)用分發(fā)類(lèi)App,需要經(jīng)常和服務(wù)器通信獲取數(shù)據(jù),并且通信消息可能還有優(yōu)先級(jí)。

    注意事項(xiàng):

    注意控制工作者線程的數(shù)量,如果過(guò)多,那么會(huì)有不少工作者線程并沒(méi)有工作,會(huì)浪費(fèi)系統(tǒng)資源,如果過(guò)少會(huì)使得任務(wù)池里塞滿,導(dǎo)致其它線程長(zhǎng)期阻塞。可根據(jù)實(shí)際工作調(diào)整線程數(shù)量,和任務(wù)池里的最大任務(wù)池?cái)?shù)。
    如果worker thread只有一條,工人線程處理的范圍就變成單線程了,可以省去共享互斥的必要。通常GUI框架都是這么實(shí)現(xiàn)的,操作界面的線程只有一個(gè),界面元素的方法不需要進(jìn)行共享互斥。如果操作界面的線程有多個(gè),那么必須進(jìn)行共享互斥,我們還會(huì)經(jīng)常設(shè)計(jì)界面元素的子類(lèi),子類(lèi)實(shí)現(xiàn)覆蓋方法時(shí)也必須使用synchronized進(jìn)行共享互斥,引入共享互斥后會(huì)引入鎖同步的開(kāi)銷(xiāo),使程序性能降低,并且如果有不恰當(dāng)?shù)墨@取鎖的順序,很容易造成死鎖,這使得GUI程序設(shè)計(jì)非常復(fù)雜,故此GUI框架一般都采用單線程。

    Java 5的并發(fā)包里已經(jīng)有線程池相關(guān)的類(lèi),無(wú)需自己實(shí)現(xiàn)線程池??墒褂肊xecutors的方法啟動(dòng)線程池,這些方法包括newFixedThreadPool,newSingleThreadExecutor,newCachedThreadPool,newScheduledThreadPool等等。

    9. Future

    在Thread-Per-Message模式和Worker Thread模式里,我們實(shí)現(xiàn)了調(diào)用和執(zhí)行分離。但是通常我們調(diào)用一個(gè)函數(shù)是可以獲得返回值的,在上述兩種模式里,雖然實(shí)現(xiàn)了調(diào)用和執(zhí)行相分離,但是并不能獲取調(diào)用執(zhí)行的返回結(jié)果。Future模式則可以獲得執(zhí)行結(jié)果,在調(diào)用時(shí)返回一個(gè)Future,可以通過(guò)Future獲得真正的執(zhí)行結(jié)果。

    示例程序:

    Host類(lèi)

    public class Host {public Data request(final int count, final char c) {System.out.println(" request (" + count + ", " + c + " ) BEGIN");final FutureData future = new FutureData();new Thread() {@Overridepublic void run() {RealData realData = new RealData(count, c);future.setRealData(realData);}}.start();return future;}}

    Data接口

    public interface Data {public String getContent(); }

    FutureData類(lèi)

    public class FutureData implements Data {private boolean _ready = false;private RealData _real_data = null;public synchronized void setRealData(RealData realData) {if (_ready)return;_real_data = realData;_ready = true;notifyAll();}@Overridepublic synchronized String getContent() {while (!_ready) {try {wait();} catch (InterruptedException e) {}}return _real_data.getContent();}}

    RealData類(lèi)

    public class RealData implements Data {private final String _content;public RealData(int count, char c) {System.out.println("Making Realdata(" + count + "," + c + ") BEGIN");char[] buffer = new char[count];for (int i = 0; i < count; i++) {buffer[i] = c;try {Thread.sleep(100);} catch (Exception e) {}}System.out.println(" making Real Data(" + count + "," + c + ") END");_content = new String(buffer);}@Overridepublic String getContent() {return _content;}}

    適用場(chǎng)景:

    如果既想實(shí)現(xiàn)調(diào)用和執(zhí)行分離,又想獲取執(zhí)行結(jié)果,適合使用Future模式。
    Future模式可以獲得異步方法調(diào)用的”返回值”,分離了”準(zhǔn)備返回值”和”使用返回值”這兩個(gè)過(guò)程。

    注意事項(xiàng):

    Java 5并發(fā)包里已經(jīng)有Future接口,不僅能獲得返回結(jié)果,還能取消任務(wù)執(zhí)行。當(dāng)調(diào)用ExecutorService對(duì)象的submit方法向任務(wù)池提交一個(gè)Callable任務(wù)后,可獲得一個(gè)Future對(duì)象,用于獲取任務(wù)執(zhí)行結(jié)果,并可取消任務(wù)執(zhí)行。

    10. Two-Phase Termination

    這一節(jié)介紹如何停止線程,我們剛開(kāi)始學(xué)習(xí)線程時(shí),可能很容易犯的錯(cuò)就是調(diào)用Thread的stop方法停止線程,該方法確實(shí)能迅速停止線程,并會(huì)讓線程拋出異常。但是調(diào)用stop方法是不安全的,如果該線程正獲取了某個(gè)對(duì)象的鎖,那么這個(gè)鎖是不會(huì)被釋放的,其他線程將繼續(xù)被阻塞在該鎖的條件隊(duì)列里,并且也許線程正在做的工作是不能被打斷的,這樣可能會(huì)造成系統(tǒng)破壞。從線程角度看,只有執(zhí)行任務(wù)的線程本身知道該何時(shí)恰當(dāng)?shù)耐V箞?zhí)行任務(wù),故此我們需要用Two-Phase Termination模式來(lái)停止線程,在該模式里如果想停止某個(gè)線程,先設(shè)置請(qǐng)求線程停止的標(biāo)志為true,然后調(diào)用Thread的interrupt方法,在該線程里每完成一定工作會(huì)檢查請(qǐng)求線程停止的標(biāo)志,如果為true,則安全地結(jié)束線程。

    示例程序:

    public class CountupThread extends Thread {private long counter = 0;private volatile boolean _shutdown_requested = false;public void shutdownRequest() {_shutdown_requested = true;interrupt();}public boolean isShutdownRequested() {return _shutdown_requested;}@Overridepublic void run() {try {while (!_shutdown_requested) {doWork();}} catch (InterruptedException e) {e.printStackTrace();} finally {doShutdown();}}private void doWork() throws InterruptedException {counter++;System.out.println("doWork: counter = " + counter);Thread.sleep(500);}private void doShutdown() {System.out.println("doShutDown: counter = " + counter);}}

    外界可調(diào)用shutdownRequest來(lái)停止線程。

    適用場(chǎng)景:

    需要停止線程時(shí),可考慮使用Two-Phase Termination模式

    注意事項(xiàng):

    我們?cè)谡?qǐng)求線程停止時(shí),若只設(shè)置請(qǐng)求停止標(biāo)志,是不夠的,因?yàn)槿绻€程正在執(zhí)行sleep操作,那么會(huì)等sleep操作執(zhí)行完后,再執(zhí)行到檢查停止標(biāo)志的語(yǔ)句才會(huì)退出,這樣程序響應(yīng)性不好。
    響應(yīng)停止請(qǐng)求的線程如果只檢查中斷狀態(tài)(不是說(shuō)我們?cè)O(shè)置的停止標(biāo)志)也是不夠的,如果線程正在sleep或者wait,則會(huì)拋出InterruptedException異常,就算沒(méi)有拋出異常,線程也會(huì)變成中斷狀態(tài),似乎我們沒(méi)必要設(shè)置停止標(biāo)志,只需檢查InterruptedException或者用isInterrupted方法檢查當(dāng)前線程的中斷狀態(tài)就可以了,但是這樣做會(huì)引入潛在的危險(xiǎn),如果該線程調(diào)用的方法忽略了InterruptedException,或者該線程使用的對(duì)象的某個(gè)方法忽略了InterruptedException,而這樣的情況是很常見(jiàn)的,尤其是如果我們使用某些類(lèi)庫(kù)代碼時(shí),又不知其實(shí)現(xiàn),即使忽略了InterruptedException,我們也不知道,在這種情況下,我們無(wú)法檢查到是否有其它線程正在請(qǐng)求本線程退出,故此說(shuō)設(shè)置終端標(biāo)志是有必要的,除非能保證線程所引用的所有對(duì)象(包括間接引用的)不會(huì)忽略InterruptedException,或者能保存中斷狀態(tài)。

    中斷狀態(tài)和InterruptedException可以互轉(zhuǎn):

    • 中斷狀態(tài) -> InterruptedException

      if (Thread.interrupted) {throw new InterruptedException() }
    • InterruptedException -> 中斷狀態(tài)

      try {Thread.sleep(1000); } catch (InterruptedException e) {Thread.currentThread().interrupt(); }
    • InterruptedException -> InterruptedException

      InterruptedException savedInterruptException = null; ... try {Thread.sleep(1000); } catch (InterruptedException e) {savedInterruptException=e; } ... if(savedInterruptException != null )throw savedInterruptException;

    11. Thread-Specific Storage

    我們知道,如果一個(gè)對(duì)象不會(huì)被多個(gè)線程訪問(wèn),那么就不存在線程安全問(wèn)題。Thread-Specific Storage模式就是這樣一種設(shè)計(jì)模式,為每個(gè)線程生成單獨(dú)的對(duì)象,解決線程安全問(wèn)題。不過(guò)為線程生成單獨(dú)的對(duì)象這些細(xì)節(jié)對(duì)于使用者來(lái)說(shuō)是隱藏的,使用者只需簡(jiǎn)單使用即可。需要用到ThreadLocal類(lèi),它是線程保管箱,為每個(gè)線程保存單獨(dú)的對(duì)象。

    示例程序:

    Log類(lèi)

    public class Log {private static ThreadLocal<TSLog> _ts_log_collection = new ThreadLocal<TSLog>();public static void println(String s) {getTSLog().println(s);}public static void close() {getTSLog().close();}private static TSLog getTSLog() {TSLog tsLog = _ts_log_collection.get();if (tsLog == null) {tsLog = new TSLog(Thread.currentThread().getName() + "-log.txt");_ts_log_collection.set(tsLog);}return tsLog;}}

    TSLog類(lèi)

    public class TSLog {private PrintWriter _writer = null;public TSLog(String fileName) {try {_writer = new PrintWriter(fileName);} catch (FileNotFoundException e) {e.printStackTrace();}}public void println(String s) {_writer.write(s);}public void close() {_writer.close();} }

    適用場(chǎng)景:

    使用Thread-Specific Storgae模式可很好的解決多線程安全問(wèn)題,每個(gè)線程都有單獨(dú)的對(duì)象,如果從ThreadLocal類(lèi)里獲取線程獨(dú)有對(duì)象的時(shí)間遠(yuǎn)小于調(diào)用對(duì)象方法的執(zhí)行時(shí)間,可提高程序性能。因此在日志系統(tǒng)里如果可以為每個(gè)線程建立日志文件,那么特別適合使用Thread-Specific Storage模式。

    注意事項(xiàng):

    采用Thread-Specific Storage模式意味著將線程特有信息放在線程外部,在示例程序里,我們將線程特有的TSLog放在了ThreadLocal的實(shí)例里。通常我們一般將線程特有的信息放在線程內(nèi)部,比如建立一個(gè)Thread類(lèi)的子類(lèi)MyThread,我們聲明的MyThread的字段,就是線程特有的信息。因?yàn)榘丫€程特有信息放在線程外部,每個(gè)線程訪問(wèn)線程獨(dú)有信息時(shí),會(huì)取出自己獨(dú)有信息,但是調(diào)試時(shí)會(huì)困難一些,因?yàn)橛须[藏的context(當(dāng)前線程環(huán)境), 程序以前的行為,也可能會(huì)使context出現(xiàn)異常,而是造成現(xiàn)在的bug的真正原因,我們比較難找到線程先前的什么行為導(dǎo)致context出現(xiàn)異常。

    設(shè)計(jì)多線程程序,主體是指主動(dòng)操作的對(duì)象,一般指線程,客體指線程調(diào)用的對(duì)象,一般指的是任務(wù)對(duì)象,會(huì)因?yàn)橹攸c(diǎn)放在“主體”與“客體”的不同,有兩種開(kāi)發(fā)方式:

    • Actor-based 注重主體
    • Task-based 注重客體

    Actor-based 注重主體,偏重于線程,由線程維護(hù)狀態(tài),將工作相關(guān)的信息都放到線程類(lèi)的字段,類(lèi)似這樣

    class Actor extends Thread {操作者內(nèi)部的狀態(tài)public void run(){從外部取得任務(wù),改變自己內(nèi)部狀態(tài)的循環(huán)} }

    Task-based注重客體,將狀態(tài)封裝到任務(wù)對(duì)象里,在線程之間傳遞這些任務(wù)對(duì)象,這些任務(wù)對(duì)象被稱(chēng)為消息,請(qǐng)求或者命令。使用這種開(kāi)發(fā)方式的最典型的例子是Worker Thread模式,生產(chǎn)者消費(fèi)者模式。任務(wù)類(lèi)似這樣:

    class Task implements Runnable{執(zhí)行任務(wù)所需的信息public void run(){ 執(zhí)行任務(wù)所需的處理內(nèi)容} }

    實(shí)際上這兩個(gè)開(kāi)發(fā)方式是混用的,本人剛設(shè)計(jì)多線程程序時(shí),總是基于Actor-based的思維方式,甚至在解決生產(chǎn)者消費(fèi)者問(wèn)題時(shí)也使用Actor-based思維方式,造成程序結(jié)構(gòu)混亂,因此最好按實(shí)際場(chǎng)景來(lái),適合使用Actor-based開(kāi)發(fā)方式的就使用Actor-based,適合Task-based開(kāi)發(fā)方式的就使用Task-based。

    12. Active Object

    Active Object模式,也稱(chēng)為Actor模式。Active Object即主動(dòng)對(duì)象,它不僅擁有獨(dú)立線程,并且可以從外部接收異步消息,并能配合需要返回處理結(jié)果。這里的Active Object不是指一個(gè)對(duì)象,而是指將一群對(duì)象組織起來(lái),對(duì)外表現(xiàn)為一個(gè)整體,這個(gè)整體擁有獨(dú)立線程,并能接收外部的異步消息,這個(gè)整體(Active Object)處理完異步消息后還可以返回結(jié)果給調(diào)用者。
    Future Pattern也能接收異步消息并返回處理結(jié)果,但是該模式聚焦在Future上,不是很關(guān)注線程主動(dòng)執(zhí)行方面,而Activie Object將獨(dú)立線程,接收異步消息并返回處理結(jié)果這些方面看作一個(gè)整體。Active Object模式綜合利用了先前介紹的Producer-Consumer模式,Thread-Per-Message模式,Future模式等多線程設(shè)計(jì)模式。

    示例程序:

    代碼可上github下載: https://github.com/cloudchou/Multithread_ActiveObject

    類(lèi)圖如下圖所示(請(qǐng)點(diǎn)擊看大圖):

    這是一個(gè)非常復(fù)雜的模式,ActiveObject接口里的每個(gè)方法對(duì)應(yīng)MethodRequest的一個(gè)子類(lèi),每個(gè)方法的參數(shù)對(duì)應(yīng)著MethodRequest的一個(gè)字段,因?yàn)锳ctiveObject的某些方法有返回值,故此設(shè)計(jì)了Result抽象類(lèi),表示返回值,為了讓調(diào)用和執(zhí)行分離,這里使用了Future模式,故此設(shè)計(jì)了三個(gè)類(lèi),Result,FutureResult,RealResult。
    也是為了分離調(diào)用和執(zhí)行,還使用了生產(chǎn)者消費(fèi)者模式,將調(diào)用轉(zhuǎn)化為請(qǐng)求對(duì)象放到ActivationQueue里,由SchedulerThread實(shí)例從ActivationQueue里不斷取出請(qǐng)求對(duì)象,并執(zhí)行。

    適用場(chǎng)景:

    這個(gè)設(shè)計(jì)模式非常復(fù)雜,是否合適要考慮問(wèn)題的規(guī)模,只有大規(guī)模的問(wèn)題才適合使用該模式。

    注意事項(xiàng):

    因?yàn)檫@個(gè)設(shè)計(jì)模式非常復(fù)雜,故此我們?cè)谑褂脮r(shí),一定注意各個(gè)對(duì)象的方法由哪些線程調(diào)用。比如Proxy對(duì)象的方法可能被多個(gè)線程同時(shí)調(diào)用,而Servant對(duì)象被封閉在Scheduler線程里,只有SchedulerThread線程才會(huì)調(diào)用它的方法,故此它是線程安全的,而RealResult可能會(huì)被多個(gè)線程使用,但它是Immutable的,FutureResult可能被多個(gè)線程同時(shí)調(diào)用,它封裝了兩個(gè)字段,故此需要使用synchronized保護(hù),并且是采用Guarded Suspension模式保護(hù)FutureResult。

    總結(jié)

    以上是生活随笔為你收集整理的Java 多线程设计模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。