java基礎(六)多線程/設計模式

這輩子沒辦法作太多事情,因此每一件都要作到精彩絕倫!java

People can't do too many things in my life,so everything will be wonderful   面試

 

本資料只進行簡單介紹說明以及相關重要問題的解答;關於類的詳細說明及使用請參考java API文檔算法

本文參考的依據是:JDK API 1.6.0 中文版,下載地址:http://down.51cto.com/data/2300228spring

1.     多線程

wKiom1j1eOTR2pMJAABsvh-6yuM484.png

1.1     多線程概述

(1)多線程:一個應用程序有多條執行路徑編程

                   進程:正在執行的應用程序設計模式

                   線程:進程的執行單元,執行路徑安全

                   單線程:一個應用程序只有一條執行路徑網絡

                   多線程:一個應用程序有多條執行路徑多線程

                  

                   多進程的意義?框架

                            提升CPU的使用率

                   多線程的意義?

                            提升應用程序的使用率

(2)Java程序的運行原理及JVM的啓動是多線程的嗎?

                   A:Java命令去啓動JVMJVM會啓動一個進程,該進程會啓動一個主線程。

    java 命令會啓動java 虛擬機,啓動JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個「主線程」 ,而後主線程去調用某個類的 main 方法。因此main方法運行在主線程中。在此以前的全部程序都是單線程的。

                   B:JVM的啓動是多線程的,由於它最低有兩個線程啓動了,主線程和垃圾回收線程。

1.2     多線程實現

1.2.1       方案一Thread

l  經過查看API來學習多線程程序的實現

    參考Thread

l  繼承Thread

    幾個小問題:

    爲何要重寫run()方法

    啓動線程使用的是那個方法

    線程能不能屢次啓動

    run()start()方法的區別:

1 start

start方法來啓動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。經過調用Thread類的start()方法來啓動一個線程,這時此線程處於就緒(可運行)狀態,並無運行,一旦獲得spu時間片,就開始執行run()方法,這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容,Run方法運行結束,此線程隨即終止。

 

2 run

run()方法只是類的一個普通方法而已,若是直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑仍是隻有一條,仍是要順序執行,仍是要等待run方法體執行完畢後纔可繼續執行下面的代碼,這樣就沒有達到寫線程的目的。

l  Thread類的基本獲取和設置方法

    public final String getName()

    public final void setName(String name)

    其實經過構造方法也能夠給線程起名字

    思考:

    如何獲取main方法所在的線程名稱呢?

    public static Thread currentThread()

l 這樣就能夠獲取任意方法所在的線程名稱

1.2.2       方案二Runnable接口

實現Runnable接口

如何獲取線程名稱

如何給線程設置名稱

實現接口方式的好處

能夠避免因爲Java單繼承帶來的侷限性。

適合多個相同程序的代碼去處理同一個資源的狀況,把線程同程序的代碼,數據有效分離,較好的體現了面向對象的設計思想。

wKioL1j1ePCCYDeSAABeBA_nGOc970.png

1.2.3       方案3實現Callable接口

實現Callable接口

步驟和1.9線程其餘概述中的線程池執行Runnable對象的差很少。

好處:

能夠有返回值

能夠拋出異常

弊端:

代碼比較複雜,因此通常不用

1.3     線程調度

假如咱們的計算機只有一個 CPU,那麼 CPU 在某一個時刻只能執行一條指令,線程只有獲得 CPU時間片,也就是使用權,才能夠執行指令。那麼Java是如何對線程進行調用的呢?

線程有兩種調度模型:

分時調度模型   全部線程輪流使用 CPU 的使用權,平均分配每一個線程佔用 CPU 的時間片

搶佔式調度模型   優先讓優先級高的線程使用 CPU,若是線程的優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。

Java使用的是搶佔式調度模型。

如何設置和獲取線程優先級

public final int getPriority()

public final void setPriority(intnewPriority)

a:默認是5

                            b:範圍是1-10

1.4     線程控制

線程休眠

public static void sleep(long millis)

線程加入

public final void join()

線程禮讓

public static void yield()

後臺線程

public final void setDaemon(boolean on)

中斷線程

public final void stop()

public void interrupt()

1.5     線程的生命週期

 

wKiom1j1ePrye4egAABTlq12qNU685.png

1.6     多線程案例

某電影院目前正在上映賀歲大片,共有100張票,而它有3個售票窗口售票,請設計一個程序模擬該電影院售票

代碼:

/*

 *

 * 同步的特色:

 *             前提:

 *                      多個線程

 *              解決問題的時候要注意:

 *                       多個線程使用的是同一個鎖對象

 * 同步的好處

 *              同步的出現解決了多線程的安全問題。

 * 同步的弊端

 *              當線程至關多時,由於每一個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會下降程序的運行效率。

 */

public class SellTicketDemo {

         publicstatic void main(String[] args) {

                   //建立資源對象

                   SellTicketst = new SellTicket();

 

                   //建立三個線程對象

                   Threadt1 = new Thread(st, "窗口1");

                   Threadt2 = new Thread(st, "窗口2");

                   Threadt3 = new Thread(st, "窗口3");

 

                   //啓動線程

                   t1.start();

                   t2.start();

                   t3.start();

         }

}

public class SellTicket implements Runnable{

 

         //定義100張票

         privateint tickets = 100;

 

         //定義同一把鎖

         privateObject obj = new Object();

 

         @Override

         publicvoid run() {

                   while(true) {

                            //t1,t2,t3都能走到這裏

                            //假設t1搶到CPU的執行權,t1就要進來

                            //假設t2搶到CPU的執行權,t2就要進來,發現門是關着的,進不去。因此就等着。

                            //(,)

                            synchronized(obj) { // 發現這裏的代碼未來是會被鎖上的,因此t1進來後,就鎖了。()

                                     if(tickets > 0) {

                                               try{

                                                        Thread.sleep(100);// t1就睡眠了

                                               }catch (InterruptedException e) {

                                                        e.printStackTrace();

                                               }

                                               System.out.println(Thread.currentThread().getName()

                                                                 +"正在出售第" + (tickets--) + "張票 ");

                                               //窗口1正在出售第100張票

                                     }

                            }//t1就出來可,而後就開門。()

                   }

         }

}

添加:每次賣票延遲100毫秒!

出現問題:相同的票出現屢次,還出現了負數的票

1.7     多線程安全問題

上述出現的問題

l  問題

    相同的票出現屢次

    CPU的一次操做必須是原子性的

    還出現了負數的票

    隨機性和延遲致使的

l  注意

    線程安全問題在理想狀態下,不容易出現,但一旦出現對軟件的影響是很是大的。

解決思路:

l  首先想爲何出現問題?(也是咱們判斷是否有問題的標準)

    是不是多線程環境

    是否有共享數據

    是否有多條語句操做共享數據

l  如何解決多線程安全問題呢?

    基本思想:讓程序沒有安全問題的環境。

    怎麼實現呢?

    把多個語句操做共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行便可。

1.7.1       方案一同步代碼塊

同步代碼塊

格式:

                   synchronized(對象){須要同步的代碼;}

同步能夠解決安全問題的根本緣由就在那個對象上。該對象如同鎖的功能。

l  同步的前提

    多個線程

    多個線程使用的是同一個鎖對象

l  同步的好處

    同步的出現解決了多線程的安全問題。

l  同步的弊端

    當線程至關多時,由於每一個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會下降程序的運行效率。

1.7.2       方案二

同步方法

就是把同步關鍵字加到方法上。

同步方法的鎖對象是什麼呢?

這裏的鎖對象是this     

若是是靜態方法,同步方法的鎖對象又是什麼呢?

靜態同步方法

                            把同步加在方法上。

                            這裏的鎖對象是當前類的字節碼文件對象

那麼,咱們到底使用誰?

若是鎖對象是this,就能夠考慮使用同步方法。

不然能使用同步代碼塊的儘可能使用同步代碼塊。

 

1.8     Lock

  • 雖然咱們能夠理解同步代碼塊和同步方法的鎖對象問題,可是咱們並無直接看到在哪裏加上了鎖,在哪裏釋放了鎖,爲了更清晰的表達如何加鎖和釋放鎖,JDK5之後提供了一個新的鎖對象Lock

Lock

void lock()

void unlock()

ReentrantLock

  • 死鎖問題:

同步弊端

效率低

若是出現了同步嵌套,就容易產生死鎖問題

死鎖問題及其代碼

是指兩個或者兩個以上的線程在執行的過程當中,因爭奪資源產生的一種互相等待現象

1.9     線程其餘概述

  • 等待喚醒機制

wKioL1j1eQmjgrXQAABXRKLkACk781.png

  • 線程間通訊

  • 線程的狀態轉換圖

wKiom1j1eRrDW6btAAFZfuOXBiA047.png

  • 線程組

Java中使用ThreadGroup來表示線程組,它能夠對一批線程進行分類管理,Java容許程序直接對線程組進行控制。

默認狀況下,全部的線程都屬於主線程組。

public final ThreadGroup getThreadGroup()

咱們也能夠給線程設置分組

Thread(ThreadGroup group,Runnable target, String name)

  • 線程池

程序啓動一個新線程成本是比較高的,由於它涉及到要與操做系統進行交互。而使用線程池能夠很好的提升性能,尤爲是當程序中要建立大量生存期很短的線程時,更應該考慮使用線程池。

線程池裏的每個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。

JDK5以前,咱們必須手動實現本身的線程池,從JDK5開始,Java內置支持線程池

JDK5新增了一個Executors工廠類來產生線程池,有以下幾個方法

    public static ExecutorService newCachedThreadPool()

    public static ExecutorService newFixedThreadPool(int nThreads)

    public static ExecutorService newSingleThreadExecutor()

    這些方法的返回值是ExecutorService對象,該對象表示一個線程池,能夠執行Runnable對象或者Callable對象表明的線程。它提供了以下方法

    Future<?> submit(Runnable task)

    <T> Future<T> submit(Callable<T> task)

  • 匿名內部類方式使用多線程

匿名內部類方式使用多線程

new Thread(){代碼…}.start();

New Thread(new Runnable(){代碼…}).start();

1.10定時器

定時器是一個應用十分普遍的線程工具,可用於調度多個定時任務之後臺線程的方式執行。在Java中,能夠經過TimerTimerTask類來實現定義調度的功能

Timer

public Timer()

public void schedule(TimerTask task,long delay)

public void schedule(TimerTask task,longdelay,long period)

TimerTask

public abstract void run()

public boolean cancel()

開發中:

Quartz是一個徹底由java編寫的開源調度框架。通常直接使用spring自帶的定時器。

2.     設計模式

  • 設計模式概述

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。

設計模式不是一種方法和技術,而是一種思想

設計模式和具體的語言無關,學習設計模式就是要創建面向對象的思想,儘量的面向接口編程,低耦合,高內聚,使設計的程序可複用

學習設計模式可以促進對面向對象思想的理解,反之亦然。它們相輔相成

l 設計模式的幾個要素

    名字 必須有一個簡單,有意義的名字

    問題 描述在什麼時候使用模式

    解決方案 描述設計的組成部分以及如何解決問題

    效果 描述模式的效果以及優缺點

l 設計模式的分類

    建立型模式 對象的建立

    結構型模式 對象的組成(結構)

    行爲型模式 對象的行爲

 

2.1     對象的常見設計原則

(1)面試對象的常見設計原則(具體含義可參閱《大話設計模式》或百度)

  • 單一職責:其實就是開發人員常常說的」高內聚,低耦合」

也就是說,每一個類應該只有一個職責,對外只能提供一種功能,而引發類變化的緣由應該只有一個。在設計模式中,全部的設計模式都遵循這一原則。

  • 開閉:核心思想是:一個對象對擴展開放,對修改關閉。

其實開閉原則的意思就是:對類的改動是經過增長代碼進行的,而不是修改現有代碼。

也就是說軟件開發人員一旦寫出了能夠運行的代碼,就不該該去改動它,而是要保證它能一直運行下去,如何可以作到這一點呢?這就須要藉助於抽象和多態,即把可能變化的內容抽象出來,從而使抽象的部分是相對穩定的,而具體的實現則是能夠改變和擴展的

  • 里氏替換:

核心思想:在任何父類出現的地方均可以用它的子類來替代。

其實就是說:同一個繼承體系中的對象應該有共同的行爲特徵。

  • 依賴注入:

核心思想:要依賴於抽象,不要依賴於具體實現。

其實就是說:在應用程序中,全部的類若是使用或依賴於其餘的類,則應該依賴這些其餘類的抽象類,而不是這些其餘類的具體類。爲了實現這一原則,就要求咱們在編程的時候針對抽象類或者接口編程,而不是針對具體實現編程。

  • 接口分離:

核心思想:不該該強迫程序依賴它們不須要使用的方法。

其實就是說:一個接口不須要提供太多的行爲,一個接口應該只提供一種對外的功能,不該該把全部的操做都封裝到一個接口中。

  • 迪米特:

核心思想:一個對象應當對其餘對象儘量少的瞭解

其實就是說:下降各個對象之間的耦合,提升系統的可維護性。在模塊之間應該只經過接口編程,而不理會模塊的內部工做原理,它可使各個模塊耦合度降到最低,促進軟件的複用

2.2     設計模式概述和分類

                   A:經驗的總結

                   B:三類

                            建立型

                            結構型

                            行爲型

改進的設計模式

A:簡單工廠模式

簡單工廠模式概述

又叫靜態工廠方法模式,它定義一個具體的工廠類負責建立一些類的實例

優勢

客戶端不須要在負責對象的建立,從而明確了各個類的職責

缺點

這個靜態工廠類負責全部對象的建立,若是有新的對象增長,或者某些對象的建立方式不一樣,就須要不斷的修改工廠類,不利於後期的維護

B:工廠方法模式

工廠方法模式概述

工廠方法模式中抽象工廠類負責定義建立對象的接口,具體對象的建立工做由繼承抽象工廠的具體類實現。

優勢

客戶端不須要在負責對象的建立,從而明確了各個類的職責,若是有新的對象增長,只須要增長一個具體的類和具體的工廠類便可,不影響已有的代碼,後期維護容易,加強了系統的擴展性

缺點

須要額外的編寫代碼,增長了工做量

C:單例模式

單例設計思想

保證類在內存中只有一個對象

如何實現類在內存中只有一個對象呢?

構造私有

自己提供一個對象

經過公共的方法讓外界訪問

單例設計模式分類

l  餓漢式(開發)

l  懶漢式(面試)

    線程安全問題

    懶加載思想(延遲加載)

單例模式應用類:Runtime

D:裝飾者設計模式

裝飾設計模式概述

裝飾模式就是使用被裝飾類的一個子類的實例,在客戶端將這個子類的實例交給裝飾類。是繼承的替代方案

優勢

使用裝飾模式,能夠提供比繼承更靈活的擴展對象的功能,它能夠動態的添加對象的功能,而且能夠隨意的組合這些功能

缺點

正由於能夠隨意組合,因此就可能出現一些不合理的邏輯

 

3.     面試題

3.1     多線程有幾種實現方案,分別是哪幾種?

參照1.2

3.2     同步有幾種方式,分別是什麼?

參考1.7

3.3     啓動一個線程是run()仍是start()?它們的區別?

參考1.2.1

3.4     sleep()wait()方法的區別

1、這兩個方法來自不一樣的類分別是ThreadObject

2、最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其餘線程可使用同步控制塊或者方法。

3waitnotifynotifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep能夠在

任何地方使用(使用範圍)

4sleep必須捕獲異常,而waitnotifynotifyAll不須要捕獲異常

3.5     爲何wait(),notify(),notifyAll()等方法都定義在Object類中

1,這些方法存在與同步中。

2,使用這些方法時必需要標識所屬的同步的鎖。

3,鎖能夠是任意對象,因此任意對象調用的方法必定定義Object類中。

3.6     線程的生命週期圖

參考1.5


java基礎系列:

java基礎(一)java語法

java基礎(二)面向對象

java基礎(三)繼承/多態/接口

java基礎(四)經常使用類/算法

java基礎(五)集合/IO流/異常

java基礎(六)多線程/設計模式

java基礎(七)網絡編程/反射/動態代理

相關文章
相關標籤/搜索