多線程詳解

多線程是java應用程序的一個特色,掌握java的多線程也是做爲一 java程序員必備的知識。多線程指的是在單個程序中能夠同時運行多個 同的線程執行不一樣的任務.線程是程序內的順序控制流,只能使用分配給 序的資源和環境。還記得剛開始學習的時候老是和進程分不清,老是對 這兩個名詞所迷惑。

下面就首先對這兩個名詞區分來做爲本篇博客的開始: java


    1、線程與進程的區別 程序員


多個進程的內部數據和狀態都是徹底獨立的,而多線程是共享一塊內存空間和一組系統資源,有可能互相影響. •線程自己的數據一般只有寄存器數據,以及一個程序執行時使用的堆棧,因此線程的切換比進程切換的負擔要小。 編程


 多線程編程的目的,就是"最大限度地利用CPU資源",當某一線程的處理不須要佔用CPU而只和I/O等資源打交道時,讓須要佔用CPU資源的其它線程有機會得到CPU資源。從根本上說,這就是多線程編程的最終目的。 數組


2、瞭解一下java在多線程中的基礎知識 多線程


1.Java中若是咱們本身沒有產生線程,那麼系統就會給咱們產生一個線程(主線程,main方法就在主線程上運行),咱們的程序都是由線程來執行的。 併發


 2. 進程:執行中的程序(程序是靜態的概念,進程是動態的概念)。  post


3. 線程的實現有兩種方式,第一種方式是繼承Thread類,而後重寫run方法;第二種是實現Runnable接口,而後實現其run方法。  學習


4. 將咱們但願線程執行的代碼放到run方法中,而後經過start方法來啓動線程,start方法首先爲線程的執行準備好系統資源,而後再去調用run方法。當某個類繼承了Thread類以後,該類就叫作一個線程類。  spa


5. 一個進程至少要包含一個線程。  .net


6. 對於單核CPU來講,某一時刻只能有一個線程在執行(微觀串行),從宏觀角度來看,多個線程在同時執行(宏觀並行)。 


7. 對於雙核或雙核以上的CPU來講,能夠真正作到微觀並行。


3、Thread源碼研究: 


1) Thread類也實現了Runnable接口,所以實現了Runnable接口中的run方法; 


2) 當生成一個線程對象時,若是沒有爲其設定名字,那麼線程對象的名字將使用以下形式:Thread-number,該number將是自動增長的,並被全部的Thread對象所共享(由於它是static的成員變量)。 


3) 當使用第一種方式來生成線程對象時,咱們須要重寫run方法,由於Thread類的run方法此時什麼事情也不作。


4)當使用第二種方式生成線程對象時,咱們須要實現Runnable接口的run方法,而後使用new Thread(new MyThread())(假如MyThread已經實現了Runnable接口)來生成線程對象,這時的線程對象的run方法或調就會MyThread類的run方法,這樣咱們本身編寫的run方法就執行了。


說明: 


Public void run(){


If(target!=null){


Target.run();


}}


 


當使用繼承Thread生成線程對象時,target爲空,什麼也不執行,當使用第二種方式生成時,執行target.run(),target爲runnable的實例對象,即爲執行重寫後的方法


總結:兩種生成線程對象的區別:


1.兩種方法均需執行線程的start方法爲線程分配必須的系統資源、調度線程運行並執行線程的run方法。 


2.在具體應用中,採用哪一種方法來構造線程體要視狀況而定。一般,當一個線程已繼承了另外一個類時,就應該用第二種方法來構造,即實現Runnable接口。 


四:線程的生命週期:



由上圖能夠看出,一個線程由出生到死亡分爲五個階段:


1).建立狀態 


•當用new操做符建立一個新的線程對象時,該線程處於建立狀態。 


•處於建立狀態的線程只是一個空的線程對象,系統不爲它分配資源 


2). 可運行狀態 


•執行線程的start()方法將爲線程分配必須的系統資源,安排其運行,並調用線程體—run()方法,這樣就使得該線程處於可運行( Runnable )狀態。 


•這一狀態並非運行中狀態(Running ),由於線程也許實際上並未真正運行。 


3).不可運行狀態 


.當發生下列事件時,處於運行狀態的線程會轉入到不可運行狀態。 


調用了sleep()方法; 


•線程調用wait方法等待特定條件的知足 


•線程輸入/輸出阻塞 


4返回可運行狀態: 


•處於睡眠狀態的線程在指定的時間過去後 


•若是線程在等待某一條件,另外一個對象必須經過notify()notifyAll()方法通知等待線程條件的改變 


•若是線程是由於輸入/輸出阻塞,等待輸入/輸出完成 


5. 消亡狀態 


當線程的run方法執行結束後,該線程天然消亡。 


注意:


1.中止線程的方式:不能使用Thread類的stop方法來終止線程的執行。通常要設定一個變量,在run方法中是一個循環,循環每次檢查該變量,若是知足條件則繼續執行,不然跳出循環,線程結束。 


2.不能依靠線程的優先級來決定線程的執行順序。 


五:多線程併發


多線程併發是線程同步中比較常見的現象,java多線程爲了不多線程併發解決多線程共享數據同步問題提供了synchronized關鍵字


synchronized關鍵字:當synchronized關鍵字修飾一個方法的時候,該方法叫作同步方法。 


1.Java中的每一個對象都有一個鎖(lock)或者叫作監視器(monitor),當訪問某個對象的synchronized方法時,表示將該對象上鎖,此時其餘任何線程都沒法再去訪問該synchronized方法了,直到以前的那個線程執行方法完畢後(或者是拋出了異常),那麼將該對象的鎖釋放掉,其餘線程纔有可能再去訪問該synchronized方法。 


2. 若是一個對象有多個synchronized方法,某一時刻某個線程已經進入到了某個synchronized方法,那麼在該方法沒有執行完畢前,其餘線程是沒法訪問該對象的任何synchronized方法的。 


 


3.若是某個synchronized方法是static的,那麼當線程訪問該方法時,它鎖的並非synchronized方法所在的對象,而是synchronized方法所在的對象所對應的Class對象由於Java中不管一個類有多少個對象,這些對象會對應惟一一個Class對象,所以當線程分別訪問同一個類的兩個對象的兩個static,synchronized方法時,他們的執行順序也是順序的,也就是說一個線程先去執行方法,執行完畢後另外一個線程纔開始執行。 


4. synchronized塊,寫法: 


synchronized(object) 




表示線程在執行的時候會對object對象上鎖。 


5.synchronized方法是一種粗粒度的併發控制,某一時刻,只能有一個線程執行該synchronized方法;synchronized塊則是一種細粒度的併發控制,只會將塊中的代碼同步,位於方法內、synchronized塊以外的代碼是能夠被多個線程同時訪問到的。 


同步的線程狀態圖:



六:wait與notify


1.wait與notify方法都是定義在Object類中,並且是final的,所以會被全部的Java類所繼承而且沒法重寫。這兩個方法要求在調用時線程應該已經得到了對象的鎖,所以對這兩個方法的調用須要放在synchronized方法或塊當中。當線程執行了wait方法時,它會釋放掉對象的鎖。 


2. 另外一個會致使線程暫停的方法就是Thread類的sleep方法,它會致使線程睡眠指定的毫秒數,但線程在睡眠的過程當中是不會釋放掉對象的鎖的。 


3.notify():喚醒在此對象監視器上等待的單個線程。若是全部線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的,並在對實現作出決定時發生。線程經過調用其中一個 wait 方法,在對象的監視器上等待。 


直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其餘全部線程進行競爭;例如,喚醒的線程在做爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。


此方法只應由做爲此對象監視器的全部者的線程來調用。經過如下三種方法之一,線程能夠成爲此對象監視器的全部者:


o 經過執行此對象的同步實例方法。


o 經過執行在此對象上進行同步的 synchronized 語句的正文。


o 對於 Class 類型的對象,能夠經過執行該類的同步靜態方法。


一次只能有一個線程擁有對象的監視器。



 關於成員變量與局部變量:若是一個變量是成員變量,那麼多個線程對同一個對象的成員變量進行操做時,他們對該成員變量是彼此影響的(也就是說一個線程對成員變量的改變會影響到另外一個線程)。  若是一個變量是局部變量,那麼每一個線程都會有一個該局部變量的拷貝,一個線程對該局部變量的改變不會影響到其餘的線程。


七:死鎖的問題: 


定義:線程1鎖住了對象A的監視器,等待對象B的監視器,線程2鎖住了對象B的監視器,等待對象A的監視器,就形成了死鎖。


     致使死鎖的根源在於不適當地運用「synchronized」關鍵詞來管理線程對特定對象的訪問。「synchronized」關鍵詞的做用是,確保在某個時刻只有一個線程被容許執行特定的代碼塊,所以,被容許執行的線程首先必須擁有對變量或對象的排他性訪問權。當線程訪問對象時,線程會給對象加鎖


Java中每一個對象都有一把鎖與之對應。但Java不提供單獨的lockunlock操做。下面筆者分析死鎖的兩個過程上鎖鎖死」 


(1) 上鎖
     許多線程在執行中必須考慮與其餘線程之間共享數據或協調執行狀態,就須要同步機制。所以大多數應用程序要求線程互相通訊來同步它們的動做,在 Java 程序中最簡單實現同步的方法就是上鎖。在 Java 編程中,全部的對象都有鎖。線程可使用 synchronized 關鍵字來得到鎖。在任一時刻對於給定的類的實例,方法或同步的代碼塊只能被一個線程執行。這是由於代碼在執行以前要求得到對象的鎖。


    爲了防止同時訪問共享資源,線程在使用資源的先後能夠給該資源上鎖和開鎖。給共享變量上鎖就使得 Java 線程可以快速方便地通訊和同步。某個線程若給一個對象上了鎖,就能夠知道沒有其餘線程可以訪問該對象。即便在搶佔式模型中,其餘線程也不可以訪問此對象,直到上鎖的線程被喚醒、完成工做並開鎖。那些試圖訪問一個上鎖對象的線程一般會進入睡眠狀態,直到上鎖的線程開鎖。一旦鎖被打開,這些睡眠進程就會被喚醒並移到準備就緒隊列中。


(2)鎖死
     若是程序中有幾個競爭資源的併發線程,那麼保證均衡是很重要的。系統均衡是指每一個線程在執行過程當中都能充分訪問有限的資源,系統中沒有餓死和死鎖的線程。當多個併發的線程分別試圖同時佔有兩個鎖時,會出現加鎖衝突的情形。若是一個線程佔有了另外一個線程必需的鎖,互相等待時被阻塞就有可能出現死鎖。


    在編寫多線程代碼時,筆者認爲死鎖是最難處理的問題之一。由於死鎖可能在最意想不到的地方發生,因此查找和修正它既費時又費力。例如,常見的例子以下面這段程序。print?


1 public int sumArrays(int[] a1, int[] a2){  


2   int value = 0;  


3   int size = a1.length;  


4   if (size == a2.length) {  


5      synchronized(a1) { //1        


6        synchronized(a2) { //2          


7          for (int i=0; i


8             value += a1[i] + a2[i];  


9        }    


10      }    


11   } return value;  


12 }   


這段代碼在求和操做中訪問兩個數組對象以前鎖定了這兩個數組對象。它形式簡短,編寫也適合所要執行的任務;但不幸的是,它有一個潛在的問題。這個問題就是它埋下了死鎖的種子。


ThreadLocal類(這個類本人沒用過,佔時不太懂)

首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,通常狀況下,經過ThreadLocal.set() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。各個線程中訪問的是不一樣的對象。

另外,說ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲mapkey來使用的。

若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題
相關文章
相關標籤/搜索