清華大學公開課《Java語言程序設計進階》

點擊查看:原文
本文是清華大學許斌老師的公開課:Java語言程序設計進階 的課堂筆記,快速複習一下,時間有限,因此大量直接截圖。許斌老師聲明:沒有配套講義,建議參考書籍:周志明《深入理解java虛擬機》。(JUC) java.utile.concurrency 部分參考源碼和技術博客。

第一章 線程(上)

1.0 導學

1539008813620

1539008859039

2.0 通過Thread類創建線程

1539009969070

1539009984077

1539009143304

1539009203189

1539009430772

1539009574503

1539009665675

1539009918717

3.0 線程的休眠

1539010059187

1539010375863

1539010405934

1539010618493

1539011791263

1539011800469

1539011806430

:線程休眠的原因就是讓其他線程有執行的機會

1.4 Thread類詳解

1539012104103

1539012342296

:線程啓動(即調用start方法)並不意味着線程馬上運行,線程是否運行取決於線程調度器。

1539012415734

1.5 通過Runnable接口創建線程

1539012516154

1539012651662

1539012759620

1539012814520

1539012944363

1539012958930

1539013125468

:Runnable接口中我們所實現的run方法就是我們這個線程想要執行的代碼

1.6 線程內部的數據共享

同樣一個線程類,它可以實例化出很多線程。同樣一個線程,它們是可以共享它們的代碼和數據,那也就是說當我們實現了Runnable接口的這個類,它所實例出來的對象的話,它去構造出的線程,它們之間是可以共享它們的代碼和它們之間的一些數據的。

1539013882402

1539012958930

1539014130891

1539014260700

1539014850716

1539014878920

1539016663199

小結

1539016705556

第二章 線程(中)

2.0 導學

1539016795632

1539016830468

2.1 線程同步的思路

1539017054833

1539017181751

1539017224926

1539017381221

1539017485915

1539017568234

1539017702394

1539017744493

:那原因就是在於說這兩個線程的話,它們是同一優先級,只不過是說這個producer先這個排在前面,所以的話從調度上,往往會調度它這個producer先執行,那它一執行呢就把這個票都生產完了,然後再等待着賣票的程序把它去賣掉,這是一種有意思的這個現象

1539017987439

2.2 線程同步的實現方式—Synchronization

1539018289399

1539018392108

1539018487545

1539018879503

:把這兩行代碼變成一個(原子)操作,就是在執行過程中不可能被打散執行

1539019284302

:用synchronized後面大括號括起來其實是代碼,實際上它把它變成一個原子操作,也就是說當我拿到這個對象t的鎖的時候,我這裏面的這些代碼是肯定都會被執行的,不會說我執行某一句以後就被這個打斷,然後那個插入別的線程去執行去訪問這個對象t,所以這個是synchronized它的很重要的作用。

1539019370141

1539019483438

1539019531529

1539019552318

1539019688049

就像剛纔我們那個例子:我們在這個售票線程裏面,每售出來票的時候,它就會休眠一毫秒,但休眠一毫秒的時候,它不會釋放出它所佔有的這個ticket對象的鎖的,它一直會持有,所以這是一個獨特的一個地方。

1539023144629

2.3 線程的等待與喚醒

1539071230986

1539071238290

:那現在wait notify notifyAll方法這三個方法都屬於object這個類的方法,也就意味着我們java當中所有的類它都有這個三個方法

1539071537462

:修改之後,相當於票箱大小爲1,Tickets.size = 1。Tickets.put() 方法中的 notify() 與 Tickets.sell() 方法中的wait()一一對應,Tickets.put() 方法中的 wait() 與 Tickets.sell() 方法中的notify()一一對應。

1539071636937

2.4 後臺進程

1539072724499

1539072801199

2.5 線程的生命週期與死鎖

1539076855969

1539076979258

:線程進入就緒狀態就是runnable state,即可運行狀態,但是並未開始運行,所以不是運行狀態(running state),是否運行取決於線程調度器是否調度它。(Runnable state isn’t running state)。

1539077334242

1539077435366

1539077461022

1539077569978

1539077751809

1539077820164

1539077847191

1539077924190

1539077981985

2.6 線程的調度

1539073197605

1539073339084

1539073553254

:可以通過這個使用這個yield的方法來去稍微改變一下它的這個執行過程,yield方法主要作用是把自己當前運行的線程暫停下來,把線程讓給同優先級的線程執行,當如果這時候不存在同優先級的線程,那還是繼續執行當前運行的線程。

1539073606722

1539075549275

1539075786770

1539075932520

1539075996274

1539076123946

1539076577555

:有交錯執行的這個過程,重要的原因:線程調用sleep方法,sleep方法是說我自己進入休眠,線程調度器有可能調度低優先級的這個線程,也就是說對高優先級的這個線程,如果要讓出自己的執行權限的話,就要調用sleep方法,然後給其它低優先級線程機會。如果高優先級的線程,僅僅只是調用了yield方法,它並不能給我們低優先級線程以執行的機會,它只給了它同優先級的線程以執行的這個機會。

小結

1539076703328

第三章 線程(下)線程安全與鎖優化

3.0 導學

1539078195419

:它是想描述線程的安全,而最重要的是描述你的程序,甚至你是某個類它的線程安全的特性。

3.1 線程安全與線程兼容與對立

1539092094328

1539092137232

1539092210369

1539093903103

1539093969693

向大家展示一下,一些java API中類,它在碰到這個線程操作的時候,有可能產生線程出錯的這個情況。

1539094981555

1539095091501

運行過程當中它不經常出錯,但是偶爾也會出錯,出現了數組下標越界的錯誤。最重要的原因:剛纔有兩個線程Thread remove,Thread print這兩個線程都在同時訪問一個數據:vector,其中一個線程的操作:刪除我們相量中的元素,另外一個線程的操作讀取我們相量中的元素。大家看到這其實這兩個操作:是有點互逆的 互斥的,那在這個讀寫向量的過程當中就可能產生錯誤,那從我們發現了這個運行的結果當中也發現了這一點。

1539095449832

1539095474977

1539095546295

3.2 線程的安全實現-互斥同步

1539099909957

1539100009472

1539100178410

1539100716303

1539100471798

3.3 線程的安全實現-非阻塞同步

1539101286254

1539101341800

1539101432019

1539101645384

那其實大家也可以這麼理解,也就是說我們對於這種線程安全,就是對我們這個訪問對象的線程安全的這種控制不是放到我們當前count這個類,它的increment方法來去實現的而是已經放到底下的叫 Atomiclnteger 這個類來實現了,所以你就可以直接去調用它的這個方法來去實現加1的這個功能,那整體上我們這個新的類,class Counter就是這個類通過用 Atomiclnteger 來改進這類,它也是線程安全的,整體上也是線程安全的,只不過說當你寫這個類的時候,你不需要考慮自己去加上synchronized這樣的同步互斥的這種實現方式,而是通過直接使用了Atomiclnteger這樣一個本身就是線程安全的這個類,就能夠保證你的整個這個代碼達到線程安全的目的。

3.4 線程的安全實現-無同步方案

1539102939632

:Threadlocal是我們java當中的一個類,它是存在於java.lang這個默認這個包當中。

1539103649773

1539103656234

這個 SequenceNumber 的實例,通過用 ThreadLocal 的這個方式也能夠保證這三個線程來訪問同一數據的時候,沒有產生錯誤。這也是爲什麼說,可以通過這個 ThreadLocal 就來去達到這個同步,就是說安全的這個目的。也不一定非得加個synchronize,因爲如果一旦加了synchronize的話,性能可能會受到影響,如果能通過類似 ThreadLocal 這樣這種線程的本地存儲的方式來達到我們這個對於數據訪問安全的控制化,那就能提高這個程序代碼的性能。

3.5 鎖優化

1539104257575

1539104378267

1539104424027

1539104476304

操作系統的堆棧與數據結構中堆棧概念參考:

  1. 什麼是堆?什麼是棧?他們之間有什麼區別和聯繫? - 知乎
    https://www.zhihu.com/question/19729973
  2. https://jingyan.baidu.com/article/6c67b1d6a09f9a2786bb1e4a.html

1539105602554

:由於細鎖太多,然後不斷切換線程的開銷反而降低了性能。

1539105752008

小結

1539105852330

  • 線程安全指的是我們的訪問對象無論被多少個線程進行訪問都能夠保證我們這對象訪問的正確性,那與之相關的這個概念是線程兼容。
  • 線程兼容是指我們的對象本身不是線程安全的,但是通過我們外部的同步控制能夠達到線程安全的目的.
  • 線程對立指的是我們的訪問對象,它本身不是線程安全,那我們外部即使加上了同步的控制也不能保證這個對象的這個正確性。
  • 我們還學習實現線程安全的幾種方式
    • 首先是互斥同步
    • 其次是非阻塞同步
    • 無同步方案
  • 最後我們還學習了鎖優化,那鎖優化的目標就是在我們不得不給我們的代碼加鎖的情況下如何去提高鎖的效率,進而達到提升整個代碼的效率的目標。

第四章 網絡編程(上)

4.0 導學

1539244505401

1539244534446

1539244801576

4.1 URL對象

1539245451379

注:保留端口號是計算機系統進行網絡交互需要的端口號,自己編寫程序不要去佔用這些保留端口號,具體保留端口號對應網絡服務google一下。

1539245767019

1539245834109

1539245898509

1539245956762

1539245984052

1539246010514

1539246052077

4.2 URLConnection對象

1539246211340

1539246251876

1539246318190

4.3 Get請求與Post請求

1539246522880

1539246527495

1539246570432

1539246720288

1539246836812

1539246936840

4.4 Socket通信原理

1539248246737

1539248277357

1539248323241

1539248356777

1539248563289

4.5 Socket通信實現

1539249189011

1539249549584

1539249542348

accept這個方法屬於ServerSocket方法,這種方法我們稱之它爲阻塞方法,就是說它是在那裏運行一直等着有客戶端來給它發送Socket連接請求,如果沒有客戶端給我們的服務端發送這個Socket連接請求accept就一直在那裏循環執行一直不返回,一直等到有客戶端的Socket發連接請求過來。那我們這服務端的 ServerSocket 這個對象的話它就會accept方法就會返回一個值,返回的是一個Socket對象,而這個Socket對象就是和我們客戶端的Socket對象進行對應的。

1539249811812

1539249848946

1539250143290

1539250150533

1539250156213

1539252104203

1539252110980

1539252121234

1539252126376

1539252147933

:那需要提醒大家注意是我們這個程序非常簡單,簡單到什麼程度呢?就是說聊天的時候,都是你說一句 我說一句如果一個想連續說兩句話的話可能現有這個機制還處理不過來,必須是一人一句,當然我們同學可以把這個程序再進一步改進使它更加的豐富。

小結

1539252275033

第五章 網絡編程(下)

5.0 導學

1539254072310

5.1 Socket 多客戶端通信實現

1539254198089

1539254477911

1539254535776

1539254560475

1539254578716

1539254484574

:先運行server線程,再運行client。

5.2 數據報通信

1539267932034

1539267969444

1539268051255

1539268477916

1539268866026

1539268872283

1539268971931

one-liners.txt這個是構造了一個文件輸入流,因爲我們做了一個非常簡單的模擬,也就是說把一些股票信息就寫到這個文件裏面了,寫到這個文件裏面了以後就是每次有客戶端發過來請求,說諮詢一下股票的價格的時候,我們就從這個文件裏讀出某一股票的價格在返還給我們的客戶端。

1539270238283

1539270248439

1539270313592

1539270333292

1539270367641

那剛纔這個程序當中客戶端服務端各一個程序,客戶端和服務端之間的通訊是通過數據報這個Socket來進行通訊的然後整個過程就非常類似於,我們人類進行平信這個通訊方式,也就是說客戶端通過構造一個DatagramPacket這個對象向它寫一封信,然後通過DatagramSocket的send方法把它發出去了,服務端收到了這封來信以後,通過這個來信知道了客戶端的地址和端口號,然後服務端它自己也寫一封信,說白了寫信就是構造一個DatagramPacket對象,寫好了以後,通過DatagramSocket的這個對象的send方法,把這個信再發出去又發還給客戶端,所以這個數據報包總結起來就非常類似於我們人類寫平信的這個過程。

5.3 使用數據報進行廣播通信

1539271072535

1539271612788

1539271626548

1539271671371

1539271676934

1539271843572

1539271849995

5.4 網絡聊天程序

1539279041381

那整個這個佈局其實大家採用一個BorderLayout就可以達到你的目標,那就是在BorderLayout的center中間那區域先放一個滾動面板,然後接着再放一個TextArea,然後在它的南部區域,我們先放一個Panel,緊接着再放一個TextView文本輸入區域,然後接着再放一個Button而且TextView和Button的話都是按照FlowLayout這種放置規則。

大家需要這個寫的事件響應是什麼呢?其實首先最重要的是說我們能夠接收這個在文本區域,就是最下面這個文本區域這個輸入的文本,我們可以給TextView這個組件來註冊一個監聽器,當我們這個一回車就在TextView裏面一輸入字符一回車的時候,它產生的是一個ActionEvent,所以我們可以給TextView註冊一個EventListener。那TextView旁邊的話,是一個按鈕發送,其實發送的話它所對應的這個事件處理也是ActionEvent,所以在這個例子當中我們只需要寫一個事件處理類然後都分別這個授權來去處理TextView和我們按鈕的這麼這個事件處理就可以完成獲得我們這個文本的這麼一個過程以及把它發送的一個過程。那在這裏面怎麼去獲得內容呢?TextView裏面有個一個getText的方法,那我們只要在我們的ActionPerform方法裏面去通過TextView的getText來獲得它的內容然後來決定一個是往外發送同時把它顯示到當前我們的這個界面上面。

1539280107792

1539280120139

1539280125626

1539280132480

1539280143416

1539280148860

1539280153459

1539280162757

1539280172673

1539280181412

1539280189503

1539280194806

小結

1539279964634

第六章 java虛擬機

6.0 導學

1539106278412

6.1 Java虛擬機概念

1539152742070

1539152824180

1539152930663

1539152984208

1539153141326

6.2 Java虛擬機內存劃分

1539158818149

1539160258794

1539160296855

本地方法(native method)它不一定是拿Java語言來編寫的方法,Java虛擬機是會運行在不同的操作系統和硬件上面,那在本身Java虛擬機的內部實現的時候,也會有一部分代碼是運行的是本地代碼非Java這個代碼,包括你們自己寫程序的時候,也可以比如用C或C加加,寫一段程序,最後把它嵌入到Java代碼當中這也是可以的。這個本地方法棧主要是用來執行本地方法,它同樣有可能會拋出異常,那所謂的拋出異常的種類也和虛擬機棧一樣,而我們的虛擬機棧它的對應的功能主要是用來執行我們的Java方法。

1539160957650

:官網G1垃圾回收器介紹Getting Started with the G1 Garbage Collector

1539161076357

6.3 Java虛擬機類加載機制

1539161252058

比較舊的一些這個編程語言的經驗,當我們編譯完了以後可能還要做鏈接然後再執行,Java它有一個這個大的特點就是在程序運行過程當中來進行這個類的加載和連接,這樣的話就保證了,它這個一個程序運行的這個流暢和靈活性。

1539161369363

1539161595199

1539161500327

1539161553038

1539161763782

1539161758041

1539161925586

1539161967122

6.4 判斷對象是否存活算法及對象引用

1539162253019

1539163215077

1539162910083

1539162974202

1539163015403

1539163248799

通過sf.get方法可以獲取到這個對象,當然當這個對象被標誌爲需要回收的對象時,它就會返回的是空,所以說軟引用主要用戶來實現類似緩存的功能,在內存足夠的情況下,我們可以直接通過軟引用來取值而不需要從繁忙的真實來源去查詢數據提升速度,那當內存不足的時候就會自動刪除這部分的緩存數據,然後從真正的數據來源當中去查詢這些數據。

1539163344947

1539163385453

6.5 分代垃圾回收

1539163621558

  1. 將引用對象設置爲空,這種方式來去釋放內存的話,應該沒什麼大問題,但如果我們用system gt方法去釋放內存的話會大大的影響我們的系統性能。
  2. 不可達含義:不用了,沒有引用鏈指向。看英文:unavailable 就明白了,沒有引用指向它們而且畢竟不屬於空閒區,當然就不可使用。

1539163888139

1539164010609

6.6 典型的垃圾收集算法

1539164393543

1539164434403

1539164514978

1539164585155

1539164642112

1539164730132

1539164819538

1539165861048

6.7典型的垃圾收集器

1539166829998

1539166851653

1539166911602

1539166985009

1539167023486

:jvm垃圾回收詳解參考官網G1垃圾回收器介紹Getting Started with the G1 Garbage Collector

今天介紹這幾種垃圾收集器一般說來它會影響到程序執行性能,尤其是當想編一些對效率要求非常高的Java程序的時候,比如說服務器端的Java程序的時候,有時候你會比較顧及Java虛擬機的垃圾回收效率是不是足夠那個幫助你的程序的運行,那據我所知國內外都有一些大公司在他們的服務器端性質當中重新改寫了一些關於Java虛擬機的裏面的垃圾回收的機制。

就舉一個例子來說:雙十一淘寶它的這個系統肯定就會承受着極大的這個用戶購買商品的點擊的壓力,淘寶實際上它的很多後臺系統拿是拿Java寫的,所以爲了提高這個Java在服務器端的這種工作效率,那我聽說他們也是對於一些垃圾回收的這些機制進行了改進。在一些性能要求特別高的情況下的話,可能我們在服務器端會對這些Java虛擬機以及相應它的一些局部做一些改進

第七章 深入集合collection

7.0 導學

1539167756540

7.1 集合框架與ArrayList

1539167879951

1539172675709

1539172739041

1539172833305

1539173020388

1539173539375

1539173752342

7.2 LinkedList

1539174547853

1539174562202

1539174689397

1539174707566

1539175102241

1539175158234

1539175293680

1539175387312

1539175494926

7.3 HashMap與HashTable

1539175639852

1539175684847

1539176066589

1539176310007

1539176528691

1539176623549

可以看到這個HashMap底層數組的長度它總是2的N次方,這就是爲了保證數組的使用率最高,儘可能的減少這個碰撞現象的產生,當HashMap中的元素越來越多的時候,這個哈希衝撞的衝突的可能性也就越來越高,因爲數組的長度是固定的。那爲了提高這查詢的效率就要對這個HashMap的數組進行擴容容量變爲原來的兩倍,這時候,原數組當中的數據必須重新計算它在新數組當中的位置,並且放進去,那這個過程呢就非常耗時。當HashMap中的元素個數超過數組大小(取個名字叫lot fat),就會進行數組的擴容,這個(lot fat)的默認值爲0.75。那這個是一個非常消耗性能的操作所以如果我們已經預知這個HashMap當中元素的個數,那麼就能夠有效的提高HashMap的性能,所以這也是一個HashMap它的一個獨特的地方。

1539177101407

1539177164867

:關於hashtable與hashmap 建議參考源碼解析 Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

7.4 TreeMap與LinkedHashMap

1539178600822

1539178636271

1539178843148

1539178868002

1539178948012

1539179258434

所以說它和之前的那個HashMap的區別就是:HashMap裏面的數據結構第一級結構是一個數組,第二級結構是一個單向鏈表,而我們的LinkedHashMap第二級結構是一個雙向鏈表,所以在往裏添加的時候就可以根據你要添加元素的位置來決定是從正向的去檢索往裏添加,還是反向從隊尾開始去檢索進行添加。

1539179432947

1539179495885

7.5 HashSet

1539179625696

1539179667322

1539179718352

小結

1539179788542

  1. 常用的一些集合類

    首先介紹了List Map Set 這幾個接口,那在這幾個接口之下又有好多的類是實現了這些接口,比如說我們的HashSet、ArrayList linkedList、HashMap、TreeMap等等這些。

  2. 集合類的內部的實現過程

    爲什麼要介紹這些呢?是因爲要告訴大家說,它的每一個數據結構這些類它到底是怎麼實現的,你明白了它的實現原理了以後,你就知道說它的效率和性能到底是怎樣的,爲什麼高爲什麼低,哪些類到底和線程安全有沒有關係,有沒有已經實現的線程安全特性,那沒有實現的話,你就得自己通過加 synchronize 辦法來去實現,所以對於通過了解我們集合類的內部,你就可以很好的去運用這些集合類

  3. 各個集合類的適用範圍

    由於這些集合類它本身所具有的特點並不一樣,所以當我們在編程序過程中,考慮選擇哪個類作爲我們的數據結構的時候,你就能夠很好的去選擇和決定。

第八章 反射與代理機制

8.0 導學

1539181467775

8.1 Java反射機制

1539186923792

1539186930949

1539186938506

1539186946955

1539186953329

1539187203705

1539187503835

8.2 Java靜態代理

1539190674840

1539191075024

1539191329444

1539191522836

8.3 Java動態代理

1539191759255

1539225038613

1539225666987

1539226052214

這個例子實際上是告訴大家說你可以給一個真實的對象,你給它生成一個動態代理,那生成這個動態代理的話。既然是個代理,你可以在這個真實對象的方法執行之前先做一些預處理,執行之後你還可以做一些後處理,所以你就可以增加一些你想幹的這個事情,而通過這個動態代理的話,它的好處就是能夠讓你更加方便的去實現這些代理的過程。

1539228376485

1539228382891

8.4 Java 反射擴展-jvm加載類原理

1539232525052

JAVA虛擬機中類加載的原理,什麼叫類加載,想想當編輯完JAVA的源程序以後,一編譯會得到是一大堆點class文件,那點class文件就是這些類文件,而這些類文件平常是存在硬盤上,也就存在電腦的文件系統當中那當這些類文件需要執行的時候就需要JAVA虛擬機把它從硬盤上給挪到我們內存當中,那整個挪到JAVA虛擬機內存當中過程實際上就是一個類加載的過程。

1539233621727

1539233745371

1539242915189

  1. 第二步要做連接,連接裏面也會包括第一部分是驗證、注意驗證主要驗證說,你裝載進來的這個點class文件它是不是符合我們JAVA虛擬機對於自解碼文件的一個規範,做格式的這種校驗,甚至是不是有惡意是不是有危害,這些都是我們驗證的過程,那第二個小步驟是準備要把我們這些類它的一些相應的靜態的成員做一下內存的分配,那第三小步驟的話是要解析,解析是什麼就是把我們很多的這些符號性的引用把它轉化成一種直接的引用。
  2. 第三個步驟是做類的初始化,比如說我們將類的靜態變量給它做賦於正確的初始值,注意這個初始值是指的是程序員在給它定義的這個初始值而不是說默認初始值,默認初始值這個確定是在第二個步驟連接步驟裏邊,這個準備小步驟裏邊已經實現了。

1539242983420

1539243003067

小結

1539243156735

我們今天主要講了三個方面的內容

  1. JAVA的反射機制

    JAVA反射機制爲程序員提供了一種直接去獲取類以及對象它的方法以及它的成員變量的一種方式,通過反射機制可以去通過一個字符串的名字去創建一個類的對象,並且很靈活的去調動它的所有的方法。

  2. JAVA的代理機制

    介紹了靜態代理和動態代理,之所以有JAVA代理機制是因爲說有些情況下並不想或者不能夠去直接訪問目標對象而需要中間有一箇中介的渠道,那這中介渠道就可以幫助很好的去控制和訪問目標對象。並且在訪問目標的前和後都可以增加一些預處理或者後處理。介紹了靜態代理的方式和動態代理的方式,動態代理方式會給大家很大的一個方便和靈活性

  3. 類的加載機制

    把我們所有編譯好的點Class文件把它加載到我們的JAVA虛擬機當中來進行運行,那這個類加載過程當中,它實際上對於JAVA是一個動態的過程而且是一個可以從多個源頭進行加載的這個過程,理解的加載類的加載過程,對於大家今後編寫更加高效有效的JAVA程序會帶來很大的幫助。

參考:

  1. 周志明《深入理解java虛擬機》
  2. Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
  3. Java Concurrency in Practice
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章