閱讀833 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java 筆記08

多線程

進程:任務

任務並發執行是一個宏觀概念,微觀上是串行的。

進程的調度是有OS負責的(有的係統為獨占式,有的係統為共享式,根據重要性,進程有優先級)。

 

由OS將時間分為若幹個時間片。

JAVA在語言級支持多線程。

分配時間的仍然是OS。

參看P377

 

線程由兩種實現方式:

第一種方式:

class MyThreadextends Thread{

   public void run(){

   需要進行執行的代碼,如循環。

}

}

 

public classTestThread{

  main(){

   Thread t1=new Mythread();

   T1.start();

}

}

 

隻有等到所有的線程全部結束之後,進程才退出。

 

第二種方式:

ClassMyThread  implements Runnable{

 Public  void run(){

 Runnable target=new MyThread();

 Thread t3=new Thread(target);

 Thread.start();//啟動線程

}

}

P384:通過接口實現繼承

 

練習:寫兩個線程:

①  輸入200個“###”②輸入200個“***”

 

下麵為線程中的7中非常重要的狀態:(有的書上也隻有認為前五種狀態:而將“鎖池”和“等待隊列”都看成是“阻塞”狀態的特殊情況:這種認識也是正確的,但是將“鎖池”和“等待隊列”單獨分離出來有利於對程序的理解)

 

 


                  ①              ⑴

                 ②                   ⑵

                ③                        ⑶             run()結束

    Start()

                           OS分配CPU

 

                        CPU時間片結束

                             yield()                      o.wait()

                                            等待鎖標記

 

 


                                                notify()

注意:圖中標記依次為

①輸入完畢;②wake up③t1退出

⑴如等待輸入(輸入設備進行處理,而CUP不處理),則放入阻塞,直到輸入完畢。

⑵線程休眠sleep()

⑶t1.join()指停止main(),然後在某段時間內將t1加入運行隊列,直到t1退出,main()才結束。

特別注意:①②③與⑴⑵⑶是一一對應的。

 

進程的休眠:Thread sleep(1000);//括號中以毫秒為單位

當main()運行完畢,即使在結束時時間片還沒有用完,CPU也放棄此時間片,繼續運行其他程序。

Try{Thread.sleep(1000);}

Catch(Exceptione){e.printStackTrace(e);}

T1.join()表示運行線程放棄執行權,進入阻塞狀態。

當t1結束時,main()可以重新進入運行狀態。

T1.join實際上是把並發的線程編程並行運行。

線程的優先級:1-10,越大優先級越高,優先級越高被OS選中的可能性就越大。(不建議使用,因為不同操作係統的優先級並不相同,使得程序不具備跨平台性,這種優先級隻是粗略地劃分)。

注:程序的跨平台性:除了能夠運行,還必須保證運行的結果。

 

一個使用yield()就馬上交出執行權,回到可運行狀態,等待OS的再次調用。

 

下午:

程序員需要關注的線程同步和互斥的問題。

多線程的並發一般不是程序員決定,而是由容器決定。

多線程出現故障的原因:

兩個線程同時訪問一個數據資源(臨界資源),形成數據發生不一致和不完整。

數據的不一致往往是因為一個線程中的兩個關聯的操作隻完成了一步。

 

避免以上的問題可采用對數據進行加鎖的方法

每個對象除了屬性和方法,都有一個monitor(互斥鎖標記),用來將這個對象交給一個線程,隻有拿到monitor的線程才能夠訪問這個對象。

Synchronized:這個修飾詞可以用來修飾方法和代碼塊

 

Object obj;

Obj.setValue(123);

Synchronized用來修飾方法,表示當某個線程調用這個方法之後,其他的事件不能再調用這個方法。隻有拿到obj標記的線程才能夠執行代碼塊。

注意:Synchronized一定使用在一個方法中。

鎖標記是對象的概念,加鎖是對對象加鎖,目的是在線程之間進行協調。

 

當用Synchronized修飾某個方法的時候,表示該方法都對當前對象加鎖。

給方法加Synchronized和用Synchronized修飾對象的效果是一致的。

 

一個線程可以拿到多個鎖標記,一個對象最多隻能將monitor給一個線程。

Synchronized是以犧牲程序運行的效率為代價的,因此應該盡量控製互斥代碼塊的範圍。

 

方法的Synchronized特性本身不會被繼承,隻能覆蓋。

 

線程因為未拿到鎖標記而發生的阻塞不同於前麵五個基本狀態中的阻塞,稱為鎖池。

每個對象都有自己的一個鎖池的空間,用於放置等待運行的線程。

這些線程中哪個線程拿到鎖標記由係統決定。

 

鎖標記如果過多,就會出現線程等待其他線程釋放鎖標記,而又都不釋放自己的鎖標記供其他線程運行的狀況。就是死鎖。

死鎖的問題通過線程間的通信的方式進行解決。

線程間通信機製實際上也就是協調機製。

線程間通信使用的空間稱之為對象的等待隊列,則個隊列也是屬於對象的空間的。

Object類中又一個wait(),在運行狀態中,線程調用wait(),此時表示著線程將釋放自己所有的鎖標記,同時進入這個對象的等待隊列。

等待隊列的狀態也是阻塞狀態,隻不過線程釋放自己的鎖標記。

Notify()

如果一個線程調用對象的notify(),就是通知對象等待隊列的一個線程出列。進入鎖池。如果使用notifyall()則通知等待隊列中所有的線程出列。

 

注意:隻能對加鎖的資源進行wait()和notify()。

 

釋放鎖標記隻有在Synchronized代碼結束或者調用wait()。

注意鎖標記是自己不會自動釋放,必須有通知。

注意在程序中判定一個條件是否成立時要注意使用WHILE要比使用IF要嚴密。

WHILE會放置程序饒過判斷條件而造成越界。

補充知識:

suspend()是將一個運行時狀態進入阻塞狀態(注意不釋放鎖標記)。恢複狀態的時候用resume()。Stop()指釋放全部。

這幾個方法上都有Deprecated標誌,說明這個方法不推薦使用。

 

一般來說,主方法main()結束的時候線程結束,可是也可能出現需要中斷線程的情況。對於多線程一般每個線程都是一個循環,如果中斷線程我們必須想辦法使其退出。


如果主方法main()想結束阻塞中的線程(比如sleep或wait)

那麼我們可以從其他進程對線程對象調用interrupt()。用於對阻塞(或鎖池)會拋出例外InterruptedException。

這個例外會使線程中斷並執行catch中代碼。

 

多線程中的重點:實現多線程的兩種方式,Synchronized,以及生產者和消費者問題(ProducerConsumer.java文件)。

 

練習:

①  存車位的停開車的次序輸出問題;

②  寫兩個線程,一個線程打印1-52,另一個線程答應字母A-Z。打印順序為12A34B56C……5152Z。通過使用線程之間的通信協調關係。

注:分別給兩個對象構造一個對象o,數字每打印兩個或字母每打印一個就執行o.wait()。在o.wait()之前不要忘了寫o.notify()。

 

補充說明:通過Synchronized,可知Vector較ArrayList方法的區別就是Vector所有的方法都有Synchronized。所以Vector更為安全。

同樣:Hashtable較HashMap也是如此。b><�u! t���0ϛ-size:12.0pt;font-family:宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>是a與b的值的比較。

 

 

注意下麵程序:

student a=new student(“LUCY”,20);

student b=new student(“LUCY”,20);

System.out.println(a==b);

System.out.println(a.equal(b));

此時返回的結果均為false。

 

以下為定義equal(加上這個定義,返回ture或false)

public booleanequals(Object o){

  student s=(student)o;

  if(s.name.equals(this.name)&&s.age==this.age)

else returnfalse;

}如果equals()返回的值為

 

以下為實現標準equals的流程:

public booleanequals(Object o){

  if (this==o) return trun;  //此時兩者相同
  if (o==null) return false;

  if (! o instanceof strudent) returnfalse;  //不同類

  studeng s=(student)o; //強製轉換

  if(s.name.equals(this.name)&&s.age==this.age) return true;

else returnfalse;

}

 

以上過程為實現equals的標準過程。

 

 練習:建立一個employee類,有String name,int id,doublesalary.運用getset方法,使用toString,使用equals

 

封裝類:

JAVA為每一個簡單數據類型提供了一個封裝類,使每個簡單數據類型可以被Object來裝載。

除了int和char,其餘類型首字母大寫即成封裝類。

轉換字符的方式:

int I=10;

String s=I+” ”;

Strings1=String.valueOf(i);

 

Int I=10;

IntergerI_class=new integer(I);

 

看javadoc的幫助文檔。

附加內容:

“==”在任何時候都是比較地址,這種比較永遠不會被覆蓋。

 

程序員自己編寫的類和JDK類是一種合作關係。(因為多態的存在,可能存在我們調用JDK類的情況,也可能存在JDK自動調用我們的類的情況。)

注意:類型轉換中double\interger\string之間的轉換最多。

最後更新:2017-04-02 16:47:44

  上一篇:go android網絡業務的封裝與調度
  下一篇:go Spring中應用反射機製淺析