面向对象程序设计(JAVA)复习笔记(下)
文章目錄
- 五、接口和多態
- 接口
- 塑型
- 多態
- 內部類
- 六、I/O與文件
- 輸入輸出流(java.io)
- 面向字符的流
- 面向字節的流
- 標準輸入輸出流
- 文件
- 二進制文件
- FILE類
- 七、數組集合
- 對象數組
- Java集合框架—Collection接口
- Java集合框架—Set、SortedSet接口
- Iterator類
- TreeSet類
- public native int hashCode()
- Java集合框架—List接口
- Java集合框架—Map、SortedMap接口
- 接口的實現
- 向量
- 集合的遍歷
- 哈希表
- 八、線程
- 多線程編程基礎
- 繼承java.lang包中的Thread類
- 實現Runnable接口
- 線程間的數據共享
- 多線程的同步控制
- 線程之間的通信
- 線程 死鎖
- 避免死鎖
- 后臺線程
- 線程的存活周期
- 控制線程的生命
- 線程的優先級
五、接口和多態
接口
接口
與抽象類一樣都是定義多個類的共同屬性
使抽象的概念更深入了一層,是一個“純”抽象類,它只提供一種形式,并不提供實現
允許創建者規定方法的基本形式:方法名、參數列表以及返回類型,但不規定方法主體
也可以包含基本數據類型的數據成員,但它們都默認為static和final
所有方法都是抽象方法,接口口里面的所有屬性都是常量。
接口的作用
統一開發標準(規范)。接口就是定義了一個標準,使用這個接口的類就是標準的使用者,實現了這個接口的類就是這個標準的實現者。接口的出現,把使用者和實現者之間的直接聯系轉化為間接聯系,把強耦合轉換為弱耦合,從而實現解耦的目的。
實現多繼承,同時免除C++中的多繼承那樣的復雜性
抽象類統一開發標準,但是是單繼承
保險公司的例子
具有車輛保險、人員保險、公司保險等多種保險業務,在對外提供服務方面具有相似性,如都需要計算保險費(premium)等,因此可聲明一個Insurable 接口
在UML圖中,實現接口用帶有空三角形的虛線表示
接口的語法
聲明格式為
[接口修飾符] interface 接口名稱 [extends 父接口名]{…//方法的原型聲明或靜態常量 }接口的數據成員一定要賦初值,且此值將不能再更改,允許省略final關鍵字
接口中的方法必須是“抽象方法”,不能有方法體,允許省略public及abstract關鍵字
接口實例:
接口與匿名內部類實驗
接口的實現
接口不能用new運算符直接產生對象,必須利用其特性設計新的類,再用新類來創建對象
利用接口設計類的過程,稱為接口的實現,使用implements關鍵字
語法如下
注意:
對象轉型
對象可以被轉型為其所屬類實現的接口類型
舉例:
getPolicyNumber、calculatePremium是Insurable接口中聲明的方法
getMileage是Car類新添加的方法,Insurable接口中沒有聲明此方法
多重繼承
Java的設計以簡單實用為導向,不允許一個類有多個父類
但允許一個類可以實現多個接口,通過這種機制可實現多重繼承
一個類實現多個接口的語法如下
接口的擴展
接口的擴展
接口可通過擴展的技術派生出新的接口
原來的接口稱為基接口(base interface)或父接口(super interface)
派生出的接口稱為派生接口(derived interface)或子接口(sub interface)
派生接口不僅可以保有父接口的成員,同時也可加入新成員以滿足實際問題的需要
實現接口的類也必須實現此接口的父接口
接口擴展的語法
Shape是父接口,Shape2D與Shape3D是其子接口。Circle類及Rectangle類實現接口Shape2D,而Box類及Sphere類實現接口Shape3D
實例:
運行:
塑型
塑型(type-casting)
又稱為類型轉換
方式:
塑型的對象包括
基本數據類型
引用變量
將對象暫時當成更一般的對象來對待,并不改變其類型 只能被塑型為任何一個父類類型對象所屬的類實現的一個接口被塑型為父類或接口后,再被塑型回其本身所在的類舉例:
隱式(自動)的類型轉換
基本數據類型
相容類型之間存儲容量低的自動向存儲容量高的類型轉換引用變量
被塑型成更一般的類 Employee emp; emp = new Manager(); //將Manager類型的對象直接賦給//Employee類的引用變量,系統會自動將Manage對象塑 //型為Employee類被塑型為對象所屬類實現的接口類型
Car jetta = new Car(); Insurable item = jetta;顯式(強制)的類型轉換
基本數據類型
(int)871.34354; // 結果為 871 (char)65; // 結果為‘A’ (long)453; // 結果為453L引用變量:還原為本來的類型
Employee emp; Manager man; emp = new Manager(); man = (Manager)emp; //將emp強制塑型為本來的類型塑型應用的場合包括
賦值號右邊的表達式類型或對象轉換為左邊的類型
實參的類型轉換為形參的類型
算數混合運算時,不同類型的項轉換為相同的類型再進行運算
字符串連接運算時,如果一個操作數為字符串,一個操作數為數值型,則會自動將數值型轉換為字符串
當一個類對象被塑型為其父類后,它提供的方法會減少
當Manager對象被塑型為Employee之后,它只能接收getName(),getEmployeeNumber()方法,不能接收getSalary()方法
將其塑型為本來的類型后,又能接收getSalary()方法了
如果在塑型前和塑型后的類中都提供了相同的方法,如果將此方法發送給塑型后的對象,那么系統將會調用哪一個類中的方法?
根據實際情況有兩種方法查找方式:
實例方法的查找:
從對象創建時的類開始,沿類層次向上查找
Manager man = new Manager(); Employee emp1 = new Employee(); Employee emp2 = (Employee)man; emp1.computePay(); // 調用Employee類中的computePay()方法 man.computePay(); // 調用Manager類中的computePay()方法 emp2.computePay(); // 調用Manager類中的computePay()方法
類方法的查找
在引用變量聲明時所屬的類中進行查找
Manager man = new Manager(); Employee emp1 = new Employee(); Employee emp2 = (Employee)man; man.expenseAllowance(); //in Manager emp1.expenseAllowance(); //in Employee emp2.expenseAllowance(); //in Employee!!!多態
多態是指不同類型的對象可以響應相同的消息,執行同一個行為,會表現出不同的行為特征。
從相同的基類派生出來的多個類型可被當作同一種類型對待,可對這些不同的類型進行同樣的處理,由于多態性,這些不同派生類對象響應同一方法時的行為是有所差別的
例如
所有的Object類的對象都響應toString()方法 所有的BankAccount類的對象都響應deposit()方法多態的目的
所有的對象都可被塑型為相同的類型,響應相同的消息
使代碼變得簡單且容易理解
使程序具有很好的“擴展性”
多態的常見形式
多態的前提
一個例子:
繪圖——直接的方式
希望能夠畫出任意子類型對象的形狀,可以在Shape 類中聲明幾個繪圖方法,對不同的實際對象,采用不同的畫法
繪圖——更好的方式(多態)
在每個子類中都聲明同名的draw()方法
以后繪圖可如下進行
Circle屬于Shape的一種,系統會執行自動塑型
當調用方法draw時,實際調用的是Circle.draw()
在程序運行時才進行綁定,接下來介紹綁定的概念
綁定指將一個方法調用同一個方法主體連接到一起
根據綁定時期的不同,可分為
一個例子:
多態的應用
技術基礎
向上塑型技術:一個父類的引用變量可以指向不同的子類對象
動態綁定技術:運行時根據父類引用變量所指對象的實際類型執行相應的子類方法,從而實現多態性
構造方法與多態
構造方法與其他方法是有區別的
構造方法并不具有多態性,但仍然非常有必要理解構造方法如何在復雜的分級結構中隨同多態性一同使用的情況
構造方法的調用順序
在構造派生類的時候,必須能假定基類所有成員都是有效的。
在構造方法內部,必須保證使用的所有成員都已初始化。
因此唯一的辦法就是首先調用基類構造方法,然后在進入派生類構造方法之前,初始化所有能夠訪問的成員
構造方法中的多態方法
在構造方法內調用準備構造的那個對象的動態綁定方法(抽象方法-抽象類/接口)
會調用位于派生類里的一個方法
被調用方法要操縱的成員可能尚未得到正確的初始化
可能造成一些難于發現的程序錯誤
內部類
內部類
在另一個類或方法的定義中定義的類
可訪問其外部類中的所有數據成員和方法成員
可對邏輯上相互聯系的類進行分組
對于同一個包中的其他類來說,能夠隱藏
可非常方便地編寫事件驅動程序
聲明方式
命名的內部類:可在類的內部多次使用 匿名內部類:可在new關鍵字后聲明內部類,立即創建一個對象假設外層類名為Myclass,則該類的內部類名為
Myclass$c1.class (c1為命名的內部類名) Myclass$1.class (表示類中聲明的第一個匿名內部類)內部類實現接口
可以完全不被看到,而且不能被調用
可以方便實現“隱藏實現細則”。你所能得到的僅僅是指向基類(base class)或者接口的一個引用
方法中的內部類
在方法內定義一個內部類
為實現某個接口,產生并返回一個引用
為解決一個復雜問題,需要建立一個類,而又不想它為外界所用
六、I/O與文件
輸入輸出流(java.io)
輸入流
為了從信息源獲取信息,程序打開一個輸入流,程序可從輸入流讀取信息
輸出流
當程序需要向目標位置寫信息時,便需要打開一個輸出流,程序通過輸出流向這個目標位置寫信息
輸入/輸出流可以從以下幾個方面進行分類
從流的方向劃分
從流的分工劃分
節點流 處理流從流的內容劃分
面向字符的流 面向字節的流面向字符的流:專門用于字符數據
面向字節的流:用于一般目的
面向字符的流
面向字符的流
針對字符數據的特點進行過優化,提供一些面向字符的有用特性
源或目標通常是文本文件
面向字符的抽象類——Reader和Writer
java.io包中所有字符流的抽象基類 Reader提供了輸入字符的API Writer提供了輸出字符的API它們的子類又可分為兩大類
節點流:從數據源讀入數據或往目的地寫出數據 處理流:對數據執行某種處理多數程序使用這兩個抽象類的一系列子類來讀入/寫出文本信息
例如FileReader/FileWriter用來讀/寫文本文件
面向字節的流
InputStream和OutputStream
是用來處理8位字節流的抽象基類,程序使用這兩個類的子類來讀寫8位的字節信息
分為兩部分
標準輸入輸出流
標準輸入輸出流對象
System類的靜態成員變量
包括
System.in: InputStream類型的,代表標準輸入流,這個流是已經打開了的,默認狀態對應于鍵盤輸入。
System.out:PrintStream類型的,代表標準輸出流,默認狀態對應于屏幕輸出
System.err:PrintStream類型的,代表標準錯誤信息輸出流,默認狀態對應于屏幕輸出
System.in
程序啟動時由Java系統自動創建的流對象,它是原始的字節流,不能直接從中讀取字符,需要對其進行進一步的處理
InputStreamReader(System.in)
以System.in為參數創建一個InputStreamReader流對象,相當于字節流和字符流之間的一座橋梁,讀取字節并將其轉換為字符
BufferedReader in
對InputStreamReader處理后的信息進行緩沖,以提高效率
IO異常
多數IO方法在遇到錯誤時會拋出異常,因此調用這些方法時必須
在方法頭聲明拋出throws IOException異常
或者在try塊中執行IO,然后捕獲catch IOException
文件
在給出 File 對象的情況下構造一個 FileWriter 對象。
FileWriter(File file, boolean append)在給出文件名的情況下構造 FileWriter 對象,它具有指示是否追加寫入數據的 boolean 值。
FileWriter(String fileName, boolean append)方法:
public void write(int c) throws IOException寫入單個字符c
public void write(char [] c, int offset, int len)寫入字符數組中開始為offset、長度為len的某一部分
public void write(String s, int offset, int len)寫入字符串中開始為offset、長度為len的某一部分
示例:
實驗6
FileReader類
從文本文件中讀取字符
繼承自Reader抽象類的子類InputStreamReader
BufferedReader類
讀文本文件的緩沖器類
具有readLine()方法,可以對換行符進行鑒別,一行一行地讀取輸入流中的內容
繼承自Reader
二進制文件
示例:
實驗7
FILE類
表示磁盤文件信息
定義了一些與平臺無關的方法來操縱文件
構造文件流可以使用File類的對象作為參數
類 java.io.File
提供文件與路徑的各種有用信息
并不打開文件,或處理文件內容
示例:
上下級文件夾之間使用分隔符分開:
在Windows中分隔符為\,在Unix/Linux中分隔符為/。
跨平臺的目錄分隔符
更專業的做法是使用File.separatorChar,這個值就會根據系統得到的相應的分割符。
注意:
如果是使用"“,則需要進行轉義,寫為”\“才可以,如果是兩個”“,則寫為”\\"。
本章其余內容略,掌握實驗內容即可。(我們期考這部分不是重點,所以這里就簡寫了不少,需要的請自行參考其他博客)
七、數組集合
對象數組
先了解兩種排序:
自然排序和客戶化排序
數組
在Java提供的存儲及隨機訪問對象序列的各種方法中,數組是效率最高的一種
優點
數組知道其元素的類型 編譯時的類型檢查 大小已知代價
數組對象的大小是固定的,在生存期內大小不可變對象數組
數組元素是類的對象 所有元素具有相同的類型 每個元素都是一個對象的引用
集合
把具有相同性質的一類東西,匯聚成一個整體
Java集合不能存放基本數據類型,而只能存放對象。
它們被組織在以Collection及Map接口為根的層次結構中,稱為集合框架
集合框架(Java Collections Framework)
為表示和操作集合而規定的一種統一的標準的體系結構
提供了一些現成的數據結構可供使用,程序員可以利用集合框架快速編寫代碼,并獲得優良性能
包含三大塊內容
集合框架接口
聲明了對各種集合類型執行的一般操作
包括Collection、Set、List、SortedSet、Map、SortedMap
Java集合包括三種類型:Set(集),List(列表),Map(映射)
所有Java集合類都位于java.util包中
Java集合框架—Collection接口
Collection接口
聲明時可以使用一個參數類型,即Collection<E> (泛型)
聲明了一組操作成批對象的抽象方法:查詢方法、修改方法
查詢方法
修改方法包括
boolean add(Object obj) – 向集合中增加對象 boolean addAll(Collection<?> c) – 將參數集合中的所有元素增加到接收者集合中 boolean remove(Object obj) –從集合中刪除對象 boolean removeAll(Collection c) -將參數集合中的所有元素從接收者集合中刪除 boolean retainAll(Collection c) – 在接收者集合中保留參數集合中的所有元素,其它元素都刪除 void clear() – 刪除集合中的所有元素Java集合框架—Set、SortedSet接口
Set接口
擴展了Collection
禁止重復的元素,是數學中“集合”的抽象
對equals和hashCode操作有更強的約定,如果兩個Set對象包含同樣的元素,二者便是相等的
實現它的兩個主要類是哈希集合(HashSet)及樹集合(TreeSet)
SortedSet接口
一種特殊的Set
其中的元素是升序排列的,還增加了與次序相關的操作
通常用于存放詞匯表這樣的內容
Set的一般用法
Set舉例:
HashSet類
HashSet類按照哈希算法來存取集合中的對象,具有很好的存取和查找性能。
當向集合中加入一個對象時,HashSet會調用對象的hashCode()方法來獲得哈希碼,然后根據哈希碼進一步計算出對象在集合中的位置。
Hash,一般翻譯做散列、雜湊,或音譯為哈希,是把任意長度的輸入通過散列算法變換成固定長度的輸出,該輸出就是散列值。
這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小于輸入的空間。
不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。
Hash算法也被稱為散列算法,Hash算法雖然被稱為算法,但實際上它更像是一種思想。Hash算法沒有一個固定的公式,只要符合散列思想的算法都可以被稱為是Hash算法。
可參考:
哈希
Iterator類
Iterable接口:實現該接口的集合對象支持迭代,可以配合foreach使用。
Iterator:迭代器,提供迭代機制的對象,具體如何迭代是Iterator接口規范的。包含三個方法:hasNext、next、remove。
如果集合中的元素沒有排序,Iterator遍歷集合中元素的順序是任意的,并不一定與集合中加入元素的順序是一致的。
Iterator功能比較簡單,并且只能單向移動:
使用方法iterator()要求容器返回一個Iterator。第一次調用Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法屬于java.lang.Iterable接口,被Collection繼承。
使用next()獲得序列中的下一個元素。
使用hasNext()檢查序列中是否還有元素。
使用remove()將迭代器新返回的元素刪除。
Iterator是Java迭代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素。
TreeSet類
TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序。
當向集合中加入一個對象時,會把它插入到有序的對象集合中。
TreeSet支持兩種排序方式:
默認情況下采用自然排序。
自然排序
對于表達式x.compareTo(y),
如果返回值為0,則表示x和y相等 如果返回值大于0,則表示x大于y 如果返回值小于0,則表示x小于yTreeSet調用對象的compareTo()方法比較集合中對象的大小,然后進行升序排列,這種方式稱為自然排序。
正常是按升序排列,在返回前面加個負號就可以降序排列
JDK類庫中實現了Comparable接口的一些類的排序方式。
使用自然排序時,只能向TreeSet集合中加入同一類型的對象,并且這些對象必須實現Comparable接口。
客戶化排序
Java.util.Comparator<Type>接口提供具體的排序方式,<Type>指定被比較的對象的類型,Comparator有個compare(Type x,Type y)方法,用于比較兩個對象的大小。
compare(x, y)
如果希望TreeSet按照Student對象的name屬性進行降序排列,可以先創建一個實現Comparator接口的類StudentComparator。
StudentComparator類就是一個自定義的比較器。
public native int hashCode()
默認返回對象的32位jvm內存地址。
僅當創建某個“類的散列表”時,該類的hashCode() 才有用(作用是:確定該類的每一個對象在散列表中的位置;)
其它情況下(例如,創建類的單個對象,或者創建類的對象數組等等),類的hashCode() 沒有作用。
散列表指的是:Java集合中本質是散列表的類,如HashMap,Hashtable,HashSet。
以HashSet為例,來深入說明hashCode()的作用。
hashCode() 和 equals() 的關系
Java集合框架—List接口
List接口
擴展了Collection接口
可包含重復元素
元素是有順序的,每個元素都有一個index值(從0開始)標明元素在列表中的位置
在聲明時可以帶有一個參數,即List<E>
四個主要實現類
List的排序
List只能對集合中的對象按索引順序排序,如果希望對List中的對象按照其他特定的方式排序,可以借助Comparator接口和Collections類。
Collections類是對Java集合類庫中的輔助類,它提供操縱集合的各種靜態方法。
sort(List list):對List中的對象進行自然排序。
sort(List list,Comparator comparator):對List中的對象進行客戶化排序, comparator參數指定排序方式。
Java集合框架—Map、SortedMap接口
Map接口
不是Collection接口的繼承
用于維護鍵/值對(key/value pairs)
描述從不重復的鍵到值的映射,是一個從關鍵字到值的映射對象
其中不能有重復的關鍵字key,每個關鍵字最多能夠映射到一個值
聲明時可以帶有兩個參數,即Map<K, V>,其中K表示關鍵字的類型,V表示值的類型
SortedMap接口
一種特殊的Map,其中的關鍵字是升序排列的
與SortedSet對等的Map,通常用于詞典和電話目錄等
在聲明時可以帶有兩個參數,即SortedMap<K, V>,其中K表示關鍵字的類型,V表示值的類型
Map(映射):集合中的每一個元素包含一對鍵對象和值對象,集合中沒有重復的鍵對象,值對象可以重復。
向Map集合中加入元素時,必須提供一對鍵對象和值對象。
Map的兩個主要實現類:HashMap和TreeMap。
Map最基本的用法,就是提供類似字典的能力。
在Map中檢索元素時,只要給出鍵對象,就會返回值對象。
HashMap按照哈希算法來存取鍵值對象。
為了保證HashMap能正常工作,和HashSet一樣,要求當兩個鍵對象通過equals()方法比較為true時,這兩個鍵對象的hashCode()方法的返回的哈希碼也一樣。
Map及Map.Entry詳解
TreeMap實現了SortedSet接口,能夠對鍵對象進行排序。支持自然排序和客戶化排序。
接口的實現
向量
向量(Vector, ArrayList)
實現了Collection接口的具體類 能夠存儲任意對象,但通常情況下,這些不同類型的對象都具有相同的父類或接口 不能存儲基本類型(`primitive`)的數據,除非將這些數據包裹在包裹類中 其容量能夠根據空間需要自動擴充 增加元素方法的效率較高,除非空間已滿,在這種情況下,在增加之前需要先擴充容量Vector方法是同步的,線程安全
ArrayList方法是非同步的,效率較高
Vector類可以實現動態的對象數組。幾乎與ArrayList相同。
由于Vector在各個方面都沒有突出的性能,所以現在已經不提倡使用。
集合的遍歷
遍歷的四種方式
增強for循環的遍歷:
哈希表
八、線程
多線程編程基礎
多道程序設計
在計算機內存中同時存放幾道相互獨立的程序,使它們在管理程序控制之下,相互穿插地運行。 兩個或兩個以上程序在計算機系統中同處于開始到結束之間的狀態。
多道程序技術運行的特征
多道 宏觀上并行 微觀上串行(單核CPU)程序的并發執行
一組在邏輯上互相獨立的程序或程序段在執行過程中其執行時間在客觀上互相重疊,即一個程序段的執行尚未結束,另一個程序段的執行已經開始的執行方式。
程序并發執行時具有如下特征:
間斷性 失去封閉性 不可再現性進程的定義
進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。是操作系統能夠進行資源分配的單位。
進程的特征
結構特征 動態性 并發性 獨立性 異步性線程的定義
線程(thread)是操作系統能夠進行CPU運算調度的最小單位。
它被包含在進程之中,是進程中的實際運作單位。
一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發多個線程,每條線程并行執行不同的任務:
進程:是并發執行的程序在執行過程中分配和管理資源的基本單位,是一個動態概念,競爭計算機系統資源的基本單位。
線程:是進程的一個執行單元,是操作系統能夠進行運算調度的最小單位。線程也被稱為輕量級進程。
一個程序至少一個進程,一個進程至少一個線程。
為什么會有線程?
每個進程都有自己的地址空間,即進程空間,在網絡或多用戶交換機下,一個服務器通常需要接收大量不確定數量用戶的并發請求,為每一個請求都創建一個進程顯然行不通(系統開銷大響應用戶請求效率低),因此操作系統中線程概念被引進。
進程線程的區別:
線程是處理器調度的基本單位,但是進程不是。
進程的3種基本狀態及其轉換
阻塞:
就是程序運行到某些函數或過程后等待某些事件發生而暫時停止CPU占用的情況;也就是說,是一種CPU閑等狀態。
什么情況下考慮使用線程?
使用線程的好處有以下幾點:
使用線程可以把占據長時間的程序中的任務放到后臺去處理。
用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度。
程序的運行速度可能加快。
在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種情況下可以釋放一些珍貴的資源如內存占用等等。
……
示例:
多線程最多的場景:
Web服務器
各種專用服務器(如游戲服務器)
后臺任務,例如:定時向大量(100w以上)的用戶發送郵件
異步處理,例如:發微博、記錄日志等
分布式計算、高性能計算
程序、進程、線程
程序(Program):是一段靜態的代碼,它是應用軟件執行的藍本。
進程(Process):是程序的一次執行過程,是系統運行程序的基本單位,系統分配管理資源的最小單位。在操作系統中能同時運行多個任務(程序);
線程(Thread) :是比進程更小的執行單位,相當于一個任務中的一條執行路徑,系統運算調度的最小單位。在同一個應用程序中有多個順序流同時執行。
多線程的目的是為了最大限度的利用CPU資源。
Java中實現多線程的方法有兩種:
繼承java.lang包中的Thread類
Thread類直接繼承了Object類,并實現了Runnable接口。位于java.lang包中
封裝了線程對象需要的屬性和方法
繼承Thread類——創建多線程的方法之一
從Thread類派生一個子類,并創建子類的對象
子類應該重寫Thread類的run方法,寫入需要在新線程中執行的語句段。
調用start方法來啟動新線程,自動進入run方法。
定義一個Thread的子類并重寫其run方法如:
class MyThread extends Thread {@Overridepublic void run() {...}}生成該類的對象:
MyThread myThread = new MyThread();啟動或運行線程,Java虛擬機會自動啟動線程,從而由Java虛擬機進一步統一調度線程,實現各個線程一起并發地運行。
public void start() myThread.start();舉例:
public class ThreadCreateDemo1 {public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); //該方法調用多次,出現IllegalThreadStateException} }class MyThread extends Thread {@Overridepublic void run() {super.run();System.out.println("hellow_world!");} }實現Runnable接口
Runnable接口
Thread類實現了Runnable接口
只有一個run()方法
更便于多個線程共享資源
Java不支持多繼承,如果已經繼承了某個基類,便需要實現Runnable接口來生成多線程
以實現runnable的對象為參數建立新的線程
start方法啟動線程就會運行run()方法
舉例:
public class ThreadCreateDemo2 {public static void main(String[] args) {Runnable runnable = new MyRunnable();Thread thread = new Thread(runnable);thread.start();} }class MyRunnable implements Runnable {public void run() {System.out.println("通過Runnable創建的線程!");} }繼承Thread和實現Runnable接口的區別:
a.實現Runnable接口避免多繼承局限 b.實現Runnable()可以更好的體現共享的概念線程間的數據共享
用同一個實現了Runnable接口的對象作為參數創建多個線程
多個線程共享同一對象中的相同的數據
獨立的同時運行的線程有時需要共享一些數據并且考慮到彼此的狀態和動作。這樣就存在了互相干擾數據的問題(共享數據錯誤)
并發編程
多線程的同步控制
有時線程之間彼此不獨立、需要同步
線程間的互斥
同時運行的幾個線程需要共享一個(些)數據 共享的數據,在某一時刻只允許一個線程對其進行操作“生產者/消費者” 問題
假設有一個線程負責往數據區寫數據,另一個線程從同一數據區中讀數據,兩個線程可以并行執行 如果數據區已滿,生產者要等消費者取走一些數據后才能再寫 當數據區空時,消費者要等生產者寫入一些數據后再取線程同步Synchronization
互斥:許多線程在同一個共享數據上操作而互不干擾,同一時刻只能有一個線程訪問該共享數據。因此有些方法或程序段在同一時刻只能被一個線程執行,稱之為監視區(臨界區)。
協作:多個線程可以有條件地同時操作共享數據。執行監視區代碼的線程在條件滿足的情況下可以允許其它線程進入監視區。
多線程的線程同步機制實際上是靠鎖的概念來控制的。
多線程的鎖同步機制
對象鎖。鎖住對象,不同實例的鎖互不影響。
類鎖。對象都共用同一把鎖,同步執行,一個線程執行結束、其他對象線程才能夠調用同步的部分。不同的類鎖互不影響。
對象鎖的兩種形式:(注意synchronized)
public class Test {// 對象鎖:形式1(修飾實例方法)public synchronized void Method1(){System.out.println("我是對象鎖也是方法鎖");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}// 對象鎖:形式2(修飾代碼塊)public void Method2(){synchronized (this){System.out.println("我是對象鎖");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}}}類鎖:
public class Test {// 類鎖:形式1public static synchronized void Method1(){System.out.println("我是類鎖一號");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}// 類鎖:形式2public void Method2(){synchronized (Test.class){System.out.println("我是類鎖二號");try{Thread.sleep(500);} catch (InterruptedException e){e.printStackTrace();}}} }Java 使用監視器機制
每個對象只有一個鎖旗標 ,利用多線程對鎖旗標的爭奪實現線程間的互斥
當線程A獲得了一個對象的鎖旗標后,線程B必須等待線程A完成規定的操作、并釋放出鎖旗標后,才能獲得該對象的鎖旗標,并執行線程B中的操作
將需要互斥的語句段放入synchronized(object){}語句中,且兩處的object是相同的
線程之間的通信
為了更有效地協調不同線程的工作,需要在線程間建立溝通渠道,通過線程間的“對話”來解決線程間的同步問題
java.lang.Object 類的一些方法為線程間的通訊提供了有效手段
wait() 如果當前狀態不適合本線程執行,正在執行同步代碼(synchronized)的某個線程A調用該方法(在對象x上),該線程暫停執行而進入對象x的等待池,并釋放已獲得的對象x的鎖旗標。線程A要一直等到其他線程在對象x上調用notify或notifyAll方法,才能夠再重新獲得對象x的鎖旗標后繼續執行(從wait語句后繼續執行)
notify() 隨機喚醒一個等待的線程,本線程繼續執行
線程被喚醒以后,還要等發出喚醒消息者釋放監視器,這期間關鍵數據仍可能被改變
被喚醒的線程開始執行時,一定要判斷當前狀態是否適合自己運行
notifyAll() 喚醒所有等待的線程,本線程繼續執行
wait、notify/notifyAll 詳解
在多線程中要測試某個條件的變化,使用if 還是while?
顯然,只有當前值滿足需要值的時候,線程才可以往下執行,所以,必須使用while 循環阻塞。注意,wait() 當被喚醒時候,只是讓while循環繼續往下走.如果此處用if的話,意味著if繼續往下走,會跳出if語句塊。但是,notifyAll 只是負責喚醒線程,并不保證條件,所以需要手動來保證程序的邏輯。
線程 死鎖
阻塞:暫停一個線程的執行以等待某個條件發生。
IO阻塞:
線程阻塞
synchronized(obj)等待obj解鎖; wait(),等待其他線程的notify()。臨界資源
多道程序系統中存在許多進程,它們共享各種資源,然而有很多資源一次只能供一個進程使用。一次僅允許一個進程使用的資源稱為臨界資源。許多物理設備都屬于臨界資源,如輸入機、打印機、磁帶機等。
各進程采取互斥的方式,實現共享的資源稱作臨界資源。
屬于臨界資源的硬件有打印機、磁帶機等,軟件有消息緩沖隊列、變量、數組、緩沖區等。
每個進程中訪問臨界資源的那段代碼稱為臨界區。顯然,若能保證諸進程互斥地進入自己的臨界區,便可實現諸進程對臨界資源的互斥訪問。
死鎖
多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞,因此程序不可能正常終止。
四個必要條件:
互斥使用,即當資源被一個線程使用(占有)時,別的線程不能使用 不可搶占,資源請求者不能強制從資源占有者手中奪取資源,資源只能由資源占有者主動釋放。 請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的占有。 循環等待,即存在一個等待隊列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源。這樣就形成了一個等待環路。一個例子:
避免死鎖
sychronized
信號量Semaphore
一個計數信號量。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然后再獲取該許可。每個release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,
Semaphore只對可用許可的號碼進行計數,并采取相應的行動。拿到信號量的線程可以進入代碼,否則就等待。通過acquire()和release()獲取和釋放訪問許可。
臨界資源:在某一時刻只能獨占使用(互斥)、在不同時刻又允許多人共享的資源。
例子:
公司的銀行賬戶,由公司授權多人存、取錢,但每次只允許一個人存取。
手機等產品,先由生產者生產,后才賣給消費者,一個產品不能同時處于生產和消費中。
線程的同步和互斥:處理臨界資源,涉及多線程之間相互協作(步驟協調)。
synchronized鎖定方法,猶如對臨界資源加“鎖”,令方法執行過程對臨界資源進行“同步”(協同好步驟)操作。
synchronized鎖定臨界資源,語法:
生產者與消費者模型
(1)有一個具有一定容量的存放產品的倉庫。
(2)生產者不斷生產產品,產品保存在倉庫中(產品入倉)。
(3)消費者不斷購買倉庫中的產品(產品出倉)。
(4)只有倉庫有空間,生產者才能生產,否則只能等待。
(5)只有倉庫存在產品,消費者才能購買(消費)。
在生產者與消費者模型中,涉及4個概念:產品、倉庫、生產者和消費者,關鍵是后3個:
倉庫,是臨界資源,倉庫的產品不能同時入倉和出倉。 生產者線程:工人。 消費者線程:購買產品的人。現實世界中,許多問題都可歸結為生產者與消費者模型,比如公司銀行賬戶的存取款操作,銀行賬戶相當于一個海量倉庫,生產(存款)能力沒有限制。
后臺線程
后臺線程
也叫守護線程,通常是為了輔助其它線程而運行的線程
它不妨礙程序終止
一個進程中只要還有一個前臺線程在運行,這個進程就不會結束;如果一個進程中的所有前臺線程都已經結束,那么無論是否還有未結束的后臺線程,這個進程都會結束
“垃圾回收”便是一個后臺線程
如果對某個線程對象在啟動(調用start方法)之前調用了setDaemon(true)方法,這個線程就變成了后臺線程
Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程)
用戶線程:運行在前臺的線程,也叫普通線程,只完成用戶自己想要完成的任務,不提供公共服務。
創建一個無限循環的后臺線程,驗證主線程結束后,程序結束
運行程序,則發現整個程序在主線程結束時就隨之中止運行了,如果注釋掉t.setDaemon(true)語句,則程序永遠不會結束
線程的存活周期
線程的生命周期
線程從產生到消亡的過程
一個線程在任何時刻都處于某種線程狀態(thread state)
控制線程的生命
結束線程的生命
用stop方法可以結束線程的生命
但如果一個線程正在操作共享數據段,操作過程沒有完成就用stop結束的話,將會導致數據的不完整,因此并不提倡使用此方法
通常,可通過控制run方法中循環條件的方式來結束一個線程
線程的優先級
線程調度
在單CPU的系統中,多個線程需要共享CPU,在任何時間點上實際只能有一個線程在運行
控制多個線程在同一個CPU上以某種順序運行稱為線程調度
Java虛擬機支持一種非常簡單的、確定的調度算法,叫做固定優先級算法。這個算法基于線程的優先級對其進行調度
線程的優先級
每個Java線程都有一個優先級,其范圍都在1和10之間。默認情況下,每個線程的優先級都設置為5
在線程A運行過程中創建的新的線程對象B,初始狀態具有和線程A相同的優先級
如果A是個后臺線程,則B也是個后臺線程
可在線程創建之后的任何時候,通過setPriority(int priority)方法改變其原來的優先級
基于線程優先級的線程調度
具有較高優先級的線程比優先級較低的線程優先執行
對具有相同優先級的線程,Java的處理是隨機的
底層操作系統支持的優先級可能要少于10個,這樣會造成一些混亂。因此,只能將優先級作為一種很粗略的工具使用。最后的控制可以通過明智地使用yield()函數來完成
我們只能基于效率的考慮來使用線程優先級,而不能依靠線程優先級來保證算法的正確性
假設某線程正在運行,則只有出現以下情況之一,才會使其暫停運行
總結
以上是生活随笔為你收集整理的面向对象程序设计(JAVA)复习笔记(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS7
- 下一篇: 键盘输入 kbhit()