pdf下載地址:Java面試寶典javascript
第一章內容介紹 20css
第二章JavaSE基礎 21html
1. 面向對象都有哪些特性以及你對這些特性的理解 21vue
2. 訪問權限修飾符public、private、protected, 以及不寫(默認)時的區別(2017-11-12) 22java
2、JavaSE語法(2017-11-12-wl) 26mysql
1. Java有沒有goto語句?(2017-11-12-wl) 26jquery
2. & 和 && 的區別(2017-11-12-wl) 27linux
3. 在Java中,如何跳出當前的多重嵌套循環(2017-11-14-wl) 27
4. 兩個對象值相同(x.equals(y) == true),但卻可有不一樣的hashCode,這句話對不對?(2017-11-14-wl) 27
5. 是否能夠繼承String (2017-11-14-wl) 28
6. 當一個對象被看成參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏究竟是值傳遞仍是引用傳遞?(2017-11-14-wl) 29
7. 重載(overload)和重寫(override)的區別?重載的方法可否根據返回類型進行區分?(2017-11-15-wl) 29
8. 爲何函數不能根據返回類型來區分重載?(2017-11-15-wl) 30
9. char 型變量中能不能存儲一箇中文漢字,爲何?(2017-11-16-wl) 31
10. 抽象類(abstract class)和接口(interface)有什麼異同?(2017-11-16-wl) 31
11. 抽象的(abstract)方法是否可同時是靜態的(static), 是否可同時是本地方法(native),是否可同時被synchronized(2017-11-16-wl) 32
12. 闡述靜態變量和實例變量的區別?(2017-11-16-wl) 32
13. ==和equals的區別?(2017-11-22-wzz) 33
14. break和continue的區別?(2017-11-23-wzz) 33
15. String s = "Hello";s = s + " world!";這兩行代碼執行後,原始的String對象中的內容到底變了沒有?(2017-12-1-lyq) 33
3. error和exception的區別?(2017-2-23) 36
5. 請寫出你最多見的5個RuntimeException(2017-11-22-wzz) 37
6. throw和throws的區別(2017-11-22-wzz) 38
7. final、finally、finalize的區別?(2017-11-23-wzz) 38
1. Math.round(11.5)等於多少?Math.round(- 11.5) 又等於多少?(2017-11-14-wl) 39
2. switch是否能做用在byte上,是否能做用在long上,是否能做用在String上?(2017-11-14-wl) 39
3. 數組有沒有length() 方法?String有沒有length() 方法?(2017-11-14-wl) 39
4. String 、StringBuilder 、StringBuffer的區別?(2017-11-14-wl) 39
5. 什麼狀況下用「+」運算符進行字符串鏈接比調用StringBuffer/StringBuilder 對象的append方法鏈接字符串性能更好?(2017-11-14-wl) 40
6. 請說出下面程序的輸出(2017-11-14-wl) 47
7. Java中的日期和時間(2017-11-19-wl) 48
2. String是基本數據類型嗎?(2017-11-12-wl) 71
3. short s1 = 1; s1 = s1 + 1; 有錯嗎?short s1 = 1; s1 += 1有錯嗎;(2017-11-12-wl) 71
4. int 和 和 Integer有什麼區別?(2017-11-12-wl) 71
5. 下面Integer類型的數值比較輸出的結果爲?(2017-11-12-wl) 72
6. String類經常使用方法(2017-11-15-lyq) 74
7. String、StringBuffer、StringBuilder的區別?(2017-11-23-wzz) 74
8. 數據類型之間的轉換(2017-11-23-wzz) 75
1. Java中有幾種類型的流(2017-11-23-wzz) 75
4. 字節流和字符流的區別(2017-11-23-wzz) 77
5. 如何實現對象克隆?(2017-11-12-wl) 77
6. 什麼是java序列化,如何實現java序列化?(2017-12-7-lyq) 80
1. HashMap排序題,上機題。(本人主要靠這道題入職的第一家公司) 81
3. ArrayList內部用什麼實現的?(2015-11-24) 83
4. 併發集合和普通集合如何區別?(2015-11-24) 89
6. List和Map、Set的區別(2017-11-22-wzz) 91
7. HashMap 和HashTable有什麼區別?(2017-2-23) 92
8. 數組和鏈表分別比較適合用於什麼場景,爲何?(2017-2-23) 93
9. Java中ArrayList和Linkedlist區別?(2017-2-23) 96
10. List a=new ArrayList()和ArrayList a =new ArrayList()的區別?(2017-2-24) 97
11. 要對集合更新操做時,ArrayList和LinkedList哪一個更適合?(2017-2-24) 97
12. 請用兩個隊列模擬堆棧結構(2017-2-24) 101
13. Collection和Map的集成體系(2017-11-14-lyq) 102
14. Map中的key和value能夠爲null麼?(2017-11-21-gxb) 103
(一)多線程基礎知識--傳統線程機制的回顧(2017-12-11-wl) 104
(二)多線程基礎知識--線程併發庫(2017-12-11-wl) 118
1. 靜態嵌套類(Static Nested Class) 和內部類(Inner Class)的不一樣?(2017-11-16-wl) 272
2. 下面的代碼哪些地方會產生編譯錯誤?(2017-11-16-wl) 272
1. 寫一個ArrayList的動態代理類(筆試題) 273
2. 動靜態代理的區別,什麼場景使用?(2015-11-25) 274
12. heap和stack有什麼區別(2017-2-23) 294
13. 解釋內存中的棧 (stack) 、堆 (heap) 和方法區 (method area) 的用法(2017-11-12-wl) 301
3. Java類加載體系之ClassLoader雙親委託機制 (2017-2-24) 302
4. 描述一下JVM加載class (2017-11-15-wl) 306
5. 得到一個類對象有哪些方式?(2017-11-23-wzz) 307
1. 既然有GC機制,爲何還會有內存泄露的狀況(2017-11-16-wl) 307
1. Java中爲何會有GC機制呢?(2017-11-16-wl) 309
2. 對於Java的GC哪些內存須要回收(2017-11-16-wl) 309
3. Java的GC何時回收垃圾(2017-11-16-wl) 309
7、Java8的新特性以及使用(2017-12-02-wl) 311
1. 經過10個示例來初步認識Java8中的lambda表達式(2017-12-02-wl) 311
2. Java8中的lambda表達式要點(2017-12-02-wl) 319
3. Java8中的Optional類的解析(2017-12-02-wl) 321
8、在開發中遇到過內存溢出麼?緣由有哪些?解決方法有哪些?(2017-11-23-gxb) 328
1. 說下原生jdbc操做數據庫流程?(2017-11-25-wzz) 329
2. 什麼要使用PreparedStatement?(2017-11-25-wzz) 330
3. 關係數據庫中鏈接池的機制是什麼?(2017-12-6-lyq) 330
1. http的長鏈接和短鏈接(2017-11-14-lyq) 331
2. HTTP/1.1與HTTP/1.0的區別(2017-11-21-wzy) 332
3. http常見的狀態碼有哪些?(2017-11-23-wzz) 335
4. GET和POST的區別?(2017-11-23-wzz) 335
5. http中重定向和請求轉發的區別?(2017-11-23-wzz) 337
1. Cookie和Session的區別(2017-11-15-lyq) 337
2. session共享怎麼作的(分佈式如何實現session共享)? 337
3. 在單點登陸中,若是cookie被禁用了怎麼辦?(2017-11-23-gxb) 340
1. 什麼是jsp,什麼是Servlet?jsp和Servlet有什麼區別?(2017-11-23-wzz) 341
2. jsp有哪些域對象和內置對象及他們的做用?(2017-11-25-wzz) 342
1. 什麼是xml,使用xml的優缺點,xml的解析器有哪幾種,分別有什麼區別?(2017-11-25-wzz) 343
1. 談談你對ajax的認識?(2017-11-23-wzz) 344
2. jsonp原理(2017-11-21-gxb) 345
2. Linux中如何查看日誌?(2017-11-21-gxb) 347
3. Linux怎麼關閉進程(2017-11-21-gxb) 348
1. jQueryUI(2017-11-23-lyq) 352
3. AngularJS (2017-11-23-lyq) 355
1. SQL的select語句完整的執行順序(2017-11-15-lyq) 359
2. SQL之聚合函數(2017-11-15-lyq) 361
3. SQL之鏈接查詢(左鏈接和右鏈接的區別)(2017-11-15-lyq) 361
4. SQL之sql注入(2017-11-15-lyq) 362
5. Mysql性能優化(2017-11-15-lyq) 362
6. 必看sql面試題(學生表_課程表_成績表_教師表)(2017-11-25-wzz) 363
7. Mysql數據庫架構圖(2017-11-25-wzz) 364
8. Mysql架構器中各個模塊都是什麼?(2017-11-25-wzz) 365
9. Mysql存儲引擎有哪些?(2017-11-25-wzz) 366
10. MySQL事務介紹(2017-11-25-wzz) 367
11. MySQL怎麼建立存儲過程(2017-11-25-wzz) 369
12. MySQL觸發器怎麼寫?(2017-11-25-wzz) 370
13. MySQL語句優化(2017-11-26-wzz) 371
14. MySQL中文亂碼問題完美解決方案(2017-12-07-lwl) 372
15. 如何提升MySQL的安全性(2017-12-8-lwl) 374
1. 什麼是存儲過程,使用存儲過程的好處?(2017-11-25-wzz) 376
2. Oracle存儲過程怎麼建立?(2017-11-25-wzz) 377
3. 如何使用Oracle的遊標?(2017-11-25-wzz) 378
4. Oracle中字符串用什麼鏈接?(2017-11-25-wzz) 378
5. Oracle中是如何進行分頁查詢的?(2017-11-25-wzz) 379
6. 存儲過程和存儲函數的特色和區別?(2017-11-25-wzz) 379
7. 存儲過程與SQL的對比?(2017-11-21-gxb) 379
8. 你以爲存儲過程和SQL語句該使用哪一個?(2017-11-21-gxb) 380
9. 觸發器的做用有哪些?(2017-11-21-gxb) 381
10. 在千萬級的數據庫查詢中,如何提升效率?(2017-11-23-gxb) 381
1. SpringMVC的工做原理(2017-11-13-lyq) 385
2. SpringMVC經常使用註解都有哪些?(2017-11-24-gxb) 386
3. 如何開啓註解處理器和適配器?(2017-11-24-gxb) 386
4. 如何解決get和post亂碼問題?(2017-11-24-gxb) 386
1. 談談你對Spring的理解(2017-11-13-lyq) 387
2. Spring中的設計模式(2017-11-13-lyq) 387
3. Spring的經常使用註解(2017-11-13-lyq) 388
4. 簡單介紹一下Spring bean的生命週期(2017-11-21-gxb) 389
5. Spring結構圖(2017-11-22-lyq) 390
6. Spring能幫咱們作什麼?(2017-11-22-lyq) 392
7. 請描述一下Spring的事務(2017-11-22-lyq) 393
8. BeanFactory 經常使用的實現類有哪些?(2017-12-03-gxb) 396
9. 解釋Spring JDBC、Spring DAO和Spring ORM(2017-12-03-gxb) 397
10. 簡單介紹一下Spring WEB 模塊。(2017-12-03-gxb) 397
11. Spring配置文件有什麼做用?(2017-12-03-gxb) 398
12. 什麼是Spring IOC 容器?(2017-12-03-gxb) 398
14. ApplicationContext的實現類有哪些?(2017-12-03-gxb) 398
15. BeanFactory與AppliacationContext有什麼區別(2017-12-03-gxb) 399
16. 什麼是Spring的依賴注入?(2017-12-04-gxb) 399
17. 有哪些不一樣類型的IOC(依賴注入)方式?(2017-12-04-gxb) 399
18. 什麼是Spring beans?(2017-12-04-gxb) 400
19. 一個Spring Beans的定義須要包含什麼?(2017-12-04-gxb) 400
20. 你怎樣定義類的做用域?(2017-12-04-gxb) 401
21. Spring支持的幾種bean的做用域。(2017-12-04-gxb) 401
22. Spring框架中的單例bean是線程安全的嗎?(2017-12-04-gxb) 401
23. 什麼是Spring的內部bean?(2017-12-04-gxb) 401
24. 在Spring中如何注入一個java集合?(2017-12-04-gxb) 402
25. 什麼是bean的自動裝配?(2017-12-04-gxb) 402
26. 解釋不一樣方式的自動裝配。(2017-12-04-gxb) 402
27. 什麼是基於Java的Spring註解配置? 給一些註解的例子(2017-12-05-gxb) 403
28. 什麼是基於註解的容器配置?(2017-12-05-gxb) 403
29. 怎樣開啓註解裝配?(2017-12-05-gxb) 403
30. 在Spring框架中如何更有效地使用JDBC?(2017-12-05-gxb) 403
31. 使用Spring經過什麼方式訪問Hibernate?(2017-12-05-gxb) 404
32. Spring支持的ORM框架有哪些?(2017-12-05-gxb) 404
33. 簡單解釋一下spring的AOP(2017-12-05-gxb) 404
34. 在Spring AOP 中,關注點和橫切關注的區別是什麼?(2017-12-05-gxb) 405
35. 什麼是鏈接點?(2017-12-05-gxb) 405
36. Spring的通知是什麼?有哪幾種類型?(2017-12-05-gxb) 405
37. 什麼是切點?(2017-12-05-gxb) 406
38. 什麼是目標對象?(2017-12-05-gxb) 406
39. 什麼是代理?(2017-12-05-gxb) 406
40. 什麼是織入?什麼是織入應用的不一樣點?(2017-12-05-gxb) 406
1. 簡單介紹一下Shiro框架(2017-11-23-gxb) 406
2. Shiro主要的四個組件(2017-12-2-wzz) 407
3. Shiro運行原理(2017-12-2-wzz) 408
4. Shiro的四種權限控制方式(2017-12-2-wzz) 408
1. Mybatis中#和$的區別?(2017-11-23-gxb) 410
2. Mybatis的編程步驟是什麼樣的?(2017-12-2-wzz) 411
3. JDBC編程有哪些不足之處,MyBatis是如何解決這些問題的?(2017-12-2-wzz) 411
4. 使用MyBatis的mapper接口調用時有哪些要求?(2017-12-2-wzz) 411
5. Mybatis中一級緩存與二級緩存?(2017-12-4-lyq) 412
6. MyBatis在insert插入操做時返回主鍵ID(2017-12-4-lyq) 412
1. 簡單介紹一下Struts2(2017-11-24-gxb) 413
2. Struts2的執行流程瞭解麼?(2017-11-24-gxb) 414
3. Struts2中Action配置的注意事項有哪些?(2017-11-24-gxb) 416
4. 攔截器和過濾器有哪些區別?(2017-11-24-gxb) 417
5. Struts2的封裝方式有哪些?(2017-11-24-gxb) 417
6. 簡單介紹一下Struts2的值棧。(2017-11-24-gxb) 419
7. SpringMVC和Struts2的區別?(2017-11-23-gxb) 420
8. Struts2中的 # 和 % 分別是作什麼的?(2017-11-30-wzz) 421
9. Struts2中有哪些經常使用結果類型?(2017-12-1-lyq) 422
1. 簡述一下hibernate的開發流程(2017-11-24-gxb) 422
2. hibernate中對象的三種狀態(2017-11-24-gxb) 423
3. hibernate的緩存機制。(2017-11-24-gxb) 423
4. Hibernate的查詢方式有哪些?(2017-11-24-gxb) 424
5. Hibernate和Mybatis的區別?(2017-11-23-gxb) 424
6. Hibernate和JDBC優缺點對比(2017-11-29-wzz) 425
7. 關於Hibernate的orm思想你瞭解多少?(2017-11-29-wzz) 426
8. get和load的區別?(2017-11-30-wzz) 426
9. 如何進行Hibernate的優化?(2017-11-30-wzz) 427
10. 什麼是Hibernate 延遲加載?(2017-12-1-lyq) 428
11. No Session問題原理及解決方法?(2017-12-4-lyq) 428
12. Spring的兩種代理JDK和CGLIB的區別淺談(2017-12-4-lyq) 429
13. 敘述Session的緩存的做用(2017-12-9-lwl) 430
14. Session的清理和清空有什麼區別?(2017-12-10-lwl) 430
15. 請簡述Session的特色有哪些?(2017-12-10-lwl) 430
16. 比較Hibernate三種檢索策略的優缺點(2017-12-10-lwl) 431
1. 什麼是Quartz 框架(2017-12-2-wzz) 432
2.配置文件 applicationContext_job.xml各個屬性做用(2017-12-2-wzz) 432
3.Cron表達式詳解(2017-12-2-wzz) 432
4. 如何監控 Quartz 的 job 執行狀態:運行中,暫停中,等待中? (2017-12-2-wzz) 433
1. Redis的特色?(2017-11-25-wzz) 433
2. 爲何redis須要把全部數據放到內存中?(2017-11-25-wzz) 434
3. Redis常見的性能問題都有哪些?如何解決?(2017-11-25-wzz) 434
4. Redis最適合的場景有哪些?(2017-11-25-wzz) 435
5. Memcache與Redis的區別都有哪些?(2017-11-25-wzz) 435
6. Redis用過RedisNX嗎?Redis有哪幾種數據結構?(2017-11-14-lyq) 435
7. Redis的優缺點(2017-11-22-lyq) 436
8. Redis的持久化(2017-11-23-lyq) 437
1. 如何使用ActiveMQ解決分佈式事務?(2017-11-21-gxb) 439
2. 瞭解哪些消息隊列?(2017-11-24-gxb) 440
3. ActiveMQ若是消息發送失敗怎麼辦?(2017-11-24-gxb) 442
1. Dubbo的容錯機制有哪些。(2017-11-23-gxb) 443
2. 使用dubbo遇到過哪些問題?(2017-11-23-gxb) 444
3. Dubbo的鏈接方式有哪些?(2017-12-1-lyq) 445
1. 如何測試併發量?(2017-11-23-gxb) 448
1. Nginx反向代理爲何可以提高服務器性能?(2017-11-24-gxb) 448
2. Nginx 和 Apache 各有什麼優缺點? (2017-11-24-gxb) 449
3. Nginx 多進程模型是如何實現高併發的?(2017-12-5-lyq) 449
1. 簡單介紹一下zookeeper以及zookeeper的原理。(2017-11-24-gxb) 450
1. 簡單介紹一下solr(2017-11-24-gxb) 451
2. solr怎麼設置搜索結果排名靠前?(2017-11-24-gxb) 452
3. solr中IK分詞器原理是什麼?(2017-11-24-gxb) 452
1. 什麼是webService?(2017-11-24-lyq) 452
2. 常見的遠程調用技術(2017-11-24-lyq) 452
1. 談談你對restful的理解以及在項目中的使用?(2017-11-30-wzz) 453
3、騰訊(2016年校招面試題2017-11-29-wzy) 464
4、北京寶藍德股份科技有限公司(2017-12-03-wmm) 477
1. 什麼叫對象?什麼叫類?什麼面向對象(OOP)? 551
2. 相對於JDK1.4,JDK1.5 有哪些新特性? 552
3. JAVA中使用final修飾符,對程序有哪些影響? 552
4. Java環境變量Unix/Linux下如何配置? 553
5. 寫出5個你在JAVA開發中經常使用的包含(全名),並簡述其做用。 554
6. 寫出5個常見的運行時異常(RuntimeException)。 555
7. 方法重載(overload)須要知足什麼條件,方法覆蓋/方法重寫(override)須要知足什麼條件?(二選一) 555
8. 繼承(inheritance)的優缺點是什麼? 556
12. 什麼叫對象持久化(OBJect PERSIstence),爲何要進行對象持久化? 558
15. 什麼叫髒數據,什麼叫髒讀(Dirty Read) 561
[if !supportLists]第一章 [endif]內容介紹
該寶典是一份知識點全面又能不斷更新,與時俱進的學習手冊,不只收錄了做者親身面試遇到的問題,還收錄了近上萬名黑馬學子面試時遇到的問題。咱們會一直不斷地更新和充實該寶典,同時也但願讀者朋友可以多多提供優質的面試題,也許下一個版本就有你提供的面試題哦。
本人的面試實戰記錄發佈在黑馬論壇:http://bbs.itheima.com/thread-196394-1-1.html
你們能夠訪問上面的網址,經過陽哥的實戰記錄略微感知一下真實面試的狀況,從中學習一些面試技巧以便讓本身在將來的面試中可以駕輕就熟,順利拿到本身喜歡的offer。
注意:該面試寶典僅供參考,因爲做者本人的知識水平有限加之編寫時間倉促所以不免有bug的存在,但願你們見諒。
該寶典的一個明確目標是可以讓90%以上的Java技術面試題都落到該寶典中,若是您有不錯的知識或者面試題,您能夠發送到wangzhenyang@itcast.cn,本人將不勝感激。讓天下沒有難學的知識,但願你個人努力能幫到更多的莘莘學子。
世間事,不少均可投機取巧,但技術卻必須靠日積月累的努力來提升。本寶典更加註重的是知識的掌握,而不只僅是對面試題的應付。在展現常見的面試問題以及回答技巧的同時還詳細講解了每一道題所包含的知識點,讓讀者不只知其然,更知其因此然。
[if !supportLists]第二章 [endif]JavaSE基礎
[if !supportLists]1、[endif]Java面向對象
[if !supportLists]1. [endif]面向對象都有哪些特性以及你對這些特性的理解
1)繼承:繼承是從已有類獲得繼承信息建立新類的過程。提供繼承信息的類被稱爲父類(超類、基類);獲得繼承信息的類被稱爲子類(派生類)。繼承讓變化中的軟件系統有了必定的延續性,同時繼承也是封裝程序中可變因素的重要手段。
2)封裝:一般認爲封裝是把數據和操做數據的方法綁定起來,對數據的訪問只能經過已定義的接口。面向對象的本質就是將現實世界描繪成一系列徹底自治、封閉的對象。咱們在類中編寫的方法就是對實現細節的一種封裝;咱們編寫一個類就是對數據和數據操做的封裝。能夠說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程接口。
3)多態性:多態性是指容許不一樣子類型的對象對同一消息做出不一樣的響應。簡單的說就是用一樣的對象引用調用一樣的方法可是作了不一樣的事情。多態性分爲編譯時的多態性和運行時的多態性。若是將對象的方法視爲對象向外界提供的服務,那麼運行時的多態性能夠解釋爲:當A系統訪問B系統提供的服務時,B系統有多種提供服務的方式,但一切對A系統來講都是透明的。方法重載(overload)實現的是編譯時的多態性(也稱爲前綁定),而方法重寫(override)實現的是運行時的多態性(也稱爲後綁定)。運行時的多態是面向對象最精髓的東西,要實現多態須要作兩件事:1. 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);2. 對象造型(用父類型引用引用子類型對象,這樣一樣的引用調用一樣的方法就會根據子類對象的不一樣而表現出不一樣的行爲)。
4)抽象:抽象是將一類對象的共同特徵總結出來構造類的過程,包括數據抽象和行爲抽象兩方面。抽象只關注對象有哪些屬性和行爲,並不關注這些行爲的細節是什麼。
注意:默認狀況下面向對象有3大特性,封裝、繼承、多態,若是面試官問讓說出4大特性,那麼咱們就把抽象加上去。
[if !supportLists]2. [endif]訪問權限修飾符public、private、protected, 以及不寫(默認)時的區別(2017-11-12)
該題目比較簡單,不一樣的權限修飾符的區別見下表。
修飾符當前類同 包子 類其餘包
public √√√√
protected√√√×
default√√××
private√×××
[if !supportLists]3. [endif]如何理解clone對象
3.1爲何要用clone?
在實際編程過程當中,咱們經常要遇到這種狀況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能會須要一個和A徹底相同新對象B,而且此後對B 任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象肯定的。在Java語言中,用簡單的賦值語句是不能知足這種需求的。要知足這種需求雖然有不少途徑,但實現clone()方法是其中最簡單,也是最高效的手段。
3.2 new一個對象的過程和clone一個對象的過程區別
new操做符的本意是分配內存。程序執行到new操做符時,首先去看new操做符後面的類型,由於知道了類型,才能知道要分配多大的內存空間。分配完內存以後,再調用構造函數,填充對象的各個域,這一步叫作對象的初始化,構造方法返回後,一個對象建立完畢,能夠把他的引用(地址)發佈到外部,在外部就能夠使用這個引用操縱這個對象。
clone在第一步是和new類似的,都是分配內存,調用clone方法時,分配的內存和原對象(即調用clone方法的對象)相同,而後再使用原對象中對應的各個域,填充新對象的域,填充完成以後,clone方法返回,一個新的相同的對象被建立,一樣能夠把這個新對象的引用發佈到外部。
3.3 clone對象的使用
3.3.1複製對象和複製引用的區別
[if !supportLists]1. [endif]Person p = new Person(23, "zhang");
[if !supportLists]2. [endif]Person p1 = p;
[if !supportLists]3. [endif]System.out.println(p);
[if !supportLists]4. [endif]System.out.println(p1);
當Person p1 = p;執行以後, 是建立了一個新的對象嗎? 首先看打印結果:
[if !supportLists]1.[endif]com.itheima.Person@2f9ee1ac
[if !supportLists]2.[endif]com.itheima.Person@2f9ee1ac
能夠看出,打印的地址值是相同的,既然地址都是相同的,那麼確定是同一個對象。p和p1只是引用而已,他們都指向了一個相同的對象Person(23, 「zhang」) 。 能夠把這種現象叫作引用的複製。上面代碼執行完成以後, 內存中的情景以下圖所示:
而下面的代碼是真真正正的克隆了一個對象。
[if !supportLists]1.[endif]Person p = new Person(23, "zhang");
[if !supportLists]2.[endif]Person p1 = (Person) p.clone();
[if !supportLists]3.[endif]System.out.println(p);
[if !supportLists]4.[endif]System.out.println(p1);
從打印結果能夠看出,兩個對象的地址是不一樣的,也就是說建立了新的對象,而不是把原對象的地址賦給了一個新的引用變量:
[if !supportLists]1. [endif]com.itheima.Person@2f9ee1ac
[if !supportLists]2. [endif]com.itheima.Person@67f1fba0
以上代碼執行完成後,內存中的情景以下圖所示:
3.3.2 深拷貝和淺拷貝
上面的示例代碼中,Person中有兩個成員變量,分別是name和age, name是String類型, age是int類型。代碼很是簡單,以下所示:
[if !supportLists]1.[endif]public class Person implements Cloneable{
[if !supportLists]2.[endif]privatint age ;
[if !supportLists]3.[endif] private String name;
[if !supportLists]4.[endif] public Person(int age, String name) {
[if !supportLists]5.[endif] this.age = age;
[if !supportLists]6.[endif] this.name = name;
[if !supportLists]7.[endif] }
[if !supportLists]8.[endif] public Person() {}
[if !supportLists]9.[endif] public int getAge() {
[if !supportLists]10.[endif] return age;
[if !supportLists]11.[endif] }
[if !supportLists]12.[endif] public String getName() {
[if !supportLists]13.[endif] return name;
[if !supportLists]14.[endif] }
[if !supportLists]15.[endif] @Override
[if !supportLists]16.[endif] protected Object clone() throws CloneNotSupportedException {
[if !supportLists]17.[endif] return (Person)super.clone();
[if !supportLists]18.[endif] }
[if !supportLists]19.[endif]}
因爲age是基本數據類型, 那麼對它的拷貝沒有什麼疑議,直接將一個4字節的整數值拷貝過來就行。可是name是String類型的, 它只是一個引用, 指向一個真正的String對象,那麼對它的拷貝有兩種方式: 直接將原對象中的name的引用值拷貝給新對象的name字段, 或者是根據原Person對象中的name指向的字符串對象建立一個新的相同的字符串對象,將這個新字符串對象的引用賦給新拷貝的Person對象的name字段。這兩種拷貝方式分別叫作淺拷貝和深拷貝。深拷貝和淺拷貝的原理以下圖所示:
下面經過代碼進行驗證。若是兩個Person對象的name的地址值相同, 說明兩個對象的name都指向同一個String對象,也就是淺拷貝, 而若是兩個對象的name的地址值不一樣, 那麼就說明指向不一樣的String對象, 也就是在拷貝Person對象的時候, 同時拷貝了name引用的String對象, 也就是深拷貝。驗證代碼以下:
[if !supportLists]1. [endif]Person p = new Person(23, "zhang");
[if !supportLists]2. [endif]Person p1 = (Person) p.clone();
[if !supportLists]3. [endif]String result = p.getName() == p1.getName()
[if !supportLists]4. [endif]? "clone是淺拷貝的" : "clone是深拷貝的";
[if !supportLists]5. [endif]System.out.println(result);
打印結果爲:
[if !supportLists]6. [endif]clone是淺拷貝的
因此,clone方法執行的是淺拷貝, 在編寫程序時要注意這個細節。
如何進行深拷貝:
由上一節的內容能夠得出以下結論:若是想要深拷貝一個對象,這個對象必需要實現Cloneable接口,實現clone方法,而且在clone方法內部,把該對象引用的其餘對象也要clone一份,這就要求這個被引用的對象必須也要實現Cloneable接口而且實現clone方法。那麼,按照上面的結論,實現如下代碼 Body類組合了Head類,要想深拷貝Body類,必須在Body類的clone方法中將Head類也要拷貝一份。代碼以下:
[if !supportLists]1.[endif]static class Body implements Cloneable{
[if !supportLists]2.[endif] public Head head;
[if !supportLists]3.[endif] public Body() {}
[if !supportLists]4.[endif] public Body(Head head) {this.head = head;}
[if !supportLists]5.[endif] @Override
[if !supportLists]6.[endif] protected Object clone() throws CloneNotSupportedException {
[if !supportLists]7.[endif] Body newBody = (Body) super.clone();
[if !supportLists]8.[endif] newBody.head = (Head) head.clone();
[if !supportLists]9.[endif] return newBody;
[if !supportLists]10.[endif] }
[if !supportLists]11.[endif]}
[if !supportLists]12.[endif]static class Head implements Cloneable{
[if !supportLists]13.[endif] public Face face;
[if !supportLists]14.[endif] public Head() {}
[if !supportLists]15.[endif] @Override
[if !supportLists]16.[endif] protected Object clone() throws CloneNotSupportedException {
[if !supportLists]17.[endif] return super.clone();
[if !supportLists]18.[endif] } }
[if !supportLists]19.[endif]public static void main(String[] args) throws CloneNotSupportedException {
[if !supportLists]20.[endif] Body body = new Body(new Head(new Face()));
[if !supportLists]21.[endif] Body body1 = (Body) body.clone();
[if !supportLists]22.[endif] System.out.println("body == body1 : " + (body == body1) );
[if !supportLists]23.[endif] System.out.println("body.head == body1.head : " + (body.head == body1.head));
[if !supportLists]24.[endif]}
打印結果爲:
[if !supportLists]1. [endif]body == body1 : false
[if !supportLists]2. [endif]body.head == body1.head : false
[if !supportLists]2、[endif]JavaSE語法(2017-11-12-wl)
[if !supportLists]1. [endif]Java有沒有goto語句?(2017-11-12-wl)
goto是Java 中的保留字,在目前版本的Java中沒有使用。根據 James Gosling(Java 之父)編寫的《The Java Programming Language》一書的附錄中給出了一個 Java 關鍵字列表,其中有 goto 和 const,可是這兩個是目前沒法使用的關鍵字,所以有些地方將其稱之爲保留字,其實保留字這個詞應該有更普遍的意義,由於熟悉 C 語言的程序員都知道,在系統類庫中使用過的有特殊意義的單詞或單詞的組合都被視爲保留字。
[if !supportLists]2. [endif]& 和 && 的區別(2017-11-12-wl)
&運算符有兩種用法:(1)按位與;(2)邏輯與。
&&運算符是短路與運算。邏輯與跟短路與的差異是很是巨大的,雖然兩者都要求運算符左右兩端的布爾值都是 true 整個表達式的值纔是 true。
&&之因此稱爲短路運算是由於,若是&&左邊的表達式的值是 false,右邊的表達式會被直接短路掉,不會進行運算。不少時候咱們可能都須要用&&而不是&,例如在驗證用戶登陸時斷定用戶名不是 null 並且不是空字符串,應當寫爲username != null &&!username.equals(""),兩者的順序不能交換,更不能用&運算符,由於第一個條件若是不成立,根本不能進行字符串的 equals 比較,不然會產生 NullPointerException 異常。注意:邏輯或運算符(|)和短路或運算符(||)的差異也是如此。
[if !supportLists]3. [endif]在Java中,如何跳出當前的多重嵌套循環(2017-11-14-wl)
在最外層循環前加一個標記如A,而後用 break A;能夠跳出多重循環。(Java 中支持帶標籤的break和continue 語句,做用有點相似於 C 和 C++中的 goto 語句,可是就像要避免使用 goto 同樣,應該避免使用帶標籤的 break 和 continue,由於它不會讓你的程序變得更優雅,不少時候甚至有相反的做用)。
[if !supportLists]4. [endif]兩個對象值相同(x.equals(y) == true),但卻可有不一樣的hashCode,這句話對不對?(2017-11-14-wl)
不對,若是兩個對象x 和 y 知足 x.equals(y) == true,它們的哈希碼(hashCode)應當相同。
Java 對於eqauls 方法和 hashCode 方法是這樣規定的:(1)若是兩個對象相同(equals 方法返回 true),那麼它們的hashCode 值必定要相同;(2)若是兩個對象的 hashCode 相同,它們並不必定相同。固然,你未必要按照要求去作,可是若是你違背了上述原則就會發如今使用容器時,相同的對象能夠出如今 Set 集合中,同時增長新元素的效率會大大降低(對於使用哈希存儲的系統,若是哈希碼頻繁的衝突將會形成存取性能急劇降低)。
關於equals 和 hashCode方法,不少Java程序員都知道,但不少人也就是僅僅知道而已,在Joshua Bloch
的大做《Effective Java》(不少軟件公司,《Effective Java》、《Java 編程思想》以及《重構:改善既有代碼質量》是 Java 程序員必看書籍,若是你還沒看過,那就趕忙去買一本吧)中是這樣介紹 equals 方法的。
首先equals 方法必須知足自反性(x.equals(x)必須返回 true)、對稱性(x.equals(y)返回 true 時,y.equals(x)也必須返回 true)、傳遞性(x.equals(y)和 y.equals(z)都返回 true 時,x.equals(z)也必須返回 true)和一致性(當x 和 y 引用的對象信息沒有被修改時,屢次調用 x.equals(y)應該獲得一樣的返回值),並且對於任何非 null值的引用 x,x.equals(null)必須返回false。實現高質量的equals方法的訣竅包括:1. 使用==操做符檢查"參數是否爲這個對象的引用";2. 使用 instanceof 操做符檢查"參數是否爲正確的類型";3. 對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;4. 編寫完 equals 方法後,問本身它是否知足對稱性、傳遞性、一致性;5. 重寫 equals 時老是要重寫 hashCode;6. 不要將 equals 方法參數中的 Object 對象替換爲其餘的類型,在重寫時不要忘掉@Override 註解。
[if !supportLists]5. [endif]是否能夠繼承String (2017-11-14-wl)
String類是final類,不能夠被繼承。
繼承String 自己就是一個錯誤的行爲,對 String 類型最好的重用方式是關聯關係(Has-A)和依賴關係(Use-A)而不是繼承關係(Is-A)。
[if !supportLists]6. [endif]當一個對象被看成參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏究竟是值傳遞仍是引用傳遞?(2017-11-14-wl)
是值傳遞。Java語言的方法調用只支持參數的值傳遞。當一個對象實例做爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性能夠在被調用過程當中被改變,但對對象引用的改變是不會影響到調用者的。C++和 C#中能夠經過傳引用或傳輸出參數來改變傳入的參數的值。說明:Java中沒有傳引用實在是很是的不方便,這一點在 Java 8 中仍然沒有獲得改進,正是如此在 Java 編寫的代碼中才會出現大量的 Wrapper 類(將須要經過方法調用修改的引用置於一個 Wrapper 類中,再將Wrapper 對象傳入方法),這樣的作法只會讓代碼變得臃腫,尤爲是讓從 C 和 C++轉型爲 Java 程序員的開發者沒法容忍。
[if !supportLists]7. [endif]重載(overload)和重寫(override)的區別?重載的方法可否根據返回類型進行區分?(2017-11-15-wl)
方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,然後者實現的是運行時的多態性。重載發生在一個類中,同名的方法若是有不一樣的參數列表(參數類型不一樣、參數個數不一樣或者兩者都不一樣)則視爲重載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。
方法重載的規則:
[if !supportLists]1.[endif]方法名一致,參數列表中參數的順序,類型,個數不一樣。
[if !supportLists]2.[endif]重載與方法的返回值無關,存在於父類和子類,同類中。
[if !supportLists]3.[endif]能夠拋出不一樣的異常,能夠有不一樣修飾符。
方法重寫的規則:
[if !supportLists]1.[endif]參數列表必須徹底與被重寫方法的一致,返回類型必須徹底與被重寫方法的返回類型一致。
[if !supportLists]2.[endif]構造方法不能被重寫,聲明爲final的方法不能被重寫,聲明爲static的方法不能被重寫,可是可以被再次聲明。
[if !supportLists]3.[endif]訪問權限不能比父類中被重寫的方法的訪問權限更低。
[if !supportLists]4.[endif]重寫的方法可以拋出任何非強制異常(UncheckedException,也叫非運行時異常),不管被重寫的方法是否拋出異常。可是,重寫的方法不能拋出新的強制性異常,或者比被重寫方法聲明的更普遍的強制性異常,反之則能夠。
[if !supportLists]8. [endif]爲何函數不能根據返回類型來區分重載?(2017-11-15-wl)
該道題來自華爲面試題。
由於調用時不能指定類型信息,編譯器不知道你要調用哪一個函數。例如:
[if !supportLists]1.[endif]float max(int a, int b);
[if !supportLists]2.[endif]int max(int a, int b);
當調用max(1, 2);時沒法肯定調用的是哪一個,單從這一點上來講,僅返回值類型不一樣的重載是不該該容許的。
再好比對下面這兩個方法來講,雖然它們有一樣的名字和自變量,但實際上是很容易區分的:
[if !supportLists]1.[endif]void f() {}
[if !supportLists]2.[endif]int f() {}
若編譯器可根據上下文(語境)明確判斷出含義,好比在int x=f()中,那麼這樣作徹底沒有問題。然而,咱們也可能調用一個方法,同時忽略返回值;咱們一般把這稱爲「爲它的反作用去調用一個方法」,由於咱們關心的不是返回值,而是方法調用的其餘效果。因此假如咱們像下面這樣調用方法:f(); Java 怎樣判斷f()的具體調用方式呢?並且別人如何識別並理解代碼呢?因爲存在這一類的問題,因此不能。
函數的返回值只是做爲函數運行以後的一個「狀態」,他是保持方法的調用者與被調用者進行通訊的關鍵。並不能做爲某個方法的「標識」。
[if !supportLists]9. [endif]char 型變量中能不能存儲一箇中文漢字,爲何?(2017-11-16-wl)
char 類型能夠存儲一箇中文漢字,由於Java中使用的編碼是 Unicode(不選擇任何特定的編碼,直接
使用字符在字符集中的編號,這是統一的惟一方法),一個char 類型佔 2 個字節(16 比特),因此放一箇中文是沒問題的。
補充:使用Unicode意味着字符在JVM內部和外部有不一樣的表現形式,在JVM內部都是 Unicode,當這個字符被從JVM內部轉移到外部時(例如存入文件系統中),須要進行編碼轉換。因此Java中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如 InputStreamReader 和 OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務;對於 C 程序員來講,要完成這樣的編碼轉換恐怕要依賴於 union(聯合體/共用體)共享內存的特徵來實現了。
[if !supportLists]10. [endif]抽象類(abstract class)和接口(interface)有什麼異同?(2017-11-16-wl)
不一樣:
抽象類:
1.抽象類中能夠定義構造器
2.能夠有抽象方法和具體方法
3.接口中的成員全都是public的
4.抽象類中能夠定義成員變量
5.有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法
6.抽象類中能夠包含靜態方法
7.一個類只能繼承一個抽象類
接口:
1.接口中不能定義構造器
2.方法所有都是抽象方法
3.抽象類中的成員能夠是 private、默認、protected、public
4.接口中定義的成員變量實際上都是常量
5.接口中不能有靜態方法
6.一個類能夠實現多個接口
相同:
1.不可以實例化
2.能夠將抽象類和接口類型做爲引用類型
3.一個類若是繼承了某個抽象類或者實現了某個接口都須要對其中的抽象方法所有進行實現,不然該類仍然須要被聲明爲抽象類
[if !supportLists]11. [endif]抽象的(abstract)方法是否可同時是靜態的(static), 是否可同時是本地方法(native),是否可同時被synchronized(2017-11-16-wl)
都不能。抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。本地方法是由
本地代碼(如C 代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized 和方法的實現細節有關,抽象方法不涉及實現細節,所以也是相互矛盾的。
[if !supportLists]12. [endif]闡述靜態變量和實例變量的區別?(2017-11-16-wl)
靜態變量: 是被static 修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類無論建立多少個對象,靜態變量在內存中有且僅有一個拷貝;
實例變量: 必須依存於某一實例,須要先建立對象而後經過對象才能訪問到它。靜態變量能夠實現讓多個對象共享內存。
[if !supportLists]13. [endif]==和equals的區別?(2017-11-22-wzz)
equals和== 最大的區別是一個是方法一個是運算符。
==:若是比較的對象是基本數據類型,則比較的是數值是否相等;若是比較的是引用數據類型,則比較的是對象的地址值是否相等。
equals():用來比較方法兩個對象的內容是否相等。
注意:equals方法不能用於基本數據類型的變量,若是沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址。
[if !supportLists]14. [endif]break和continue的區別?(2017-11-23-wzz)
break和continue都是用來控制循環的語句。
break用於徹底結束一個循環,跳出循環體執行循環後面的語句。
continue用於跳過本次循環,執行下次循環。
沒有。由於String被設計成不可變(immutable)類,因此它的全部對象都是不可變對象。在這段代碼中,s原先指向一個String對象,內容是 "Hello",而後咱們對s進行了「+」操做,那麼s所指向的那個對象是否發生了改變呢?答案是沒有。這時,s不指向原來那個對象了,而指向了另外一個 String對象,內容爲"Hello world!",原來那個對象還存在於內存之中,只是s這個引用變量再也不指向它了。
經過上面的說明,咱們很容易導出另外一個結論,若是常常對字符串進行各類各樣的修改,或者說,不可預見的修改,那麼使用String來表明字符串的話會引發很大的內存開銷。由於 String對象創建以後不能再改變,因此對於每個不一樣的字符串,都須要一個String對象來表示。這時,應該考慮使用StringBuffer類,它容許修改,而不是每一個不一樣的字符串都要生成一個新的對象。而且,這兩種類的對象轉換十分容易。同時,咱們還能夠知道,若是要使用內容相同的字符串,沒必要每次都new一個String。例如咱們要在構造器中對一個名叫s的String引用變量進行初始化,把它設置爲初始值,應當這樣作:
[if !supportLists]1.[endif]public class Demo {
[if !supportLists]2.[endif] private String s;
[if !supportLists]3.[endif] ...
[if !supportLists]4.[endif] s = "Initial Value";
[if !supportLists]5.[endif] ...
[if !supportLists]6.[endif]}
而非
[if !supportLists]1.[endif]s = new String("Initial Value");
後者每次都會調用構造器,生成新對象,性能低下且內存開銷大,而且沒有意義,由於String對象不可改變,因此對於內容相同的字符串,只要一個String對象來表示就能夠了。也就說,屢次調用上面的構造器建立多個對象,他們的String類型屬性s都指向同一個對象。
上面的結論還基於這樣一個事實:對於字符串常量,若是內容相同,Java認爲它們表明同一個String對象。而用關鍵字new調用構造器,老是會建立一個新的對象,不管內容是否相同。 至於爲何要把String類設計成不可變類,是它的用途決定的。其實不僅String,不少Java標準類庫中的類都是不可變的。在開發一個系統的時候,咱們有時候也須要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的體現。不可變類有一些優勢,好比由於它的對象是隻讀的,因此多線程併發訪問也不會有任何問題。固然也有一些缺點,好比每一個不一樣的狀態都要一個對象來表明,可能會形成性能上的問題。因此Java標準類庫還提供了一個可變版本,即 StringBuffer。
[if !supportLists]3、[endif]Java中的多態
[if !supportLists]1. [endif]Java中實現多態的機制是什麼?
靠的是父類或接口定義的引用變量能夠指向子類或具體實現類的實例對象,而程序調用的方法在運行期才動態綁定,就是引用變量所指向的具體實例對象的方法,也就是內存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。
[if !supportLists]4、[endif]Java的異常處理
[if !supportLists]1. [endif]Java中異常分爲哪些種類
[if !supportLists]1)[endif]按照異常須要處理的時機分爲編譯時異常(也叫強制性異常)也叫CheckedException和運行時異常(也叫非強制性異常)也叫RuntimeException。只有java語言提供了Checked異常,Java認爲Checked異常都是能夠被處理的異常,因此Java程序必須顯式處理Checked異常。若是程序沒有處理Checked異常,該程序在編譯時就會發生錯誤沒法編譯。這體現了Java的設計哲學:沒有完善錯誤處理的代碼根本沒有機會被執行。對Checked異常處理方法有兩種:
1 當前方法知道如何處理該異常,則用try...catch塊來處理該異常。
2 當前方法不知道如何處理,則在定義該方法是聲明拋出該異常。
運行時異常只有當代碼在運行時才發行的異常,編譯時不須要try catch。Runtime如除數是0和數組下標越界等,其產生頻繁,處理麻煩,若顯示申明或者捕獲將會對程序的可讀性和運行效率影響很大。因此由系統自動檢測並將它們交給缺省的異常處理程序。固然若是你有處理要求也能夠顯示捕獲它們。
[if !supportLists]2. [endif]調用下面的方法,獲得的返回值是什麼?
[if !supportLists]1. [endif]public int getNum(){
[if !supportLists]2. [endif] try {
[if !supportLists]3. [endif] int a = 1/0;
[if !supportLists]4. [endif] return 1;
[if !supportLists]5. [endif] } catch (Exception e) {
[if !supportLists]6. [endif] return 2;
[if !supportLists]7. [endif] }finally{
[if !supportLists]8. [endif] return 3;
[if !supportLists]9. [endif] }
代碼在走到第3行的時候遇到了一個MathException,這時第四行的代碼就不會執行了,代碼直接跳轉到catch語句中,走到第6行的時候,異常機制有這麼一個原則若是在catch中遇到了return或者異常等能使該函數終止的話那麼有finally就必須先執行完finally代碼塊裏面的代碼而後再返回值。所以代碼又跳到第8行,惋惜第8行是一個return語句,那麼這個時候方法就結束了,所以第6行的返回結果就沒法被真正返回。若是finally僅僅是處理了一個釋放資源的操做,那麼該道題最終返回的結果就是2。所以上面返回值是3。
[if !supportLists]3. [endif]error和exception的區別?(2017-2-23)
Error類和Exception類的父類都是Throwable類,他們的區別以下。
Error類通常是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢出等。對於這類錯誤的致使的應用程序中斷,僅靠程序自己沒法恢復和和預防,遇到這樣的錯誤,建議讓程序終止。
Exception類表示程序能夠處理的異常,能夠捕獲且可能恢復。遇到這類異常,應該儘量處理異常,使程序恢復運行,而不該該隨意終止異常。
Exception類又分爲運行時異常(Runtime Exception)和受檢查的異常(Checked Exception ),運行時異常;ArithmaticException,IllegalArgumentException,編譯能經過,可是一運行就終止了,程序不會處理運行時異常,出現這類異常,程序會終止。而受檢查的異常,要麼用try。。。catch捕獲,要麼用throws字句聲明拋出,交給它的父類處理,不然編譯不會經過。
[if !supportLists]4. [endif]java異常處理機制(2017-2-23)
Java對異常進行了分類,不一樣類型的異常分別用不一樣的Java類表示,全部異常的根類爲java.lang.Throwable,Throwable下面又派生了兩個子類:Error和Exception,Error表示應用程序自己沒法克服和恢復的一種嚴重問題。Exception表示程序還可以克服和恢復的問題,其中又分爲系統異常和普通異常,系統異常是軟件自己缺陷所致使的問題,也就是軟件開發人員考慮不周所致使的問題,軟件使用者沒法克服和恢復這種問題,但在這種問題下還能夠讓軟件系統繼續運行或者讓軟件死掉,例如,數組腳本越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException);普通異常是運行環境的變化或異常所致使的問題,是用戶可以克服的問題,例如,網絡斷線,硬盤空間不夠,發生這樣的異常後,程序不該該死掉。
java爲系統異常和普通異常提供了不一樣的解決方案,編譯器強制普通異常必須try..catch處理或用throws聲明繼續拋給上層調用方法處理,因此普通異常也稱爲checked異常,而系統異常能夠處理也能夠不處理,因此,編譯器不強制用try..catch處理或用throws聲明,因此係統異常也稱爲unchecked異常。
[if !supportLists]5. [endif]請寫出你最多見的5個RuntimeException(2017-11-22-wzz)
下面列舉幾個常見的RuntimeException。
1)java.lang.NullPointerException 空指針異常;出現緣由:調用了未經初始化的對象或者是不存在的對象。
2)java.lang.ClassNotFoundException 指定的類找不到;出現緣由:類的名稱和路徑加載錯誤;一般都是程序試圖經過字符串來加載某個類時可能引起異常。
3)java.lang.NumberFormatException 字符串轉換爲數字異常;出現緣由:字符型數據中包含非數字型字符。
4)java.lang.IndexOutOfBoundsException 數組角標越界異常,常見於操做數組對象時發生。
5)java.lang.IllegalArgumentException 方法傳遞參數錯誤。
6)java.lang.ClassCastException 數據類型轉換異常。
7)java.lang.NoClassDefFoundException 未找到類定義錯誤。
8)SQLException SQL異常,常見於操做數據庫時的SQL語句錯誤。
9)java.lang.InstantiationException實例化異常。
10)java.lang.NoSuchMethodException 方法不存在異常。
[if !supportLists]6. [endif]throw和throws的區別(2017-11-22-wzz)
throw:
1)throw語句用在方法體內,表示拋出異常,由方法體內的語句處理。
2)throw是具體向外拋出異常的動做,因此它拋出的是一個異常實例,執行throw必定是拋出了某種異常。
throws:
1)throws語句是用在方法聲明後面,表示若是拋出異常,由該方法的調用者來進行異常的處理。
2)throws主要是聲明這個方法會拋出某種類型的異常,讓它的使用者要知道須要捕獲的異常的類型。
3)throws表示出現異常的一種可能性,並不必定會發生這種異常。
[if !supportLists]7. [endif]final、finally、finalize的區別?(2017-11-23-wzz)
1)final:用於聲明屬性,方法和類,分別表示屬性不可變,方法不可覆蓋,被其修飾的類不可繼承。
2)finally:異常處理語句結構的一部分,表示老是執行。
3)finalize:Object類的一個方法,在垃圾回收器執行的時候會調用被回收對象的此方法,能夠覆蓋此方法提供垃圾收集時的其餘資源回收,例如關閉文件等。該方法更像是一個對象生命週期的臨終方法,當該方法被系統調用則表明該對象即將「死亡」,可是須要注意的是,咱們主動行爲上去調用該方法並不會致使該對象「死亡」,這是一個被動的方法(其實就是回調方法),不須要咱們調用。
[if !supportLists]5、[endif]JavaSE經常使用API
[if !supportLists]1. [endif]Math.round(11.5)等於多少?Math.round(- 11.5) 又等於多少?(2017-11-14-wl)
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四捨五入的原理是在參數上加 0.5而後進行取整。
[if !supportLists]2. [endif]switch是否能做用在byte上,是否能做用在long上,是否能做用在String上?(2017-11-14-wl)
Java5之前switch(expr)中,expr 只能是 byte、short、char、int。從 Java 5 開始,Java 中引入了枚舉類型,expr 也能夠是 enum 類型。
從Java 7 開始,expr 還能夠是字符串(String),可是長整型(long)在目前全部的版本中都是不能夠的。
[if !supportLists]3. [endif]數組有沒有length() 方法?String有沒有length() 方法?(2017-11-14-wl)
數組沒有length()方法,而是有length 的屬性。String有length()方法。JavaScript 中,得到字符串的長度是經過 length 屬性獲得的,這一點容易和 Java 混淆。
[if !supportLists]4. [endif]String 、StringBuilder 、StringBuffer的區別?(2017-11-14-wl)
Java平臺提供了兩種類型的字符串:String和StringBuffer/StringBuilder,它們均可以儲存和操做字符串,區別以下。
[if !supportLists]1)[endif]String是隻讀字符串,也就意味着String引用的字符串內容是不能被改變的。初學者可能會有這樣的誤解:
[if !supportLists]1. [endif]String str = 「abc」;
[if !supportLists]2. [endif]str = 「bcd」;
如上,字符串str明明是能夠改變的呀!其實否則,str僅僅是一個引用對象,它指向一個字符串對象「abc」。第二行代碼的含義是讓str從新指向了一個新的字符串「bcd」對象,而「abc」對象並無任何改變,只不過該對象已經成爲一個不可及對象罷了。
2)StringBuffer/StringBuilder表示的字符串對象能夠直接進行修改。
3)StringBuilder是Java5中引入的,它和 StringBuffer的方法徹底相同,區別在於它是在單線程環境下使用的,由於它的全部方法都沒有被synchronized修飾,所以它的效率理論上也比StringBuffer要高。
[if !supportLists]5. [endif]什麼狀況下用「+」運算符進行字符串鏈接比調用StringBuffer/StringBuilder 對象的append方法鏈接字符串性能更好?(2017-11-14-wl)
該題來自華爲。
字符串是Java程序中最經常使用的數據結構之一。在Java中String類已經重載了"+"。也就是說,字符串能夠直接使用"+"進行鏈接,以下面代碼所示:
[if !supportLists]1.[endif]String s = "abc" + "ddd";
但這樣作真的好嗎?固然,這個問題不能簡單地回答yes or no。要根據具體狀況來定。在Java中提供了一個StringBuilder類(這個類只在J2SE5及以上版本提供,之前的版本使用StringBuffer類),這個類也能夠起到"+"的做用。那麼咱們應該用哪一個呢?
下面讓咱們先看看以下的代碼:
[if !supportLists]1. [endif]package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] public class TestSimplePlus
[if !supportLists]4. [endif] {
[if !supportLists]5. [endif] public static void main(String[] args)
[if !supportLists]6. [endif] {
[if !supportLists]7. [endif] String s = "abc";
[if !supportLists]8. [endif] String ss = "ok" + s + "xyz" + 5;
[if !supportLists]9. [endif] System.out.println(ss);
[if !supportLists]10. [endif] }
[if !supportLists]11. [endif] }
上面的代碼將會輸出正確的結果。從表面上看,對字符串和整型使用"+"號並無什麼區別,但事實真的如此嗎?下面讓咱們來看看這段代碼的本質。
咱們首先使用反編譯工具(如jdk帶的javap、或jad)將TestSimplePlus反編譯成Java Byte Code,其中的奧祕就一目瞭然了。在本文將使用jad來反編譯,命令以下:jad -o -a -s d.java TestSimplePlus.class
反編譯後的代碼以下:
[if !supportLists]1. [endif]package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]import java.io.PrintStream;
[if !supportLists]4. [endif]
[if !supportLists]5. [endif]public class TestSimplePlus
[if !supportLists]6. [endif]{
[if !supportLists]7. [endif] public TestSimplePlus()
[if !supportLists]8. [endif] {
[if !supportLists]9. [endif] // 0 0:aload_0
[if !supportLists]10. [endif] // 1 1:invokespecial #8
[if !supportLists]11. [endif] // 2 4:return
[if !supportLists]12. [endif] }
[if !supportLists]13. [endif]
[if !supportLists]14. [endif] public static void main(String args[])
[if !supportLists]15. [endif] {
[if !supportLists]16. [endif] String s = "abc";
[if !supportLists]17. [endif] // 0 0:ldc1 #16
[if !supportLists]18. [endif] // 1 2:astore_1
[if !supportLists]19. [endif] String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();
[if !supportLists]20. [endif] // 2 3:new #18
[if !supportLists]21. [endif] // 3 6:dup
[if !supportLists]22. [endif] // 4 7:ldc1 #20
[if !supportLists]23. [endif] // 5 9:invokespecial #22
[if !supportLists]24. [endif] // 6 12:aload_1
[if !supportLists]25. [endif] // 7 13:invokevirtual #25
[if !supportLists]26. [endif] // 8 16:ldc1 #29
[if !supportLists]27. [endif] // 9 18:invokevirtual #25
[if !supportLists]28. [endif] // 10 21:iconst_5
[if !supportLists]29. [endif] // 11 22:invokevirtual #31
[if !supportLists]30. [endif] // 12 25:invokevirtual #34
[if !supportLists]31. [endif] // 13 28:astore_2
[if !supportLists]32. [endif] System.out.println(ss);
[if !supportLists]33. [endif] // 14 29:getstatic #38
[if !supportLists]34. [endif] // 15 32:aload_2
[if !supportLists]35. [endif] // 16 33:invokevirtual #44
[if !supportLists]36. [endif] // 17 36:return
[if !supportLists]37. [endif] }
[if !supportLists]38. [endif]}
讀者可能看到上面的Java字節碼感到迷糊,不過你們沒必要擔憂。本文的目的並非講解Java Byte Code,所以,並不用瞭解具體的字節碼的含義。
使用jad反編譯的好處之一就是能夠同時生成字節碼和源代碼。這樣能夠進行對照研究。從上面的代碼很容易看出,雖然在源程序中使用了"+",但在編譯時仍然將"+"轉換成StringBuilder。所以,咱們能夠得出結論,在Java中不管使用何種方式進行字符串鏈接,實際上都使用的是StringBuilder。
那麼是否是能夠根據這個結論推出使用"+"和StringBuilder的效果是同樣的呢?這個要從兩個方面的解釋。若是從運行結果來解釋,那麼"+"和StringBuilder是徹底等效的。但若是從運行效率和資源消耗方面看,那它們將存在很大的區別。
固然,若是鏈接字符串行表達式很簡單(如上面的順序結構),那麼"+"和StringBuilder基本是同樣的,但若是結構比較複雜,如使用循環來鏈接字符串,那麼產生的Java Byte Code就會有很大的區別。先讓咱們看看以下的代碼:
[if !supportLists]1. [endif]package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] import java.util.*;
[if !supportLists]4. [endif]
[if !supportLists]5. [endif] public class TestComplexPlus
[if !supportLists]6. [endif] {
[if !supportLists]7. [endif] public static void main(String[] args)
[if !supportLists]8. [endif] {
[if !supportLists]9. [endif] String s = "";
[if !supportLists]10. [endif] Random rand = new Random();
[if !supportLists]11. [endif] for (int i = 0; i < 10; i++)
[if !supportLists]12. [endif] {
[if !supportLists]13. [endif] s = s + rand.nextInt(1000) + " ";
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif] System.out.println(s);
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif] }
上面的代碼返編譯後的Java Byte Code以下:
[if !supportLists]1. [endif]package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]import java.io.PrintStream;
[if !supportLists]4. [endif]import java.util.Random;
[if !supportLists]5. [endif]
[if !supportLists]6. [endif]public class TestComplexPlus
[if !supportLists]7. [endif]{
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] public TestComplexPlus()
[if !supportLists]10. [endif] {
[if !supportLists]11. [endif] // 0 0:aload_0
[if !supportLists]12. [endif] // 1 1:invokespecial #8
[if !supportLists]13. [endif] // 2 4:return
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] public static void main(String args[])
[if !supportLists]17. [endif] {
[if !supportLists]18. [endif] String s = "";
[if !supportLists]19. [endif] // 0 0:ldc1 #16
[if !supportLists]20. [endif] // 1 2:astore_1
[if !supportLists]21. [endif] Random rand = new Random();
[if !supportLists]22. [endif] // 2 3:new #18
[if !supportLists]23. [endif] // 3 6:dup
[if !supportLists]24. [endif] // 4 7:invokespecial #20
[if !supportLists]25. [endif] // 5 10:astore_2
[if !supportLists]26. [endif] for(int i = 0; i < 10; i++)
[if !supportLists]27. [endif] //* 6 11:iconst_0
[if !supportLists]28. [endif] //* 7 12:istore_3
[if !supportLists]29. [endif] //* 8 13:goto 49
[if !supportLists]30. [endif] s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").toString();
[if !supportLists]31. [endif] // 9 16:new #21
[if !supportLists]32. [endif] // 10 19:dup
[if !supportLists]33. [endif] // 11 20:aload_1
[if !supportLists]34. [endif] // 12 21:invokestatic #23
[if !supportLists]35. [endif] // 13 24:invokespecial #29
[if !supportLists]36. [endif] // 14 27:aload_2
[if !supportLists]37. [endif] // 15 28:sipush 1000
[if !supportLists]38. [endif] // 16 31:invokevirtual #32
[if !supportLists]39. [endif] // 17 34:invokevirtual #36
[if !supportLists]40. [endif] // 18 37:ldc1 #40
[if !supportLists]41. [endif] // 19 39:invokevirtual #42
[if !supportLists]42. [endif] // 20 42:invokevirtual #45
[if !supportLists]43. [endif] // 21 45:astore_1
[if !supportLists]44. [endif]
[if !supportLists]45. [endif] // 22 46:iinc 3 1
[if !supportLists]46. [endif] // 23 49:iload_3
[if !supportLists]47. [endif] // 24 50:bipush 10
[if !supportLists]48. [endif] // 25 52:icmplt 16
[if !supportLists]49. [endif] System.out.println(s);
[if !supportLists]50. [endif] // 26 55:getstatic #49
[if !supportLists]51. [endif] // 27 58:aload_1
[if !supportLists]52. [endif] // 28 59:invokevirtual #55
[if !supportLists]53. [endif] // 29 62:return
[if !supportLists]54. [endif] }
[if !supportLists]55. [endif]}
你們能夠看到,雖然編譯器將"+"轉換成了StringBuilder,但建立StringBuilder對象的位置卻在for語句內部。這就意味着每執行一次循環,就會建立一個StringBuilder對象(對於本例來講,是建立了10個StringBuilder對象),雖然Java有垃圾回收器,但這個回收器的工做時間是不定的。若是不斷產生這樣的垃圾,那麼仍然會佔用大量的資源。解決這個問題的方法就是在程序中直接使用StringBuilder來鏈接字符串,代碼以下:
[if !supportLists]1. [endif]package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]import java.util.*;
[if !supportLists]4. [endif]
[if !supportLists]5. [endif]public class TestStringBuilder
[if !supportLists]6. [endif]{
[if !supportLists]7. [endif] public static void main(String[] args)
[if !supportLists]8. [endif] {
[if !supportLists]9. [endif] String s = "";
[if !supportLists]10. [endif] Random rand = new Random();
[if !supportLists]11. [endif] StringBuilder result = new StringBuilder();
[if !supportLists]12. [endif] for (int i = 0; i < 10; i++)
[if !supportLists]13. [endif] {
[if !supportLists]14. [endif] result.append(rand.nextInt(1000));
[if !supportLists]15. [endif] result.append(" ");
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif] System.out.println(result.toString());
[if !supportLists]18. [endif] }
[if !supportLists]19. [endif]}
上面代碼反編譯後的結果以下:
[if !supportLists]1. [endif]20.package string;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]import java.io.PrintStream;
[if !supportLists]4. [endif]import java.util.Random;
[if !supportLists]5. [endif]
[if !supportLists]6. [endif]public class TestStringBuilder
[if !supportLists]7. [endif]{
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] public TestStringBuilder()
[if !supportLists]10. [endif] {
[if !supportLists]11. [endif] // 0 0:aload_0
[if !supportLists]12. [endif] // 1 1:invokespecial #8
[if !supportLists]13. [endif] // 2 4:return
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] public static void main(String args[])
[if !supportLists]17. [endif] {
[if !supportLists]18. [endif] String s = "";
[if !supportLists]19. [endif] // 0 0:ldc1 #16
[if !supportLists]20. [endif] // 1 2:astore_1
[if !supportLists]21. [endif] Random rand = new Random();
[if !supportLists]22. [endif] // 2 3:new #18
[if !supportLists]23. [endif] // 3 6:dup
[if !supportLists]24. [endif] // 4 7:invokespecial #20
[if !supportLists]25. [endif] // 5 10:astore_2
[if !supportLists]26. [endif] StringBuilder result = new StringBuilder();
[if !supportLists]27. [endif] // 6 11:new #21
[if !supportLists]28. [endif] // 7 14:dup
[if !supportLists]29. [endif] // 8 15:invokespecial #23
[if !supportLists]30. [endif] // 9 18:astore_3
[if !supportLists]31. [endif] for(int i = 0; i < 10; i++)
[if !supportLists]32. [endif] //* 10 19:iconst_0
[if !supportLists]33. [endif] //* 11 20:istore 4
[if !supportLists]34. [endif] //* 12 22:goto 47
[if !supportLists]35. [endif] {
[if !supportLists]36. [endif] result.append(rand.nextInt(1000));
[if !supportLists]37. [endif] // 13 25:aload_3
[if !supportLists]38. [endif] // 14 26:aload_2
[if !supportLists]39. [endif] // 15 27:sipush 1000
[if !supportLists]40. [endif] // 16 30:invokevirtual #24
[if !supportLists]41. [endif] // 17 33:invokevirtual #28
[if !supportLists]42. [endif] // 18 36:pop
[if !supportLists]43. [endif] result.append(" ");
[if !supportLists]44. [endif] // 19 37:aload_3
[if !supportLists]45. [endif] // 20 38:ldc1 #32
[if !supportLists]46. [endif] // 21 40:invokevirtual #34
[if !supportLists]47. [endif] // 22 43:pop
[if !supportLists]48. [endif] }
[if !supportLists]49. [endif]
[if !supportLists]50. [endif] // 23 44:iinc 4 1
[if !supportLists]51. [endif] // 24 47:iload 4
[if !supportLists]52. [endif] // 25 49:bipush 10
[if !supportLists]53. [endif] // 26 51:icmplt 25
[if !supportLists]54. [endif] System.out.println(result.toString());
[if !supportLists]55. [endif] // 27 54:getstatic #37
[if !supportLists]56. [endif] // 28 57:aload_3
[if !supportLists]57. [endif] // 29 58:invokevirtual #43
[if !supportLists]58. [endif] // 30 61:invokevirtual #47
[if !supportLists]59. [endif] // 31 64:return
[if !supportLists]60. [endif] }
[if !supportLists]61. [endif]}
從上面的反編譯結果能夠看出,建立StringBuilder的代碼被放在了for語句外。雖然這樣處理在源程序中看起來複雜,但卻換來了更高的效率,同時消耗的資源也更少了。
在使用StringBuilder時要注意,儘可能不要"+"和StringBuilder混着用,不然會建立更多的StringBuilder對象,以下面代碼所:
for (int i = 0; i < 10; i++) { result.append(rand.nextInt(1000)); result.append(" "); }
改爲以下形式:
for (int i = 0; i < 10; i++){ result.append(rand.nextInt(1000) + " ");}
則反編譯後的結果以下:
for(int i = 0; i < 10; i++) //* 10 19:iconst_0 //* 11 20:istore 4 //* 12 22:goto 65 { result.append((new StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString()); // 13 25:aload_3 // 14 26:new #21 // 15 29:dup
從上面的代碼能夠看出,Java編譯器將"+"編譯成了StringBuilder,這樣for語句每循環一次,又建立了一個StringBuilder對象。 若是將上面的代碼在JDK1.4下編譯,必須將StringBuilder改成StringBuffer,而JDK1.4將"+"轉換爲StringBuffer(由於JDK1.4並無提供StringBuilder類)。StringBuffer和StringBuilder的功能基本同樣,只是StringBuffer是線程安全的,而StringBuilder不是線程安全的。所以,StringBuilder的效率會更高。
[if !supportLists]6. [endif]請說出下面程序的輸出(2017-11-14-wl)
[if !supportLists]1. [endif]class StringEqualTest {
[if !supportLists]2. [endif] public static void main(String[] args) {
[if !supportLists]3. [endif] String s1 = "Programming";
[if !supportLists]4. [endif] String s2 = new String("Programming");
[if !supportLists]5. [endif] String s3 = "Program";
[if !supportLists]6. [endif] String s4 = "ming";
[if !supportLists]7. [endif] String s5 = "Program" + "ming";
[if !supportLists]8. [endif] String s6 = s3 + s4;
[if !supportLists]9. [endif] System.out.println(s1 == s2); //false
[if !supportLists]10. [endif] System.out.println(s1 == s5); //true
[if !supportLists]11. [endif] System.out.println(s1 == s6); //false
[if !supportLists]12. [endif] System.out.println(s1 == s6.intern()); //true
[if !supportLists]13. [endif] System.out.println(s2 == s2.intern()); //false
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
補充:解答上面的面試題須要知道以下兩個知識點:
1. String對象的intern()方法會獲得字符串對象在常量池中對應的版本的引用(若是常量池中有一個字符串與String 對象的 equals結果是 true),若是常量池中沒有對應的字符串,則該字符串將被添加到常量池中,而後返回常量池中字符串的引用;
2. 字符串的+操做其本質是建立了StringBuilder 對象進行 append 操做,而後將拼接後的 StringBuilder 對象用 toString 方法處理成 String 對象,這一點能夠用 javap -c StringEqualTest.class 命令得到 class 文件對應的 JVM 字節碼指令就能夠看出來。
[if !supportLists]7. [endif]Java中的日期和時間(2017-11-19-wl)
7.1如何取得年月日、小時分鐘秒?(2017-11-19-wl)
[if !supportLists]1. [endif]public class DateTimeTest {
[if !supportLists]2. [endif] public static void main(String[] args) {
[if !supportLists]3. [endif] Calendar cal = Calendar.getInstance();
[if !supportLists]4. [endif] System.out.println(cal.get(Calendar.YEAR));
[if !supportLists]5. [endif] System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
[if !supportLists]6. [endif] System.out.println(cal.get(Calendar.DATE));
[if !supportLists]7. [endif] System.out.println(cal.get(Calendar.HOUR_OF_DAY));
[if !supportLists]8. [endif] System.out.println(cal.get(Calendar.MINUTE));
[if !supportLists]9. [endif] System.out.println(cal.get(Calendar.SECOND));
[if !supportLists]10. [endif] // Java 8
[if !supportLists]11. [endif] LocalDateTime dt = LocalDateTime.now();
[if !supportLists]12. [endif] System.out.println(dt.getYear());
[if !supportLists]13. [endif] System.out.println(dt.getMonthValue()); // 1 - 12
[if !supportLists]14. [endif] System.out.println(dt.getDayOfMonth());
[if !supportLists]15. [endif] System.out.println(dt.getHour());
[if !supportLists]16. [endif] System.out.println(dt.getMinute());
[if !supportLists]17. [endif] System.out.println(dt.getSecond());
[if !supportLists]18. [endif] }
[if !supportLists]19. [endif]}
7.2如何取得從1970年1月1日0時0分0 秒到如今的毫秒數?(2017-11-19-wl)
[if !supportLists]1. [endif]Calendar.getInstance().getTimeInMillis(); //第一種方式
[if !supportLists]2. [endif]System.currentTimeMillis(); //第二種方式
[if !supportLists]3. [endif]// Java 8
[if !supportLists]4. [endif]Clock.systemDefaultZone().millis();
7.3如何取得某月的最後一天?(2017-11-19-wl)
[if !supportLists]1. [endif]//獲取當前月第一天:
[if !supportLists]2. [endif]Calendar c = Calendar.getInstance();
[if !supportLists]3. [endif]c.add(Calendar.MONTH, 0);
[if !supportLists]4. [endif]c.set(Calendar.DAY_OF_MONTH,1);//設置爲1號,當前日期既爲本月第一天
[if !supportLists]5. [endif]String first = format.format(c.getTime());
[if !supportLists]6. [endif]System.out.println("===============first:"+first);
[if !supportLists]7. [endif]
[if !supportLists]8. [endif]//獲取當前月最後一天
[if !supportLists]9. [endif] Calendar ca = Calendar.getInstance();
[if !supportLists]10. [endif]ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));
[if !supportLists]11. [endif]String last = format.format(ca.getTime());
[if !supportLists]12. [endif]System.out.println("===============last:"+last);
[if !supportLists]13. [endif]
[if !supportLists]14. [endif]//Java 8
[if !supportLists]15. [endif]LocalDate today = LocalDate.now();
[if !supportLists]16. [endif]//本月的第一天
[if !supportLists]17. [endif]LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);
[if !supportLists]18. [endif]//本月的最後一天
[if !supportLists]19. [endif]LocalDate lastDay =today.with(TemporalAdjusters.lastDayOfMonth());
[if !supportLists]20. [endif]System.out.println("本月的第一天"+firstday);
[if !supportLists]21. [endif]System.out.println("本月的最後一天"+lastDay);
7.4如何格式化日期?(2017-11-19-wl)
1)Java.text.DataFormat 的子類(如 SimpleDateFormat 類)中的 format(Date)方法可將日期格式化。
2)Java 8 中能夠用 java.time.format.DateTimeFormatter來格式化時間日期,代碼以下所示:
[if !supportLists]1. [endif]import java.text.SimpleDateFormat;
[if !supportLists]2. [endif]import java.time.LocalDate;
[if !supportLists]3. [endif]import java.time.format.DateTimeFormatter;
[if !supportLists]4. [endif]import java.util.Date;
[if !supportLists]5. [endif]class DateFormatTest {
[if !supportLists]6. [endif]
[if !supportLists]7. [endif] public static void main(String[] args) {
[if !supportLists]8. [endif] SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
[if !supportLists]9. [endif] Date date1 = new Date();
[if !supportLists]10. [endif] System.out.println(oldFormatter.format(date1));
[if !supportLists]11. [endif]
[if !supportLists]12. [endif] // Java 8
[if !supportLists]13. [endif] DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
[if !supportLists]14. [endif] LocalDate date2 = LocalDate.now();
[if !supportLists]15. [endif] System.out.println(date2.format(newFormatter));
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif]}
補充:Java的時間日期API一直以來都是被詬病的東西,爲了解決這一問題,Java 8中引入了新的時間日期API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等類,這些的類的設計都使用了不變模式,所以是線程安全的設計。
7.5打印昨天的當前時刻? (2017-11-19-wl)
[if !supportLists]1. [endif]import java.util.Calendar;
[if !supportLists]2. [endif]class YesterdayCurrent {
[if !supportLists]3. [endif] public static void main(String[] args){
[if !supportLists]4. [endif] Calendar cal = Calendar.getInstance();
[if !supportLists]5. [endif] cal.add(Calendar.DATE, -1);
[if !supportLists]6. [endif] System.out.println(cal.getTime());
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif]}
[if !supportLists]9. [endif]
[if !supportLists]10. [endif]
[if !supportLists]11. [endif]//java-8
[if !supportLists]12. [endif]import java.time.LocalDateTime;
[if !supportLists]13. [endif]class YesterdayCurrent {
[if !supportLists]14. [endif] public static void main(String[] args) {
[if !supportLists]15. [endif] LocalDateTime today = LocalDateTime.now();
[if !supportLists]16. [endif] LocalDateTime yesterday = today.minusDays(1);
[if !supportLists]17. [endif] System.out.println(yesterday);
[if !supportLists]18. [endif] }
[if !supportLists]19. [endif]}
7.6 Java8的日期特性? (2017-12-3-wl)
Java 8日期/時間特性
Java 8日期/時間API是JSR-310的實現,它的實現目標是克服舊的日期時間實現中全部的缺陷,新的日期/時間API的一些設計原則是:
[if !supportLists]l [endif]不變性:新的日期/時間API中,全部的類都是不可變的,這對多線程環境有好處。
[if !supportLists]l [endif]關注點分離:新的API將人可讀的日期時間和機器時間(unix timestamp)明確分離,它爲日期(Date)、時間(Time)、日期時間(DateTime)、時間戳(unix timestamp)以及時區定義了不一樣的類。
[if !supportLists]l [endif]清晰:在全部的類中,方法都被明肯定義用以完成相同的行爲。舉個例子,要拿到當前實例咱們能夠使用now()方法,在全部的類中都定義了format()和parse()方法,而不是像之前那樣專門有一個獨立的類。爲了更好的處理問題,全部的類都使用了工廠模式和策略模式,一旦你使用了其中某個類的方法,與其餘類協同工做並不困難。
[if !supportLists]l [endif]實用操做:全部新的日期/時間API類都實現了一系列方法用以完成通用的任務,如:加、減、格式化、解析、從日期/時間中提取單獨部分,等等。
[if !supportLists]l [endif]可擴展性:新的日期/時間API是工做在ISO-8601日曆系統上的,但咱們也能夠將其應用在非ISO的日曆上。
Java 8日期/時間API包解釋
[if !supportLists]l [endif]java.time包:這是新的Java日期/時間API的基礎包,全部的主要基礎類都是這個包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。全部這些類都是不可變的和線程安全的,在絕大多數狀況下,這些類可以有效地處理一些公共的需求。
[if !supportLists]l [endif]java.time.chrono包:這個包爲非ISO的日曆系統定義了一些泛化的API,咱們能夠擴展AbstractChronology類來建立本身的日曆系統。
[if !supportLists]l [endif]java.time.format包:這個包包含可以格式化和解析日期時間對象的類,在絕大多數狀況下,咱們不該該直接使用它們,由於java.time包中相應的類已經提供了格式化和解析的方法。
[if !supportLists]l [endif]java.time.temporal包:這個包包含一些時態對象,咱們能夠用其找出關於日期/時間對象的某個特定日期或時間,好比說,能夠找到某月的第一天或最後一天。你能夠很是容易地認出這些方法,由於它們都具備「withXXX」的格式。
[if !supportLists]l [endif]java.time.zone包:這個包包含支持不一樣時區以及相關規則的類。
Java 8日期/時間經常使用API
1.java.time.LocalDate
LocalDate是一個不可變的類,它表示默認格式(yyyy-MM-dd)的日期,咱們能夠使用now()方法獲得當前時間,也能夠提供輸入年份、月份和日期的輸入參數來建立一個LocalDate實例。該類爲now()方法提供了重載方法,咱們能夠傳入ZoneId來得到指定時區的日期。該類提供與java.sql.Date相同的功能,對於如何使用該類,咱們來看一個簡單的例子。
package com.journaldev.java8.time;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;
/**
* LocalDate Examples
* @author pankaj
*
*/
public class LocalDateExample {
public static void main(String[] args) {
//Current Date
LocalDate today = LocalDate.now();
System.out.println("Current Date="+today);
//Creating LocalDate by providing input arguments
LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
System.out.println("Specific Date="+firstDay_2014);
//Try creating date by providing invalid inputs
//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
//Exception in thread "main" java.time.DateTimeException:
//Invalid date 'February 29' as '2014' is not a leap year
//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
System.out.println("Current Date in IST="+todayKolkata);
//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
//Getting date from the base date i.e 01/01/1970
LocalDate dateFromBase = LocalDate.ofEpochDay(365);
System.out.println("365th day from base date= "+dateFromBase);
LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
System.out.println("100th day of 2014="+hundredDay2014);
}
}
輸出:
Current Date=2014-04-28
Specific Date=2014-01-01
Current Date in IST=2014-04-29
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10
2.java.time.LocalTime
LocalTime是一個不可變的類,它的實例表明一個符合人類可讀格式的時間,默認格式是hh:mm:ss.zzz。像LocalDate同樣,該類也提供了時區支持,同時也能夠傳入小時、分鐘和秒等輸入參數建立實例,咱們來看一個簡單的程序,演示該類的使用方法。
package com.journaldev.java8.time;
import java.time.LocalTime;
import java.time.ZoneId;
/**
* LocalTime Examples
*/
public class LocalTimeExample {
public static void main(String[] args) {
//Current Time
LocalTime time = LocalTime.now();
System.out.println("Current Time="+time);
//Creating LocalTime by providing input arguments
LocalTime specificTime = LocalTime.of(12,20,25,40);
System.out.println("Specific Time of Day="+specificTime);
//Try creating time by providing invalid inputs
//LocalTime invalidTime = LocalTime.of(25,20);
//Exception in thread "main" java.time.DateTimeException:
//Invalid value for HourOfDay (valid values 0 - 23): 25
//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
System.out.println("Current Time in IST="+timeKolkata);
//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
//LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));
//Getting date from the base date i.e 01/01/1970
LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
System.out.println("10000th second time= "+specificSecondTime);
}
}
輸出:
Current Time=15:51:45.240
Specific Time of Day=12:20:25.000000040
Current Time in IST=04:21:45.276
10000th second time= 02:46:40
3. java.time.LocalDateTime
LocalDateTime是一個不可變的日期-時間對象,它表示一組日期-時間,默認格式是yyyy-MM-dd-HH-mm-ss.zzz。它提供了一個工廠方法,接收LocalDate和LocalTime輸入參數,建立LocalDateTime實例。咱們來看一個簡單的例子。
package com.journaldev.java8.time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
public class LocalDateTimeExample {
public static void main(String[] args) {
//Current Date
LocalDateTime today = LocalDateTime.now();
System.out.println("Current DateTime="+today);
//Current Date using LocalDate and LocalTime
today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
System.out.println("Current DateTime="+today);
//Creating LocalDateTime by providing input arguments
LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
System.out.println("Specific Date="+specificDate);
//Try creating date by providing invalid inputs
//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
//Exception in thread "main" java.time.DateTimeException:
//Invalid value for HourOfDay (valid values 0 - 23): 25
//Current date in "Asia/Kolkata", you can get it from ZoneId javadoc
LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
System.out.println("Current Date in IST="+todayKolkata);
//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
//LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));
//Getting date from the base date i.e 01/01/1970
LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
System.out.println("10000th second time from 01/01/1970= "+dateFromBase);
}
}
輸出:
Current DateTime=2014-04-28T16:00:49.455
Current DateTime=2014-04-28T16:00:49.493
Specific Date=2014-01-01T10:10:30
Current Date in IST=2014-04-29T04:30:49.493
10000th second time from 01/01/1970= 1970-01-01T02:46:40
在全部這三個例子中,咱們已經看到若是咱們提供了無效的參數去建立日期/時間,那麼系統會拋出java.time.DateTimeException,這是一種運行時異常,咱們並不須要顯式地捕獲它。
同時咱們也看到,可以經過傳入ZoneId獲得日期/時間數據,你能夠從它的Javadoc中獲得支持的Zoneid的列表,當運行以上類時,能夠獲得以上輸出。
4. java.time.Instant
Instant類是用在機器可讀的時間格式上的,它以Unix時間戳的形式存儲日期時間,咱們來看一個簡單的程序
package com.journaldev.java8.time;
import java.time.Duration;
import java.time.Instant;
public class InstantExample {
public static void main(String[] args) {
//Current timestamp
Instant timestamp = Instant.now();
System.out.println("Current Timestamp = "+timestamp);
//Instant from timestamp
Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
System.out.println("Specific Time = "+specificTime);
//Duration example
Duration thirtyDay = Duration.ofDays(30);
System.out.println(thirtyDay);
}
}
輸出:
Current Timestamp = 2014-04-28T23:20:08.489Z
Specific Time = 2014-04-28T23:20:08.489Z
PT720H
5.日期API工具
咱們早些時候提到過,大多很多天期/時間API類都實現了一系列工具方法,如:加/減天數、週數、月份數,等等。還有其餘的工具方法可以使用TemporalAdjuster調整日期,並計算兩個日期間的週期。
package com.journaldev.java8.time;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;
public class DateAPIUtilities {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
//Get the Year, check if it's leap year
System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
//Compare two LocalDate for before and after
System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));
//Create LocalDateTime from LocalDate
System.out.println("Current Time="+today.atTime(LocalTime.now()));
//plus and minus operations
System.out.println("10 days after today will be "+today.plusDays(10));
System.out.println("3 weeks after today will be "+today.plusWeeks(3));
System.out.println("20 months after today will be "+today.plusMonths(20));
System.out.println("10 days before today will be "+today.minusDays(10));
System.out.println("3 weeks before today will be "+today.minusWeeks(3));
System.out.println("20 months before today will be "+today.minusMonths(20));
//Temporal adjusters for adjusting the dates
System.out.println("First date of this month= "+today.
with(TemporalAdjusters.firstDayOfMonth()));
LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
System.out.println("Last date of this year= "+lastDayOfYear);
Period period = today.until(lastDayOfYear);
System.out.println("Period Format= "+period);
System.out.println("Months remaining in the year= "+period.getMonths());
}
}
輸出:
Year 2014 is Leap Year? false
Today is before 01/01/2015? true
Current Time=2014-04-28T16:23:53.154
10 days after today will be 2014-05-08
3 weeks after today will be 2014-05-19
20 months after today will be 2015-12-28
10 days before today will be 2014-04-18
3 weeks before today will be 2014-04-07
20 months before today will be 2012-08-28
First date of this month= 2014-04-01
Last date of this year= 2014-12-31
Period Format= P8M3D
Months remaining in the year= 8
6.解析和格式化
將一個日期格式轉換爲不一樣的格式,以後再解析一個字符串,獲得日期時間對象,這些都是很常見的。咱們來看一下簡單的例子。
package com.journaldev.java8.time;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateParseFormatExample {
public static void main(String[] args) {
//Format examples
LocalDate date = LocalDate.now();
//default format
System.out.println("Default format of LocalDate="+date);
//specific format
System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
LocalDateTime dateTime = LocalDateTime.now();
//default format
System.out.println("Default format of LocalDateTime="+dateTime);
//specific format
System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
Instant timestamp = Instant.now();
//default format
System.out.println("Default format of Instant="+timestamp);
//Parse examples
LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",
DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
System.out.println("Default format after parsing = "+dt);
}
}
輸出:
Default format of LocalDate=2014-04-28
28::Apr::2014
20140428
Default format of LocalDateTime=2014-04-28T16:25:49.341
28::Apr::2014 16::25::49
20140428
Default format of Instant=2014-04-28T23:25:49.342Z
Default format after parsing = 2014-04-27T21:39:48
7.舊的日期時間支持
舊的日期/時間類已經在幾乎全部的應用程序中使用,所以作到向下兼容是必須的。這也是爲何會有若干工具方法幫助咱們將舊的類轉換爲新的類,反之亦然。咱們來看一下簡單的例子。
package com.journaldev.java8.time;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
public class DateAPILegacySupport {
public static void main(String[] args) {
//Date to Instant
Instant timestamp = new Date().toInstant();
//Now we can convert Instant to LocalDateTime or other similar classes
LocalDateTime date = LocalDateTime.ofInstant(timestamp,
ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
System.out.println("Date = "+date);
//Calendar to Instant
Instant time = Calendar.getInstance().toInstant();
System.out.println(time);
//TimeZone to ZoneId
ZoneId defaultZone = TimeZone.getDefault().toZoneId();
System.out.println(defaultZone);
//ZonedDateTime from specific Calendar
ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
System.out.println(gregorianCalendarDateTime);
//Date API to Legacy classes
Date dt = Date.from(Instant.now());
System.out.println(dt);
TimeZone tz = TimeZone.getTimeZone(defaultZone);
System.out.println(tz);
GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
System.out.println(gc);
}
}
輸出:
Date = 2014-04-28T16:28:54.340
2014-04-28T23:28:54.395Z
America/Los_Angeles
2014-04-28T16:28:54.404-07:00[America/Los_Angeles]
Mon Apr 28 16:28:54 PDT 2014
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
補充:咱們能夠看到,舊的TimeZone和GregorianCalendar類的toString()方法太囉嗦了,一點都不友好。
7.7 Java8以前的日期和時間使用的槽點 (2017-11-19-wl)
Tiago Fernandez 作過一次投票,選舉最爛的 JAVA API,排第一的 EJB2.X,第二的就是日期 API(Date和Calender)
[if !supportLists]1. [endif]槽點一
最開始的時候,Date 既要承載日期信息,又要作日期之間的轉換,還要作不一樣日期格式的顯示,職責較繁雜(不懂單一職責,你媽媽知道嗎?純屬惡搞~哈哈)
後來從JDK 1.1 開始,這三項職責分開了:
· 1)使用Calendar 類實現日期和時間字段之間轉換;
· 2)使用DateFormat 類來格式化和分析日期字符串;
· 3)而Date 只用來承載日期和時間信息。
原有Date 中的相應方法已廢棄。不過,不管是 Date,仍是 Calendar,都用着太不方便了,這是 API 沒有設計好的地方。
[if !supportLists]2. [endif]槽點二
坑爹的year和month。
咱們看下面的代碼:
[if !supportLists]1. [endif]Date date = new Date(2012,1,1);
[if !supportLists]2. [endif]System.out.println(date);
輸出Thu Feb 01 00:00:00 CST 3912
觀察輸出結果,year 是 2012+1900,而 month,月份參數我不是給了 1 嗎?怎麼輸出二月(Feb)了?
應該曾有人告訴你,若是你要設置日期,應該使用java.util.Calendar,像這樣...
[if !supportLists]1. [endif]Calendar calendar = Calendar.getInstance();
[if !supportLists]2. [endif]calendar.set(2013, 8, 2);
這樣寫又不對了,calendar 的 month 也是從 0 開始的,表達 8 月份應該用 7 這個數字,要麼就乾脆用枚舉
[if !supportLists]1. [endif]calendar.set(2013, Calendar.AUGUST, 2);
注意上面的代碼,Calendar 年份的傳值不須要減去 1900(固然月份的定義和 Date 仍是同樣),這種不一致真是讓人抓狂!有些人可能知道,Calendar 相關的 API 是 IBM 捐出去的,因此才致使不一致。
[if !supportLists]3. [endif]槽點三
java.util.Date 與 java.util.Calendar 中的全部屬性都是可變的
下面的代碼,計算兩個日期之間的天數....
[if !supportLists]1. [endif]public static void main(String[] args) {
[if !supportLists]2. [endif] Calendar birth = Calendar.getInstance();
[if !supportLists]3. [endif] birth.set(1975, Calendar.MAY, 26);
[if !supportLists]4. [endif] Calendar now = Calendar.getInstance();
[if !supportLists]5. [endif] System.out.println(daysBetween(birth, now));
[if !supportLists]6. [endif]System.out.println(daysBetween(birth, now)); //顯示 0?
[if !supportLists]7. [endif]}
[if !supportLists]8. [endif]
[if !supportLists]9. [endif]public static long daysBetween(Calendar begin, Calendar end) {
[if !supportLists]10. [endif] long daysBetween = 0;
[if !supportLists]11. [endif] while(begin.before(end)) {
[if !supportLists]12. [endif] begin.add(Calendar.DAY_OF_MONTH, 1);
[if !supportLists]13. [endif] daysBetween++;
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif] return daysBetween;
[if !supportLists]16. [endif]}
daysBetween 有點問題,若是連續計算兩個 Date 實例的話,第二次會取得 0,由於 Calendar 狀態是可變的,考慮到重複計算的場合,最好複製一個新的 Calendar
[if !supportLists]1. [endif]public static long daysBetween(Calendar begin, Calendar end) {
[if !supportLists]2. [endif]Calendar calendar = (Calendar) begin.clone(); //複製
[if !supportLists]3. [endif] long daysBetween = 0;
[if !supportLists]4. [endif] while(calendar.before(end)) {
[if !supportLists]5. [endif] calendar.add(Calendar.DAY_OF_MONTH, 1);
[if !supportLists]6. [endif] daysBetween++;
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] return daysBetween;
[if !supportLists]9. [endif]}
以上種種,致使目前有些第三方的java 日期庫誕生,好比普遍使用的 JODA-TIME,還有 Date4j 等,雖然第三方庫已經足3 / 8夠強大,好用,但仍是有兼容問題的,好比標準的 JSF 日期轉換器與 joda-time API 就不兼容,你須要編寫本身的轉換器,因此標準的 API 仍是必須的,因而就有了JSR310。
7.8 Java8日期實現JSR310規範 (2017-11-23-wl)
[if !supportLists]1. [endif]JSR310介紹
JSR 310 實際上有兩個日期概念。第一個是 Instant,它大體對應於 java.util.Date 類,由於它表明了一個肯定的時間點,即相對於標準 Java 紀元(1970 年 1 月 1 日)的偏移量;但與 java.util.Date 類不一樣的是其精確到了納秒級別。
第二個對應於人類自身的觀念,好比LocalDate 和 LocalTime。他們表明了通常的時區概念,要麼是日期(不包含時間),要麼是時間(不包含日期),相似於 java.sql 的表示方式。此外,還有一個 MonthDay,它能夠存儲某人的生日(不包含年份)。每一個類都在內部存儲正確的數據而不是像 java.util.Date 那樣利用午夜 12 點來區分日期,利用 1970-01-01 來表示時間。
目前Java8 已經實現了 JSR310 的所有內容。新增了 java.time 包定義的類表示了日期-時間概念的規則,包括 instants,durations, dates, times, time-zones and periods。這些都是基於 ISO 日曆系統,它又是遵循 Gregorian 規則的。最重要的一點是值不可變,且線程安全,經過下面一張圖,咱們快速看下 java.time 包下的一些主要的類的值的格式,方便理解。
[if !supportLists]2. [endif]Java8方法概覽
java.time包下的方法概覽
方法名說明
Of靜態工廠方法
parse靜態工廠方法,關注於解析
get獲取某些東西的值
is檢查某些東西的是不是 true
with不可變的 setter 等價物
plus加一些量到某個對象
minus從某個對象減去一些量
to轉換到另外一個類型
at把這個對象與另外一個對象組合起來
與舊的API相比
[if !supportLists]3. [endif]簡單實用java.time的API實用
[if !supportLists]1. [endif]public class TimeIntroduction {
[if !supportLists]2. [endif] public static void testClock() throws InterruptedException {
[if !supportLists]3. [endif]//時鐘提供給咱們用於訪問某個特定 時區的 瞬時時間、日期 和 時間的。
[if !supportLists]4. [endif]Clock c1 = Clock.systemUTC(); //系統默認 UTC 時鐘(當前瞬時時間 System.currentTimeMillis())
[if !supportLists]5. [endif]System.out.println(c1.millis()); //每次調用將返回當前瞬時時間(UTC)
[if !supportLists]6. [endif]Clock c2 = Clock.systemDefaultZone(); //系統默認時區時鐘(當前瞬時時間)
[if !supportLists]7. [endif]Clock c31 = Clock.system(ZoneId.of("Europe/Paris")); //巴黎時區
[if !supportLists]8. [endif]System.out.println(c31.millis()); //每次調用將返回當前瞬時時間(UTC)
[if !supportLists]9. [endif]Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));//上海時區
[if !supportLists]10. [endif]System.out.println(c32.millis());//每次調用將返回當前瞬時時間(UTC)
[if !supportLists]11. [endif]Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));//固定上海時區時鐘
[if !supportLists]12. [endif] System.out.println(c4.millis());
[if !supportLists]13. [endif] Thread.sleep(1000);
[if !supportLists]14. [endif]
[if !supportLists]15. [endif]System.out.println(c4.millis()); //不變 即時鐘時鐘在那一個點不動
[if !supportLists]16. [endif]Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相對於系統默認時鐘兩秒的時鐘
[if !supportLists]17. [endif] System.out.println(c1.millis());
[if !supportLists]18. [endif] System.out.println(c5.millis());
[if !supportLists]19. [endif] }
[if !supportLists]20. [endif] public static void testInstant() {
[if !supportLists]21. [endif]//瞬時時間 至關於之前的 System.currentTimeMillis()
[if !supportLists]22. [endif] Instant instant1 = Instant.now();
[if !supportLists]23. [endif]System.out.println(instant1.getEpochSecond());//精確到秒 獲得相對於 1970-01-01 00:00:00
[if !supportLists]24. [endif]UTC的一個時間
[if !supportLists]25. [endif]System.out.println(instant1.toEpochMilli()); //精確到毫秒
[if !supportLists]26. [endif]Clock clock1 = Clock.systemUTC(); //獲取系統 UTC 默認時鐘
[if !supportLists]27. [endif]Instant instant2 = Instant.now(clock1);//獲得時鐘的瞬時時間
[if !supportLists]28. [endif] System.out.println(instant2.toEpochMilli());
[if !supportLists]29. [endif]Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬時時間時鐘
[if !supportLists]30. [endif]Instant instant3 = Instant.now(clock2);//獲得時鐘的瞬時時間
[if !supportLists]31. [endif] System.out.println(instant3.toEpochMilli());//equals instant1
[if !supportLists]32. [endif] }
[if !supportLists]33. [endif] public static void testLocalDateTime() {
[if !supportLists]34. [endif]//使用默認時區時鐘瞬時時間建立 Clock.systemDefaultZone() -->即相對於 ZoneId.systemDefault()
[if !supportLists]35. [endif]默認時區
[if !supportLists]36. [endif] LocalDateTime now = LocalDateTime.now();
[if !supportLists]37. [endif] System.out.println(now);
[if !supportLists]38. [endif]//自定義時區
[if !supportLists]39. [endif] LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));
[if !supportLists]40. [endif]System.out.println(now2);//會以相應的時區顯示日期
[if !supportLists]41. [endif]//自定義時鐘
[if !supportLists]42. [endif] Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));
[if !supportLists]43. [endif] LocalDateTime now3 = LocalDateTime.now(clock);
[if !supportLists]44. [endif]System.out.println(now3);//會以相應的時區顯示日期
[if !supportLists]45. [endif]//不須要寫什麼相對時間 如 java.util.Date 年是相對於 1900 月是從 0 開始
[if !supportLists]46. [endif] //2013-12-31 23:59
[if !supportLists]47. [endif] LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);
[if !supportLists]48. [endif]//年月日 時分秒 納秒
[if !supportLists]49. [endif] LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);
[if !supportLists]50. [endif]//使用瞬時時間 + 時區
[if !supportLists]51. [endif] Instant instant = Instant.now();
[if !supportLists]52. [endif] LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());
[if !supportLists]53. [endif] System.out.println(d3);
[if !supportLists]54. [endif]//解析 String--->LocalDateTime
[if !supportLists]55. [endif] LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");
[if !supportLists]56. [endif] System.out.println(d4);
[if !supportLists]57. [endif]LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");//999毫秒 等價於
[if !supportLists]58. [endif]999000000納秒
[if !supportLists]59. [endif]
[if !supportLists]60. [endif] System.out.println(d5);
[if !supportLists]61. [endif]//使用 DateTimeFormatter API 解析 和 格式化
[if !supportLists]62. [endif] DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
[if !supportLists]63. [endif] LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);
[if !supportLists]64. [endif] System.out.println(formatter.format(d6));
[if !supportLists]65. [endif]//時間獲取
[if !supportLists]66. [endif] System.out.println(d6.getYear());
[if !supportLists]67. [endif] System.out.println(d6.getMonth());
[if !supportLists]68. [endif] System.out.println(d6.getDayOfYear());
[if !supportLists]69. [endif] System.out.println(d6.getDayOfMonth());
[if !supportLists]70. [endif] System.out.println(d6.getDayOfWeek());
[if !supportLists]71. [endif] System.out.println(d6.getHour());
[if !supportLists]72. [endif] System.out.println(d6.getMinute());
[if !supportLists]73. [endif] System.out.println(d6.getSecond());
[if !supportLists]74. [endif] System.out.println(d6.getNano());
[if !supportLists]75. [endif]//時間增減
[if !supportLists]76. [endif] LocalDateTime d7 = d6.minusDays(1);
[if !supportLists]77. [endif] LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);
[if !supportLists]78. [endif]//LocalDate即年月日 無時分秒
[if !supportLists]79. [endif]//LocalTime即時分秒 無年月日
[if !supportLists]80. [endif]//API和 LocalDateTime 相似就不演示了
[if !supportLists]81. [endif] }
[if !supportLists]82. [endif] public static void testZonedDateTime() {
[if !supportLists]83. [endif]//即帶有時區的 date-time 存儲納秒、時區和時差(避免與本地 date-time 歧義)。
[if !supportLists]84. [endif]//API和 LocalDateTime 相似,只是多了時差(如 2013-12-20T10:35:50.711+08:00[Asia/Shanghai])
[if !supportLists]85. [endif] ZonedDateTime now = ZonedDateTime.now();
[if !supportLists]86. [endif] System.out.println(now);
[if !supportLists]87. [endif] ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
[if !supportLists]88. [endif] System.out.println(now2);
[if !supportLists]89. [endif]//其餘的用法也是相似的 就不介紹了
[if !supportLists]90. [endif] ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");
[if !supportLists]91. [endif] System.out.println(z1);
[if !supportLists]92. [endif]}
[if !supportLists]93. [endif] public static void testDuration() {
[if !supportLists]94. [endif]//表示兩個瞬時時間的時間段
[if !supportLists]95. [endif] Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123),
[if !supportLists]96. [endif] Instant.now())
[if !supportLists]97. [endif];
[if !supportLists]98. [endif]//獲得相應的時差
[if !supportLists]99. [endif] System.out.println(d1.toDays());
[if !supportLists]100. [endif] System.out.println(d1.toHours());
[if !supportLists]101. [endif] System.out.println(d1.toMinutes());
[if !supportLists]102. [endif]
[if !supportLists]103. [endif] System.out.println(d1.toMillis());
[if !supportLists]104. [endif] System.out.println(d1.toNanos());
[if !supportLists]105. [endif]//1天時差 相似的還有如 ofHours()
[if !supportLists]106. [endif] Duration d2 = Duration.ofDays(1);
[if !supportLists]107. [endif] System.out.println(d2.toDays());
[if !supportLists]108. [endif]}
[if !supportLists]109. [endif] public static void testChronology() {
[if !supportLists]110. [endif]//提供對 java.util.Calendar 的替換,提供對年曆系統的支持
[if !supportLists]111. [endif] Chronology c = HijrahChronology.INSTANCE;
[if !supportLists]112. [endif] ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());
[if !supportLists]113. [endif] System.out.println(d);
[if !supportLists]114. [endif] }
[if !supportLists]115. [endif]/**
[if !supportLists]116. [endif]*新舊日期轉換
[if !supportLists]117. [endif]*/
[if !supportLists]118. [endif] public static void testNewOldDateConversion(){
[if !supportLists]119. [endif] Instant instant=new Date().toInstant();
[if !supportLists]120. [endif] Date date=Date.from(instant);
[if !supportLists]121. [endif] System.out.println(instant);
[if !supportLists]122. [endif] System.out.println(date);
[if !supportLists]123. [endif] }
[if !supportLists]124. [endif] public static void main(String[] args) throws InterruptedException {
[if !supportLists]125. [endif] testClock();
[if !supportLists]126. [endif] testInstant();
[if !supportLists]127. [endif] testLocalDateTime();
[if !supportLists]128. [endif] testZonedDateTime();
[if !supportLists]129. [endif] testDuration();
[if !supportLists]130. [endif] testChronology();
[if !supportLists]131. [endif] testNewOldDateConversion();
[if !supportLists]132. [endif] }
[if !supportLists]133. [endif]}
7.9 JSR310規範Joda-Time的區別 (2017-11-23-wl)
其實JSR310 的規範領導者 Stephen Colebourne,同時也是 Joda-Time 的建立者,JSR310 是在 Joda-Time 的基礎上創建的,參考了絕大部分的 API,但並非說 JSR310=JODA-Time,下面幾個比較明顯的區別是:
1. 最明顯的變化就是包名(從 org.joda.time 以及 java.time)
2. JSR310 不接受 NULL 值,Joda-Time 視 NULL 值爲 0
3. JSR310 的計算機相關的時間(Instant)和與人類相關的時間(DateTime)之間的差異變得更明顯
4. JSR310 全部拋出的異常都是 DateTimeException 的子類。雖然 DateTimeException 是一個 RuntimeException
7.10 總結 (2017-11-23-wl)
Java.timejava.util.Calendar 以及 Date
流暢的 API不流暢的 API
實例不可變實例可變
線程安全非線程安全
[if !supportLists]6、[endif]Java的數據類型
[if !supportLists]1. [endif]Java的基本數據類型都有哪些各佔幾個字節
以下表所示:
四類八種字節數數據表示範圍
整型
byte1-128~127
short2-32768~32767
int4-2147483648~2147483647
long8-263~263-1
浮點型float4-3.403E38~3.403E38
double8-1.798E308~1.798E308
字符型char2表示一個字符,如('a','A','0','家')
布爾型boolean1只有兩個值true與false
[if !supportLists]2. [endif]String是基本數據類型嗎?(2017-11-12-wl)
String是引用類型,底層用char數組實現的。
[if !supportLists]3. [endif]short s1 = 1; s1 = s1 + 1; 有錯嗎?short s1 = 1; s1 += 1有錯嗎;(2017-11-12-wl)
前者不正確,後者正確。對於short s1 = 1; s1 = s1 + 1;因爲1是 int 類型,所以 s1+1 運算結果也是 int 型,須要強制轉換類型才能賦值給short型。而 short s1 = 1; s1 += 1;能夠正確編譯,由於 s1+= 1;至關於 s1 = (short)(s1 + 1);其中有隱含的強制類型轉換。
[if !supportLists]4. [endif]int 和 和 Integer有什麼區別?(2017-11-12-wl)
Java是一個近乎純潔的面向對象編程語言,可是爲了編程的方便仍是引入了基本數據類型,爲了可以將這些基本數據類型當成對象操做,Java爲每個基本數據類型都引入了對應的包裝類型(wrapper class),int 的包裝類就是 Integer,從 Java 5 開始引入了自動裝箱/拆箱機制,使得兩者能夠相互轉換。
Java爲每一個原始類型提供了包裝類型:
- 原始類型: boolean,char,byte,short,int,long,float,double
- 包裝類型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
[if !supportLists]5. [endif]下面Integer類型的數值比較輸出的結果爲?(2017-11-12-wl)
若是不明就裏很容易認爲兩個輸出要麼都是true 要麼都是 false。首先須要注意的是 f一、f二、f三、f4 四個變量都是 Integer 對象引用,因此下面的==運算比較的不是值而是引用。裝箱的本質是什麼呢?當咱們給一個Integer 對象賦一個 int 值的時候,會調用 Integer 類的靜態方法 valueOf,若是看看 valueOf 的源代碼就知道發生了什麼。
源碼:
IntegerCache 是 Integer 的內部類,其代碼以下所示:
簡單的說,若是整型字面量的值在-128 到 127 之間,那麼不會 new 新的 Integer 對象,而是直接引用常量池中的 Integer 對象,因此上面的面試題中 f1==f2 的結果是 true,而 f3==f4 的結果是 false。
提醒:越是貌似簡單的面試題其中的玄機就越多,須要面試者有至關深厚的功力。
[if !supportLists]6. [endif]String類經常使用方法(2017-11-15-lyq)
[if !supportLists]7. [endif]String、StringBuffer、StringBuilder的區別?(2017-11-23-wzz)
(1)、可變不可變
String:字符串常量,在修改時不會改變自身;若修改,等於從新生成新的字符串對象。
StringBuffer:在修改時會改變對象自身,每次操做都是對StringBuffer對象自己進行修改,不是生成新的對象;使用場景:對字符串常常改變狀況下,主要方法:append(),insert()等。
[if !supportLists](2)[endif]、線程是否安全
String:對象定義後不可變,線程安全。
StringBuffer:是線程安全的(對調用方法加入同步鎖),執行效率較慢,適用於多線程下操做字符串緩衝區大量數據。
StringBuilder:是線程不安全的,適用於單線程下操做字符串緩衝區大量數據。
[if !supportLists](3)[endif]、共同點
StringBuilder與StringBuffer有公共父類AbstractStringBuilder(抽象類)。
StringBuilder、StringBuffer的方法都會調用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer會在方法上加synchronized關鍵字,進行同步。最後,若是程序不是多線程的,那麼使用StringBuilder效率高於StringBuffer。
[if !supportLists]8. [endif]數據類型之間的轉換(2017-11-23-wzz)
(1)、字符串如何轉基本數據類型?
調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)便可返回相應基本類型。
[if !supportLists](2)[endif]、基本數據類型如何轉字符串?
一種方法是將基本數據類型與空字符串(「」)鏈接(+)便可得到其所對應的字符串;另外一種方法是調用String 類中的valueOf()方法返回相應字符串。
[if !supportLists]7、[endif]Java的IO
[if !supportLists]1. [endif]Java中有幾種類型的流(2017-11-23-wzz)
按照流的方向:輸入流(inputStream)和輸出流(outputStream)。
按照實現功能分:節點流(能夠從或向一個特定的地方(節點)讀寫數據。如FileReader)和處理流(是對一個已存在的流的鏈接和封裝,經過所封裝的流的功能調用實現數據讀寫。如BufferedReader。處理流的構造方法老是要帶一個其餘的流對象作參數。一個流對象通過其餘流的屢次包裝,稱爲流的連接。)
按照處理數據的單位:字節流和字符流。字節流繼承於InputStream和OutputStream,字符流繼承於InputStreamReader 和OutputStreamWriter。
[if !supportLists]2. [endif]字節流如何轉爲字符流
字節輸入流轉字符輸入流經過InputStreamReader實現,該類的構造函數能夠傳入InputStream對象。
字節輸出流轉字符輸出流經過OutputStreamWriter實現,該類的構造函數能夠傳入OutputStream對象。
[if !supportLists]3. [endif]如何將一個java對象序列化到文件裏
在java中可以被序列化的類必須先實現Serializable接口,該接口沒有任何抽象方法只是起到一個標記做用。
[if !supportLists]1. [endif]//對象輸出流
[if !supportLists]2. [endif] ObjectOutputStream objectOutputStream =
[if !supportLists]3. [endif]new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
[if !supportLists]4. [endif] objectOutputStream.writeObject(new User("zhangsan", 100));
[if !supportLists]5. [endif] objectOutputStream.close();
[if !supportLists]6. [endif] //對象輸入流
[if !supportLists]7. [endif] ObjectInputStream objectInputStream =
[if !supportLists]8. [endif]new ObjectInputStream(new FileInputStream(new File("D://obj")));
[if !supportLists]9. [endif] User user = (User)objectInputStream.readObject();
[if !supportLists]10. [endif] System.out.println(user);
[if !supportLists]11. [endif] objectInputStream.close();
[if !supportLists]4. [endif]字節流和字符流的區別(2017-11-23-wzz)
字節流讀取的時候,讀到一個字節就返回一個字節;字符流使用了字節流讀到一個或多個字節(中文對應的字節數是兩個,在UTF-8碼錶中是3個字節)時。先去查指定的編碼表,將查到的字符返回。 字節流能夠處理全部類型數據,如:圖片,MP3,AVI視頻文件,而字符流只能處理字符數據。只要是處理純文本數據,就要優先考慮使用字符流,除此以外都用字節流。字節流主要是操做byte類型數據,以byte數組爲準,主要操做類就是OutputStream、InputStream
字符流處理的單元爲2個字節的Unicode字符,分別操做字符、字符數組或字符串,而字節流處理單元爲1個字節,操做字節和字節數組。因此字符流是由Java虛擬機將字節轉化爲2個字節的Unicode字符爲單位的字符而成的,因此它對多國語言支持性比較好!若是是音頻文件、圖片、歌曲,就用字節流好點,若是是關係到中文(文本)的,用字符流好點。在程序中一個字符等於兩個字節,java提供了Reader、Writer兩個專門操做字符流的類。
[if !supportLists]5. [endif]如何實現對象克隆?(2017-11-12-wl)
有兩種方式。
1). 實現 Cloneable 接口並重寫 Object 類中的 clone()方法;
2). 實現 Serializable 接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下。
[if !supportLists]12. [endif]import java.io.ByteArrayInputStream;
[if !supportLists]13. [endif]import java.io.ByteArrayOutputStream;
[if !supportLists]14. [endif]import java.io.ObjectInputStream;
[if !supportLists]15. [endif]import java.io.ObjectOutputStream;
[if !supportLists]16. [endif]import java.io.Serializable;
[if !supportLists]17. [endif]public class MyUtil {
[if !supportLists]18. [endif]private MyUtil() {
[if !supportLists]19. [endif]throw new AssertionError();
[if !supportLists]20. [endif]}
[if !supportLists]21. [endif]@SuppressWarnings("unchecked")
[if !supportLists]22. [endif]public static T clone(T obj) throws Exception {
[if !supportLists]23. [endif]ByteArrayOutputStream bout = new ByteArrayOutputStream();
[if !supportLists]24. [endif]ObjectOutputStream oos = new ObjectOutputStream(bout);
[if !supportLists]25. [endif]oos.writeObject(obj);
[if !supportLists]26. [endif]ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
[if !supportLists]27. [endif]ObjectInputStream ois = new ObjectInputStream(bin);
[if !supportLists]28. [endif]return (T) ois.readObject();
[if !supportLists]29. [endif]//說明:調用 ByteArrayInputStream 或 ByteArrayOutputStream 對象的 close 方法沒有任何意義
[if !supportLists]30. [endif]//這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放
[if !supportLists]31. [endif]}
[if !supportLists]32. [endif]}
測試代碼:
[if !supportLists]1. [endif]import java.io.Serializable;
[if !supportLists]2. [endif]/**
[if !supportLists]3. [endif]*人類
[if !supportLists]4. [endif] */
[if !supportLists]5. [endif]class Person implements Serializable {
[if !supportLists]6. [endif]private static final long serialVersionUID = -9102017020286042305L;
[if !supportLists]7. [endif]private String name; //姓名
[if !supportLists]8. [endif]private int age; //年齡
[if !supportLists]9. [endif]private Car car; //座駕
[if !supportLists]10. [endif]public Person(String name, int age, Car car) {
[if !supportLists]11. [endif]this.name = name;
[if !supportLists]12. [endif]this.age = age;
[if !supportLists]13. [endif]this.car = car;
[if !supportLists]14. [endif]}
[if !supportLists]15. [endif]public String getName() {
[if !supportLists]16. [endif]return name;
[if !supportLists]17. [endif]}
[if !supportLists]18. [endif]public void setName(String name) {
[if !supportLists]19. [endif]this.name = name;
[if !supportLists]20. [endif]}
[if !supportLists]21. [endif]public int getAge() {
[if !supportLists]22. [endif]return age;
[if !supportLists]23. [endif]}
[if !supportLists]24. [endif]public void setAge(int age) {
[if !supportLists]25. [endif]this.age = age;
[if !supportLists]26. [endif]}
[if !supportLists]27. [endif]public Car getCar() {
[if !supportLists]28. [endif]return car;
[if !supportLists]29. [endif]}
[if !supportLists]30. [endif]public void setCar(Car car) {
[if !supportLists]31. [endif]this.car = car;
[if !supportLists]32. [endif]}
[if !supportLists]33. [endif]@Override
[if !supportLists]34. [endif]public String toString() {
[if !supportLists]35. [endif]return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
[if !supportLists]36. [endif]}
[if !supportLists]37. [endif]}
[if !supportLists]1. [endif]/**
[if !supportLists]2. [endif]*小汽車類
[if !supportLists]3. [endif]*/
[if !supportLists]4. [endif]class Car implements Serializable {
[if !supportLists]5. [endif]private static final long serialVersionUID = -5713945027627603702L;
[if !supportLists]6. [endif]private String brand; //品牌
[if !supportLists]7. [endif]private int maxSpeed; //最高時速
[if !supportLists]8. [endif]public Car(String brand, int maxSpeed) {
[if !supportLists]9. [endif]this.brand = brand;
[if !supportLists]10. [endif]this.maxSpeed = maxSpeed;
[if !supportLists]11. [endif]}
[if !supportLists]12. [endif]public String getBrand() {
[if !supportLists]13. [endif]return brand;
[if !supportLists]14. [endif]}
[if !supportLists]15. [endif]public void setBrand(String brand) {
[if !supportLists]16. [endif]this.brand = brand;
[if !supportLists]17. [endif]}
[if !supportLists]18. [endif]public int getMaxSpeed() {
[if !supportLists]19. [endif]return maxSpeed;
[if !supportLists]20. [endif]}
[if !supportLists]21. [endif]public void setMaxSpeed(int maxSpeed) {
[if !supportLists]22. [endif]this.maxSpeed = maxSpeed;
[if !supportLists]23. [endif]}
[if !supportLists]24. [endif]@Override
[if !supportLists]25. [endif]public String toString() {
[if !supportLists]26. [endif]return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
[if !supportLists]27. [endif]}
[if !supportLists]28. [endif]}
[if !supportLists]1. [endif]class CloneTest {
[if !supportLists]2. [endif]public static void main(String[] args) {
[if !supportLists]3. [endif]try {
[if !supportLists]4. [endif]Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
[if !supportLists]5. [endif]Person p2 = MyUtil.clone(p1); //深度克隆
[if !supportLists]6. [endif]p2.getCar().setBrand("BYD");
[if !supportLists]7. [endif]//修改克隆的 Person 對象 p2 關聯的汽車對象的品牌屬性
[if !supportLists]8. [endif]//原來的 Person 對象 p1 關聯的汽車不會受到任何影響
[if !supportLists]9. [endif]//由於在克隆 Person 對象時其關聯的汽車對象也被克隆了
[if !supportLists]10. [endif]System.out.println(p1);
[if !supportLists]11. [endif]} catch (Exception e) {
[if !supportLists]12. [endif]e.printStackTrace();
[if !supportLists]13. [endif]}
[if !supportLists]14. [endif]}
[if !supportLists]15. [endif]}
注意:基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object 類的 clone 方法克隆對象。讓問題在編譯的時候暴露出來老是好過把問題留到運行時。
[if !supportLists]6. [endif]什麼是java序列化,如何實現java序列化?(2017-12-7-lyq)
序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決在對對象流進行讀寫操做時所引起的問題。
序列化的實現:將須要被序列化的類實現Serializable接口,該接口沒有須要實現的方法,implements Serializable只是爲了標註該對象是可被序列化的,而後使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,接着,使用ObjectOutputStream對象的writeObject(Object obj)方法就能夠將參數爲obj的對象寫出(即保存其狀態),要恢復的話則用輸入流。
原文連接:https://www.cnblogs.com/yangchunze/p/6728086.html
[if !supportLists]8、[endif]Java的集合
[if !supportLists]1. [endif]HashMap排序題,上機題。(本人主要靠這道題入職的第一家公司)
已知一個HashMap集合, User有name(String)和age(int)屬性。請寫一個方法實現對HashMap的排序功能,該方法接收HashMap爲形參,返回類型爲HashMap,要求對HashMap中的User的age倒序進行排序。排序時key=value鍵值對不得拆散。
注意:要作出這道題必須對集合的體系結構很是的熟悉。HashMap自己就是不可排序的,可是該道題恰恰讓給HashMap排序,那咱們就得想在API中有沒有這樣的Map結構是有序的,LinkedHashMap,對的,就是他,他是Map結構,也是鏈表結構,有序的,更可喜的是他是HashMap的子類,咱們返回LinkedHashMap便可,還符合面向接口(父類編程的思想)。
但凡是對集合的操做,咱們應該保持一個原則就是能用JDK中的API就有JDK中的API,好比排序算法咱們不該該去用冒泡或者選擇,而是首先想到用Collections集合工具類。
[if !supportLists]1. [endif]public class HashMapTest {
[if !supportLists]2. [endif] public static void main(String[] args) {
[if !supportLists]3. [endif] HashMap users = new HashMap<>();
[if !supportLists]4. [endif] users.put(1, new User("張三", 25));
[if !supportLists]5. [endif] users.put(3, new User("李四", 22));
[if !supportLists]6. [endif] users.put(2, new User("王五", 28));
[if !supportLists]7. [endif] System.out.println(users);
[if !supportLists]8. [endif] HashMap sortHashMap = sortHashMap(users);
[if !supportLists]9. [endif] System.out.println(sortHashMap);
[if !supportLists]10. [endif] /**
[if !supportLists]11. [endif] *控制檯輸出內容
[if !supportLists]12. [endif] * {1=User [name=張三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]}
[if !supportLists]13. [endif] {2=User [name=王五, age=28], 1=User [name=張三, age=25], 3=User [name=李四, age=22]}
[if !supportLists]14. [endif] */
[if !supportLists]15. [endif] }
[if !supportLists]16. [endif]
[if !supportLists]17. [endif] public static HashMap sortHashMap(HashMap map) {
[if !supportLists]18. [endif] //首先拿到map的鍵值對集合
[if !supportLists]19. [endif] Set> entrySet = map.entrySet();
[if !supportLists]20. [endif]
[if !supportLists]21. [endif] //將set集合轉爲List集合,爲何,爲了使用工具類的排序方法
[if !supportLists]22. [endif] List> list = new ArrayList>(entrySet);
[if !supportLists]23. [endif] //使用Collections集合工具類對list進行排序,排序規則使用匿名內部類來實現
[if !supportLists]24. [endif] Collections.sort(list, new Comparator>() {
[if !supportLists]25. [endif]
[if !supportLists]26. [endif] @Override
[if !supportLists]27. [endif] public int compare(Entry o1, Entry o2) {
[if !supportLists]28. [endif] //按照要求根據User的age的倒序進行排
[if !supportLists]29. [endif] return o2.getValue().getAge()-o1.getValue().getAge();
[if !supportLists]30. [endif] }
[if !supportLists]31. [endif] });
[if !supportLists]32. [endif] //建立一個新的有序的HashMap子類的集合
[if !supportLists]33. [endif] LinkedHashMap linkedHashMap = new LinkedHashMap();
[if !supportLists]34. [endif] //將List中的數據存儲在LinkedHashMap中
[if !supportLists]35. [endif] for(Entry entry : list){
[if !supportLists]36. [endif] linkedHashMap.put(entry.getKey(), entry.getValue());
[if !supportLists]37. [endif] }
[if !supportLists]38. [endif] //返回結果
[if !supportLists]39. [endif] return linkedHashMap;
[if !supportLists]40. [endif] }
[if !supportLists]41. [endif]}
[if !supportLists]42. [endif]
[if !supportLists]2. [endif]集合的安全性問題
請問ArrayList、HashSet、HashMap是線程安全的嗎?若是不是我想要線程安全的集合怎麼辦?
咱們都看過上面那些集合的源碼(若是沒有那就看看吧),每一個方法都沒有加鎖,顯然都是線程不安全的。話又說過來若是他們安全了也就沒第二問了。
在集合中Vector和HashTable卻是線程安全的。你打開源碼會發現其實就是把各自核心方法添加上了synchronized關鍵字。
Collections工具類提供了相關的API,能夠讓上面那3個不安全的集合變爲安全的。
[if !supportLists]1. [endif]// Collections.synchronizedCollection(c)
[if !supportLists]2. [endif]// Collections.synchronizedList(list)
[if !supportLists]3. [endif]// Collections.synchronizedMap(m)
[if !supportLists]4. [endif]// Collections.synchronizedSet(s)
上面幾個函數都有對應的返回值類型,傳入什麼類型返回什麼類型。打開源碼其實實現原理很是簡單,就是將集合的核心方法添加上了synchronized關鍵字。
[if !supportLists]3. [endif]ArrayList內部用什麼實現的?(2015-11-24)
(回答這樣的問題,不要只回答個皮毛,能夠再介紹一下ArrayList內部是如何實現數組的增長和刪除的,由於數組在建立的時候長度是固定的,那麼就有個問題咱們往ArrayList中不斷的添加對象,它是如何管理這些數組呢?)
ArrayList內部是用Object[]實現的。接下來咱們分別分析ArrayList的構造、add、remove、clear方法的實現原理。
[if !supportLists]1、[endif]構造函數
1)空參構造
/**
* Constructs a new {@code ArrayList} instance with zero initial capacity.
*/
public ArrayList() {
array = EmptyArray.OBJECT;
}
array是一個Object[]類型。當咱們new一個空參構造時系統調用了EmptyArray.OBJECT屬性,EmptyArray僅僅是一個系統的類庫,該類源碼以下:
public final class EmptyArray {
private EmptyArray() {}
public static final boolean[] BOOLEAN = new boolean[0];
public static final byte[] BYTE = new byte[0];
public static final char[] CHAR = new char[0];
public static final double[] DOUBLE = new double[0];
public static final int[] INT = new int[0];
public static final Class[] CLASS = new Class[0];
public static final Object[] OBJECT = new Object[0];
public static final String[] STRING = new String[0];
public static final Throwable[] THROWABLE = new Throwable[0];
public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
}
也就是說當咱們new 一個空參ArrayList的時候,系統內部使用了一個new Object[0]數組。
[if !supportLists]2)[endif]帶參構造1
/**
* Constructs a new instance of {@code ArrayList} with the specified
* initial capacity.
*
* @param capacity
* the initial capacity of this {@code ArrayList}.
*/
public ArrayList(int capacity) {
if (capacity < 0) {
throw new IllegalArgumentException("capacity < 0: " + capacity);
}
array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);
}
該構造函數傳入一個int值,該值做爲數組的長度值。若是該值小於0,則拋出一個運行時異常。若是等於0,則使用一個空數組,若是大於0,則建立一個長度爲該值的新數組。
3)帶參構造2
/**
* Constructs a new instance of {@code ArrayList} containing the elements of
* the specified collection.
*
* @param collection
* the collection of elements to add.
*/
public ArrayList(Collection collection) {
if (collection == null) {
throw new NullPointerException("collection == null");
}
Object[] a = collection.toArray();
if (a.getClass() != Object[].class) {
Object[] newArray = new Object[a.length];
System.arraycopy(a, 0, newArray, 0, a.length);
a = newArray;
}
array = a;
size = a.length;
}
若是調用構造函數的時候傳入了一個Collection的子類,那麼先判斷該集合是否爲null,爲null則拋出空指針異常。若是不是則將該集合轉換爲數組a,而後將該數組賦值爲成員變量array,將該數組的長度做爲成員變量size。這裏面它先判斷a.getClass是否等於Object[].class,其實通常都是相等的,我也暫時沒想明白爲何多加了這個判斷,toArray方法是Collection接口定義的,所以其全部的子類都有這樣的方法,list集合的toArray和Set集合的toArray返回的都是Object[]數組。
這裏講些題外話,其實在看Java源碼的時候,做者的不少意圖都很費人心思,我能知道他的目標是啥,可是不知道他爲什麼這樣寫。好比對於ArrayList, array是他的成員變量,可是每次在方法中使用該成員變量的時候做者都會從新在方法中開闢一個局部變量,而後給局部變量賦值爲array,而後再使用,有人可能說這是爲了防止併發修改array,畢竟array是成員變量,你們均可以使用所以須要將array變爲局部變量,而後再使用,這樣的說法並非都成立的,也許有時候就是老外們寫代碼的一個習慣而已。
[if !supportLists]2、[endif]add方法
add方法有兩個重載,這裏只研究最簡單的那個。
/**
* Adds the specified object at the end of this {@code ArrayList}.
*
* @param object
* the object to add.
* @return always true
*/
@Override public boolean add(E object) {
Object[] a = array;
int s = size;
if (s == a.length) {
Object[] newArray = new Object[s +
(s < (MIN_CAPACITY_INCREMENT / 2) ?
MIN_CAPACITY_INCREMENT : s >> 1)];
System.arraycopy(a, 0, newArray, 0, s);
array = a = newArray;
}
a[s] = object;
size = s + 1;
modCount++;
return true;
}
[if !supportLists]一、[endif]首先將成員變量array賦值給局部變量a,將成員變量size賦值給局部變量s。
[if !supportLists]二、[endif]判斷集合的長度s是否等於數組的長度(若是集合的長度已經等於數組的長度了,說明數組已經滿了,該從新分配新數組了),從新分配數組的時候須要計算新分配內存的空間大小,若是當前的長度小於MIN_CAPACITY_INCREMENT/2(這個常量值是12,除以2就是6,也就是若是當前集合長度小於6)則分配12個長度,若是集合長度大於6則分配當前長度s的一半長度。這裏面用到了三元運算符和位運算,s >> 1,意思就是將s往右移1位,至關於s=s/2,只不過位運算是效率最高的運算。
[if !supportLists]三、[endif]將新添加的object對象做爲數組的a[s]個元素。
[if !supportLists]四、[endif]修改集合長度size爲s+1
[if !supportLists]五、[endif]modCotun++,該變量是父類中聲明的,用於記錄集合修改的次數,記錄集合修改的次數是爲了防止在用迭代器迭代集合時避免併發修改異常,或者說用於判斷是否出現併發修改異常的。
[if !supportLists]六、[endif]return true,這個返回值意義不大,由於一直返回true,除非報了一個運行時異常。
[if !supportLists]3、[endif]remove方法
remove方法有兩個重載,咱們只研究remove(int index)方法。
/**
* Removes the object at the specified location from this list.
*
* @param index
* the index of the object to remove.
* @return the removed object.
* @throws IndexOutOfBoundsException
* when {@code location < 0 || location >= size()}
*/
@Override public E remove(int index) {
Object[] a = array;
int s = size;
if (index >= s) {
throwIndexOutOfBoundsException(index, s);
}
@SuppressWarnings("unchecked")
E result = (E) a[index];
System.arraycopy(a, index + 1, a, index, --s - index);
a[s] = null; // Prevent memory leak
size = s;
modCount++;
return result;
}
[if !supportLists]一、[endif]先將成員變量array和size賦值給局部變量a和s。
[if !supportLists]二、[endif]判斷形參index是否大於等於集合的長度,若是成了則拋出運行時異常
[if !supportLists]三、[endif]獲取數組中腳標爲index的對象result,該對象做爲方法的返回值
[if !supportLists]四、[endif]調用System的arraycopy函數,拷貝原理以下圖所示。
[if !supportLists]五、[endif]接下來就是很重要的一個工做,由於刪除了一個元素,並且集合總體向前移動了一位,所以須要將集合最後一個元素設置爲null,不然就可能內存泄露。
[if !supportLists]六、[endif]從新給成員變量array和size賦值
[if !supportLists]七、[endif]記錄修改次數
[if !supportLists]八、[endif]返回刪除的元素(讓用戶再看最後一眼)
[if !supportLists]4、[endif]clear方法
/**
* Removes all elements from this {@code ArrayList}, leaving it empty.
*
* @see #isEmpty
* @see #size
*/
@Override public void clear() {
if (size != 0) {
Arrays.fill(array, 0, size, null);
size = 0;
modCount++;
}
}
若是集合長度不等於0,則將全部數組的值都設置爲null,而後將成員變量size設置爲0便可,最後讓修改記錄加1。
[if !supportLists]4. [endif]併發集合和普通集合如何區別?(2015-11-24)
併發集合常見的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。併發集合位於java.util.concurrent包下,是jdk1.5以後纔有的,主要做者是Doug Lea(http://baike.baidu.com/view/3141057.htm)完成的。
在java中有普通集合、同步(線程安全)的集合、併發集合。普通集合一般性能最高,可是不保證多線程的安全性和併發的可靠性。線程安全集合僅僅是給集合添加了synchronized同步鎖,嚴重犧牲了性能,並且對併發的效率就更低了,併發集合則經過複雜的策略不只保證了多線程的安全又提升的併發時的效率。
參考閱讀:
ConcurrentHashMap是線程安全的HashMap的實現,默認構造一樣有initialCapacity和loadFactor屬性,不過還多了一個concurrencyLevel屬性,三屬性默認值分別爲1六、0.75及16。其內部使用鎖分段技術,維持這鎖Segment的數組,在Segment數組中又存放着Entity[]數組,內部hash算法將數據較均勻分佈在不一樣鎖中。
put操做:並無在此方法上加上synchronized,首先對key.hashcode進行hash操做,獲得key的hash值。hash操做的算法和map也不一樣,根據此hash值計算並獲取其對應的數組中的Segment對象(繼承自ReentrantLock),接着調用此Segment對象的put方法來完成當前操做。
ConcurrentHashMap基於concurrencyLevel劃分出了多個Segment來對key-value進行存儲,從而避免每次put操做都得鎖住整個數組。在默認的狀況下,最佳狀況下可容許16個線程併發無阻塞的操做集合對象,儘量地減小併發時的阻塞現象。
get(key)
首先對key.hashCode進行hash操做,基於其值找到對應的Segment對象,調用其get方法完成當前操做。而Segment的get操做首先經過hash值和對象數組大小減1的值進行按位與操做來獲取數組上對應位置的HashEntry。在這個步驟中,可能會由於對象數組大小的改變,以及數組上對應位置的HashEntry產生不一致性,那麼ConcurrentHashMap是如何保證的?
對象數組大小的改變只有在put操做時有可能發生,因爲HashEntry對象數組對應的變量是volatile類型的,所以能夠保證如HashEntry對象數組大小發生改變,讀操做可看到最新的對象數組大小。
在獲取到了HashEntry對象後,怎麼能保證它及其next屬性構成的鏈表上的對象不會改變呢?這點ConcurrentHashMap採用了一個簡單的方式,即HashEntry對象中的hash、key、next屬性都是final的,這也就意味着沒辦法插入一個HashEntry對象到基於next屬性構成的鏈表中間或末尾。這樣就能夠保證當獲取到HashEntry對象後,其基於next屬性構建的鏈表是不會發生變化的。
ConcurrentHashMap默認狀況下采用將數據分爲16個段進行存儲,而且16個段分別持有各自不一樣的鎖Segment,鎖僅用於put和remove等改變集合對象的操做,基於volatile及HashEntry鏈表的不變性實現了讀取的不加鎖。這些方式使得ConcurrentHashMap可以保持極好的併發支持,尤爲是對於讀遠比插入和刪除頻繁的Map而言,而它採用的這些方法也可謂是對於Java內存模型、併發機制深入掌握的體現。
推薦博客地址:http://m.oschina.net/blog/269037
[if !supportLists]5. [endif]List的三個子類的特色(2017-2-23)
ArrayList 底層結構是數組,底層查詢快,增刪慢。
LinkedList 底層結構是鏈表型的,增刪快,查詢慢。
voctor 底層結構是數組線程安全的,增刪慢,查詢慢。
[if !supportLists]6. [endif]List和Map、Set的區別(2017-11-22-wzz)
List和Set是存儲單列數據的集合,Map是存儲鍵和值這樣的雙列數據的集合;List中存儲的數據是有順序,而且容許重複;Map中存儲的數據是沒有順序的,其鍵是不能重複的,它的值是能夠有重複的,Set中存儲的數據是無序的,且不容許有重複,但元素在集合中的位置由元素的hashcode決定,位置是固定的(Set集合根據hashcode來進行數據的存儲,因此位置是固定的,可是位置不是用戶能夠控制的,因此對於用戶來講set中的元素仍是無序的);
List接口有三個實現類(LinkedList:基於鏈表實現,鏈表內存是散亂的,每個元素存儲自己內存地址的同時還存儲下一個元素的地址。鏈表增刪快,查找慢;ArrayList:基於數組實現,非線程安全的,效率高,便於索引,但不便於插入刪除;Vector:基於數組實現,線程安全的,效率低)。
Map接口有三個實現類(HashMap:基於hash表的Map接口實現,非線程安全,高效,支持null值和null鍵;HashTable:線程安全,低效,不支持null值和null鍵;LinkedHashMap:是HashMap的一個子類,保存了記錄的插入順序;SortMap接口:TreeMap,可以把它保存的記錄根據鍵排序,默認是鍵值的升序排序)。
Set接口有兩個實現類(HashSet:底層是由HashMap實現,不容許集合中有重複的值,使用該方式時須要重寫equals()和hashCode()方法;LinkedHashSet:繼承與HashSet,同時又基於LinkedHashMap來進行實現,底層使用的是LinkedHashMp)。
List集合中對象按照索引位置排序,能夠有重複對象,容許按照對象在集合中的索引位置檢索對象,例如經過list.get(i)方法來獲取集合中的元素;Map中的每個元素包含一個鍵和一個值,成對出現,鍵對象不能夠重複,值對象能夠重複;Set集合中的對象不按照特定的方式排序,而且沒有重複對象,但它的實現類能對集合中的對象按照特定的方式排序,例如TreeSet類,能夠按照默認順序,也能夠經過實現Java.util.Comparator接口來自定義排序方式。
[if !supportLists]7. [endif]HashMap 和HashTable有什麼區別?(2017-2-23)
HashMap是線程不安全的,HashMap是一個接口,是Map的一個子接口,是將鍵映射到值得對象,不容許鍵值重複,容許空鍵和空值;因爲非線程安全,HashMap的效率要較HashTable的效率高一些.
HashTable 是線程安全的一個集合,不容許null值做爲一個key值或者Value值;
HashTable是sychronize,多個線程訪問時不須要本身爲它的方法實現同步,而HashMap在被多個線程訪問的時候須要本身爲它的方法實現同步;
[if !supportLists]8. [endif]數組和鏈表分別比較適合用於什麼場景,爲何?(2017-2-23)
8.1 數組和鏈表簡介
在計算機中要對給定的數據集進行若干處理,首要任務是把數據集的一部分(當數據量很是大時,可能只能一部分一部分地讀取數據到內存中來處理)或所有存儲到內存中,而後再對內存中的數據進行各類處理。
例如,對於數據集S{1,2,3,4,5,6},要求S中元素的和,首先要把數據存儲到內存中,而後再將內存中的數據相加。
當內存空間中有足夠大的連續空間時,能夠把數據連續的存放在內存中,各類編程語言中的數組通常都是按這種方式存儲的(也可能有例外),如圖1(b);當內存中只有一些離散的可用空間時,想連續存儲數據就很是困難了,這時能想到的一種解決方式是移動內存中的數據,把離散的空間彙集成連續的一塊大空間,如圖1(c)所示,這樣作固然也能夠,可是這種狀況由於可能要移動別人的數據,因此會存在一些困難,移動的過程當中也有可能會把一些別人的重要數據給丟失。另一種,不影響別人的數據存儲方式是把數據集中的數據分開離散地存儲到這些不連續空間中,如圖(d)。這時爲了能把數據集中的全部數據聯繫起來,須要在前一塊數據的存儲空間中記錄下一塊數據的地址,這樣只要知道第一塊內存空間的地址就能環環相扣地把數據集總體聯繫在一塊兒了。C/C++中用指針實現的鏈表就是這種存儲形式。
圖內存分配
由上可知,內存中的存儲形式能夠分爲連續存儲和離散存儲兩種。所以,數據的物理存儲結構就有連續存儲和離散存儲兩種,它們對應了咱們一般所說的數組和鏈表,
8.2 數組和鏈表的區別
數組是將元素在內存中連續存儲的;它的優勢:由於數據是連續存儲的,內存地址連續,因此在查找數據的時候效率比較高;它的缺點:在存儲以前,咱們須要申請一塊連續的內存空間,而且在編譯的時候就必須肯定好它的空間的大小。在運行的時候空間的大小是沒法隨着你的須要進行增長和減小而改變的,當數據兩比較大的時候,有可能會出現越界的狀況,數據比較小的時候,又有可能會浪費掉內存空間。在改變數據個數時,增長、插入、刪除數據效率比較低
鏈表是動態申請內存空間,不須要像數組須要提早申請好內存的大小,鏈表只需在用的時候申請就能夠,根據須要來動態申請或者刪除內存空間,對於數據增長和刪除以及插入比數組靈活。還有就是鏈表中數據在內存中能夠在任意的位置,經過應用來關聯數據(就是經過存在元素的指針來聯繫)
8.3 鏈表和數組使用場景
數組應用場景:數據比較少;常常作的運算是按序號訪問數據元素;數組更容易實現,任何高級語言都支持;構建的線性表較穩定。
鏈表應用場景:對線性表的長度或者規模難以估計;頻繁作插入刪除操做;構建動態性比較強的線性表。
參考博客:http://blog.csdn.net/u011277123/article/details/53908387
8.4 跟數組相關的面試題
用面向對象的方法求出數組中重複value的個數,按以下個數輸出:
1出現:1次
3出現:2次
8出現:3次
2出現:4次
int[] arr = {1,4,1,4,2,5,4,5,8,7,8,77,88,5,4,9,6,2,4,1,5};
[if !supportLists]9. [endif]Java中ArrayList和Linkedlist區別?(2017-2-23)
ArrayList和Vector使用了數組的實現,能夠認爲ArrayList或者Vector封裝了對內部數組的操做,好比向數組中添加,刪除,插入新的元素或者數據的擴展和重定向。
LinkedList使用了循環雙向鏈表數據結構。與基於數組的ArrayList相比,這是兩種大相徑庭的實現技術,這也決定了它們將適用於徹底不一樣的工做場景。
LinkedList鏈表由一系列表項鍊接而成。一個表項老是包含3個部分:元素內容,前驅表和後驅表,如圖所示:
在下圖展現了一個包含3個元素的LinkedList的各個表項間的鏈接關係。在JDK的實現中,不管LikedList是否爲空,鏈表內部都有一個header表項,它既表示鏈表的開始,也表示鏈表的結尾。表項header的後驅表項即是鏈表中第一個元素,表項header的前驅表項即是鏈表中最後一個元素。
[if !supportLists]10. [endif]List a=new ArrayList()和ArrayList a =new ArrayList()的區別?(2017-2-24)
List list = new ArrayList();這句建立了一個ArrayList的對象後把上溯到了List。此時它是一個List對象了,有些ArrayList有可是List沒有的屬性和方法,它就不能再用了。而ArrayList list=new ArrayList();建立一對象則保留了ArrayList的全部屬性。 因此須要用到ArrayList獨有的方法的時候不能用前者。實例代碼以下:
[if !supportLists]1.[endif]List list = new ArrayList();
[if !supportLists]2.[endif]ArrayList arrayList = new ArrayList();
[if !supportLists]3.[endif]list.trimToSize(); //錯誤,沒有該方法。
[if !supportLists]4.[endif]arrayList.trimToSize(); //ArrayList裏有該方法。
[if !supportLists]11. [endif]要對集合更新操做時,ArrayList和LinkedList哪一個更適合?(2017-2-24)
1.ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。 2.若是集合數據是對於集合隨機訪問get和set,ArrayList絕對優於LinkedList,由於LinkedList要移動指針。 3.若是集合數據是對於集合新增和刪除操做add和remove,LinedList比較佔優點,由於ArrayList要移動數據。
ArrayList和LinkedList 是兩個集合類,用於存儲一系列的對象引用(references)。例如咱們能夠用ArrayList來存儲一系列的String或者Integer。那 麼ArrayList和LinkedList在性能上有什麼差異呢?何時應該用ArrayList何時又該用LinkedList呢?
[if !supportLists]一.[endif]時間複雜度 首先一點關鍵的是,ArrayList的內部實現是基於基礎的對象數組的,所以,它使用get方法訪問列表中的任意一個元素時(random access),它的速度要比LinkedList快。LinkedList中的get方法是按照順序從列表的一端開始檢查,直到另一端。對LinkedList而言,訪問列表中的某個指定元素沒有更快的方法了。 假設咱們有一個很大的列表,它裏面的元素已經排好序了,這個列表多是ArrayList類型的也多是LinkedList類型的,如今咱們對這個列表來進行二分查找(binary search),比較列表是ArrayList和LinkedList時的查詢速度,看下面的程序:
[if !supportLists]1.[endif]public class TestList{
[if !supportLists]2.[endif] public static final int N=50000; //50000個數
[if !supportLists]3.[endif] public static List values; //要查找的集合
[if !supportLists]4.[endif]//放入50000個數給value;
[if !supportLists]5.[endif] static{
[if !supportLists]6.[endif] Integer vals[]=new Integer[N];
[if !supportLists]7.[endif] Random r=new Random();
[if !supportLists]8.[endif] for(int i=0,currval=0;i
[if !supportLists]9.[endif] vals=new Integer(currval);
[if !supportLists]10.[endif] currval+=r.nextInt(100)+1;
[if !supportLists]11.[endif] }
[if !supportLists]12.[endif] values=Arrays.asList(vals);
[if !supportLists]13.[endif] }
[if !supportLists]14.[endif]//經過二分查找法查找
[if !supportLists]15.[endif] static long timeList(List lst){
[if !supportLists]16.[endif] long start=System.currentTimeMillis();
[if !supportLists]17.[endif] for(int i=0;i
[if !supportLists]18.[endif] int index=Collections.binarySearch(lst, values.get(i));
[if !supportLists]19.[endif] if(index!=i)
[if !supportLists]20.[endif]System.out.println("***錯誤***");
[if !supportLists]21.[endif] }
[if !supportLists]22.[endif] return System.currentTimeMillis()-start;
[if !supportLists]23.[endif] }
[if !supportLists]24.[endif] public static void main(String args[])...{
[if !supportLists]25.[endif]System.out.println("ArrayList消耗時間:"+timeList(new ArrayList(values)));
[if !supportLists]26.[endif]System.out.println("LinkedList消耗時間:"+timeList(new LinkedList(values)));
[if !supportLists]27.[endif] }
[if !supportLists]28.[endif]}
獲得的輸出是:
[if !supportLists]1. [endif]ArrayList消耗時間:15
[if !supportLists]2. [endif]LinkedList消耗時間:2596
這個結果不是固定的,可是基本上ArrayList的時間要明顯小於LinkedList的時間。所以在這種狀況下不宜用LinkedList。二分查找法使用的隨機訪問(random access)策略,而LinkedList是不支持快速的隨機訪問的。對一個LinkedList作隨機訪問所消耗的時間與這個list的大小是成比例的。而相應的,在ArrayList中進行隨機訪問所消耗的時間是固定的。 這是否代表ArrayList老是比 LinkedList性能要好呢?這並不必定,在某些狀況下LinkedList的表現要優於ArrayList,有些算法在LinkedList中實現 時效率更高。比方說,利用Collections.reverse方法對列表進行反轉時,其性能就要好些。看這樣一個例子,加入咱們有一個列表,要對其進行大量的插入和刪除操做,在這種狀況下LinkedList就是一個較好的選擇。請看以下一個極端的例子,咱們重複的在一個列表的開端插入一個元素:
[if !supportLists]1.[endif]import java.util.*;
[if !supportLists]2.[endif]public class ListDemo {
[if !supportLists]3.[endif] static final int N=50000;
[if !supportLists]4.[endif] static long timeList(List list){
[if !supportLists]5.[endif] long start=System.currentTimeMillis();
[if !supportLists]6.[endif] Object o = new Object();
[if !supportLists]7.[endif] for(int i=0;i
[if !supportLists]8.[endif] list.add(0, o);
[if !supportLists]9.[endif] return System.currentTimeMillis()-start;
[if !supportLists]10.[endif] }
[if !supportLists]11.[endif] public static void main(String[] args) {
[if !supportLists]12.[endif]System.out.println("ArrayList耗時:"+timeList(new ArrayList()));
[if !supportLists]13.[endif]System.out.println("LinkedList耗時:"+timeList(new LinkedList()));
[if !supportLists]14.[endif] }
[if !supportLists]15.[endif]}
這時個人輸出結果是
[if !supportLists]1. [endif]ArrayList耗時:2463
[if !supportLists]2. [endif]LinkedList耗時:15
[if !supportLists]二.[endif]空間複雜度在LinkedList中有一個私有的內部類,定義以下:
[if !supportLists]1.[endif]private static class Entry {
[if !supportLists]2.[endif] Object element;
[if !supportLists]3.[endif] Entry next;
[if !supportLists]4.[endif] Entry previous;
[if !supportLists]5.[endif] }
每一個Entry對象reference列表 中的一個元素,同時還有在LinkedList中它的上一個元素和下一個元素。一個有1000個元素的LinkedList對象將有1000個連接在一塊兒 的Entry對象,每一個對象都對應於列表中的一個元素。這樣的話,在一個LinkedList結構中將有一個很大的空間開銷,由於它要存儲這1000個 Entity對象的相關信息。 ArrayList使用一個內置的數組來存 儲元素,這個數組的起始容量是10.當數組須要增加時,新的容量按以下公式得到:新容量=(舊容量*3)/2+1,也就是說每一次容量大概會增加50%。 這就意味着,若是你有一個包含大量元素的ArrayList對象,那麼最終將有很大的空間會被浪費掉,這個浪費是由ArrayList的工做方式自己形成 的。若是沒有足夠的空間來存放新的元素,數組將不得不被從新進行分配以便可以增長新的元素。對數組進行從新分配,將會致使性能急劇降低。若是咱們知道一個 ArrayList將會有多少個元素,咱們能夠經過構造方法來指定容量。咱們還能夠經過trimToSize方法在ArrayList分配完畢以後去掉浪 費掉的空間。
三.總結 ArrayList和LinkedList在性能上各有優缺點,都有各自所適用的地方,總的說來能夠描述以下: 1.對ArrayList和 LinkedList而言,在列表末尾增長一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增長一項,指向所添加的元素,偶 爾可能會致使對數組從新進行分配;而對LinkedList而言,這個開銷是統一的,分配一個內部Entry對象。 2.在ArrayList的中間插入或刪除一個元素意味着這個列表中剩餘的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。 3.LinkedList不支持高效的隨機元素訪問。 4.ArrayList的空間浪費主要體如今在list列表的結尾預留必定的容量空間,而LinkedList的空間花費則體如今它的每個元素都須要消耗至關的空間 能夠這樣說:當操做是在一列數據的後面添加數據而不是在前面或中間,而且須要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操做是在一列數據的前面或中間添加或刪除數據,而且按照順序訪問其中的元素時,就應該使用LinkedList了。
[if !supportLists]12. [endif]請用兩個隊列模擬堆棧結構(2017-2-24)
兩個隊列模擬一個堆棧,隊列是先進先出,而堆棧是先進後出。模擬以下
隊列a和b
[if !supportLists](1)[endif]入棧:a隊列爲空,b爲空。例:則將」a,b,c,d,e」須要入棧的元素先放a中,a進棧爲」a,b,c,d,e」
[if !supportLists](2)[endif]出棧:a隊列目前的元素爲」a,b,c,,d,e」。將a隊列依次加入Arraylist集合a中。以倒序的方法,將a中的集合取出,放入b隊列中,再將b隊列出列。代碼以下:
[if !supportLists]1.[endif] public static void main(String[] args) {
[if !supportLists]2.[endif] Queue queue = new LinkedList(); //a隊列
[if !supportLists]3.[endif] Queue queue2=new LinkedList(); //b隊列
[if !supportLists]4.[endif] ArrayList a=new ArrayList(); //arrylist集合是中間參數
[if !supportLists]5.[endif] //往a隊列添加元素
[if !supportLists]6.[endif] queue.offer("a");
[if !supportLists]7.[endif] queue.offer("b");
[if !supportLists]8.[endif] queue.offer("c");
[if !supportLists]9.[endif] queue.offer("d");
[if !supportLists]10.[endif] queue.offer("e");
[if !supportLists]11.[endif] System.out.print("進棧:");
[if !supportLists]12.[endif]//a隊列依次加入list集合之中
[if !supportLists]13.[endif] for(String q : queue){
[if !supportLists]14.[endif] a.add(q);
[if !supportLists]15.[endif] System.out.print(q);
[if !supportLists]16.[endif] }
[if !supportLists]17.[endif]//以倒序的方法取出(a隊列依次加入list集合)之中的值,加入b對列
[if !supportLists]18.[endif] for(int i=a.size()-1;i>=0;i--){
[if !supportLists]19.[endif] queue2.offer(a.get(i));
[if !supportLists]20.[endif] }
[if !supportLists]21.[endif]//打印出棧隊列
[if !supportLists]22.[endif] System.out.println("");
[if !supportLists]23.[endif] System.out.print("出棧:");
[if !supportLists]24.[endif] for(String q : queue2){
[if !supportLists]25.[endif] System.out.print(q);
[if !supportLists]26.[endif] }
[if !supportLists]27.[endif] }
打印結果爲(遵循棧模式先進後出):
進棧:a b c d e
出棧:e d c b a
[if !supportLists]13. [endif]Collection和Map的集成體系(2017-11-14-lyq)
Collection:
Map:
[if !supportLists]14. [endif]Map中的key和value能夠爲null麼?(2017-11-21-gxb)
HashMap對象的key、value值都可爲null。
HahTable對象的key、value值均不可爲null。
且二者的的key值均不能重複,若添加key相同的鍵值對,後面的value會自動覆蓋前面的value,但不會報錯。測試代碼以下:
[if !supportLists]1. [endif]public class Test {
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] public static void main(String[] args) {
[if !supportLists]4. [endif]Map map = new HashMap();//HashMap對象
[if !supportLists]5. [endif]Map tableMap = new Hashtable();//HashTable對象
[if !supportLists]6. [endif]
[if !supportLists]7. [endif] map.put(null, null);
[if !supportLists]8. [endif]System.out.println("hashMap的[key]和[value]都可覺得null:" + map.get(null));
[if !supportLists]9. [endif]
[if !supportLists]10. [endif] try {
[if !supportLists]11. [endif] tableMap.put(null, "3");
[if !supportLists]12. [endif] System.out.println(tableMap.get(null));
[if !supportLists]13. [endif] } catch (Exception e) {
[if !supportLists]14. [endif]System.out.println("【ERROR】:hashTable的[key]不能爲null");
[if !supportLists]15. [endif] }
[if !supportLists]16. [endif]
[if !supportLists]17. [endif] try {
[if !supportLists]18. [endif] tableMap.put("3", null);
[if !supportLists]19. [endif] System.out.println(tableMap.get("3"));
[if !supportLists]20. [endif] } catch (Exception e) {
[if !supportLists]21. [endif]System.out.println("【ERROR】:hashTable的[value]不能爲null");
[if !supportLists]22. [endif] }
[if !supportLists]23. [endif] }
[if !supportLists]24. [endif]
[if !supportLists]25. [endif]}
運行結果:
hashMap的[key]和[value]都可覺得null:null 【ERROR】:hashTable的[key]不能爲null 【ERROR】:hashTable的[value]不能爲null
[if !supportLists]9、[endif]Java的多線程和併發庫
對於Java程序員來講,多線程在工做中的使用場景仍是比較常見的,而僅僅掌握了Java中的傳統多線程機制,仍是不夠的。在JDK5.0以後,Java增長的併發庫中提供了不少優秀的API,在實際開發中用的比較多。所以在看具體的面試題以前咱們有必要對這部分知識作一個全面的瞭解。
(一)多線程基礎知識--傳統線程機制的回顧(2017-12-11-wl)
[if !supportLists]( 1 ) [endif]傳統使用類Thread和接口Runnable實現
[if !supportLists]1. [endif]在Thread子類覆蓋的run方法中編寫運行代碼
方式一
new Thread(){
@Override
public void run(){
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
[if !supportLists]2. [endif]在傳遞給Thread對象的Runnable對象的run方法中編寫代碼
new Thread(new Runnable(){
public void run(){
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}).start();
[if !supportLists]3. [endif]總結
查看Thread類的run()方法的源代碼,能夠看到其實這兩種方式都是在調用Thread對象的run方法,若是Thread類的run方法沒有被覆蓋,而且爲該Thread對象設置了一個Runnable對象,該run方法會調用Runnable對象的run方法
/**
* If this thread was constructed using a separate
* Runnable
run object, then that
* Runnable
object's run
method is called;
* otherwise, this method does nothing and returns.
*
* Subclasses of Thread
should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
[if !supportLists]( 2 ) [endif]定實現時器Timer和TimerTask
Timer在實際開發中應用場景很少,通常來講都會用其餘第三方庫來實現。但有時會在一些面試題中出現。下面咱們就針對一道面試題來使用Timer定時類。
[if !supportLists]1. [endif]請模擬寫出雙重定時器(面試題)
要求:使用定時器,間隔4秒執行一次,再間隔2秒執行一次,以此類推執行。
class TimerTastCus extends TimerTask{
@Override
public void run() {
count = (count +1)%2;
System.err.println("Boob boom ");
new Timer().schedule(new TimerTastCus(), 2000+2000*count);
}
}
Timer timer = new Timer();
timer.schedule(new TimerTastCus(), 2000+2000*count);
while (true) {
System.out.println(new Date().getSeconds());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//PS:下面的代碼中的count變量中
//此參數要使用在你匿名內部類中,使用final修飾就沒法對其值進行修改,
//只能改成靜態變量
private static volatile int count = 0;
[if !supportLists]( 3 ) [endif]線程互斥與同步
在引入多線程後,因爲線程執行的異步性,會給系統形成混亂,特別是在急用臨界資源時,如多個線程急用同一臺打印機,會使打印結果交織在一塊兒,難於區分。當多個線程急用共享變量,表格,鏈表時,可能會致使數據處理出錯,所以線程同步的主要任務是使併發執行的各線程之間可以有效的共享資源和相互合做,從而使程序的執行具備可再現性。
當線程併發執行時,因爲資源共享和線程協做,使用線程之間會存在如下兩種制約關係。
[if !supportLists]1. [endif]間接相互制約。一個系統中的多個線程必然要共享某種系統資源,如共享CPU,共享I/O設備,所謂間接相互制約即源於這種資源共享,打印機就是最好的例子,線程A在使用打印機時,其它線程都要等待。
[if !supportLists]2. [endif]直接相互制約。這種制約主要是由於線程之間的合做,若有線程A將計算結果提供給線程B做進一步處理,那麼線程B在線程A將數據送達以前都將處於阻塞狀態。
間接相互制約能夠稱爲互斥,直接相互制約能夠稱爲同步,對於互斥能夠這樣理解,線程A和線程B互斥訪問某個資源則它們之間就會產個順序問題——要麼線程A等待線程B操做完畢,要麼線程B等待線程操做完畢,這其實就是線程的同步了。所以同步包括互斥,互斥實際上是一種特殊的同步。
下面咱們經過一道面試題來體會線程的交互。
要求:子線程運行執行10次後,主線程再運行5次。這樣交替執行三遍
public static void main(String[] args) {
final Bussiness bussiness = new Bussiness();
//子線程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
bussiness.subMethod();
}
}
}).start();
//主線程
for (int i = 0; i < 3; i++) {
bussiness.mainMethod();
}
}
}
class Bussiness {
private boolean subFlag = true;
public synchronized void mainMethod() {
while (subFlag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()
+ " : main thread running loop count -- " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
subFlag = true;
notify();
}
public synchronized void subMethod() {
while (!subFlag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 10; i++) {
System.err.println(Thread.currentThread().getName()
+ " : sub thread running loop count -- " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
subFlag = false;
notify();
}
}
[if !supportLists]( 4 ) [endif]線程局部變量ThreadLocal
[if !supportLists]l [endif]ThreadLocal的做用和目的:用於實現線程內的數據共享,即對於相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另一份數據。
[if !supportLists]l [endif]每一個線程調用全局ThreadLocal對象的set方法,在set方法中,首先根據當前線程獲取當前線程的ThreadLocalMap對象,而後往這個map中插入一條記錄,key實際上是ThreadLocal對象,value是各自的set方法傳進去的值。也就是每一個線程其實都有一份本身獨享的ThreadLocalMap對象,該對象的Key是ThreadLocal對象,值是用戶設置的具體值。在線程結束時能夠調用ThreadLocal.remove()方法,這樣會更快釋放內存,不調用也能夠,由於線程結束後也能夠自動釋放相關的ThreadLocal變量。
[if !supportLists]l [endif]ThreadLocal的應用場景:
[if !supportLists]Ø [endif]訂單處理包含一系列操做:減小庫存量、增長一條流水臺帳、修改總帳,這幾個操做要在同一個事務中完成,一般也即同一個線程中進行處理,若是累加公司應收款的操做失敗了,則應該把前面的操做回滾,不然,提交全部操做,這要求這些操做使用相同的數據庫鏈接對象,而這些操做的代碼分別位於不一樣的模塊類中。
[if !supportLists]Ø [endif]銀行轉帳包含一系列操做:把轉出賬戶的餘額減小,把轉入賬戶的餘額增長,這兩個操做要在同一個事務中完成,它們必須使用相同的數據庫鏈接對象,轉入和轉出操做的代碼分別是兩個不一樣的賬戶對象的方法。
[if !supportLists]Ø [endif]例如Strut2的ActionContext,同一段代碼被不一樣的線程調用運行時,該代碼操做的數據是每一個線程各自的狀態和數據,對於不一樣的線程來講,getContext方法拿到的對象都不相同,對同一個線程來講,無論調用getContext方法多少次和在哪一個模塊中getContext方法,拿到的都是同一個。
[if !supportLists]1. [endif]ThreadLocal的使用方式
[if !supportLists](1) [endif]在關聯數據類中建立private static ThreadLocal
在下面的類中,私有靜態 ThreadLocal 實例(serialNum)爲調用該類的靜態SerialNum.get() 方法的每一個線程維護了一個「序列號」,該方法將返回當前線程的序列號。(線程的序列號是在第一次調用SerialNum.get() 時分配的,並在後續調用中不會更改。)
public class SerialNum {
// The next serial number to be assigned
private static int nextSerialNum = 0;
private static ThreadLocal serialNum = new ThreadLocal() {
protected synchronized Object initialValue() {
return new Integer(nextSerialNum++);
}
};
public static int get() {
return ((Integer) (serialNum.get())).intValue();
}
}
另外一個例子,也是私有靜態 ThreadLocal 實例:
public class ThreadContext { private String userId; private Long transactionId; private static ThreadLocal threadLocal = new ThreadLocal(){ @Override protected ThreadContext initialValue() { return new ThreadContext(); } }; public static ThreadContext get() { return threadLocal.get(); }
public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public Long getTransactionId() { return transactionId; } public void setTransactionId(Long transactionId) { this.transactionId = transactionId; }}
補充:在JDK的API對ThreadLocal私有化的說明。並舉例‘線程惟一標識符’UniqueThreadIdGenerator ,你們學習是能夠結合官方API來學習。
[if !supportLists]2. [endif]在Util類中建立ThreadLocal
這是上面用法的擴展,即把ThreadLocal的建立放到工具類中。
public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class);private static final SessionFactory sessionFactory; //定義SessionFactory static { try {//經過默認配置文件hibernate.cfg.xml建立SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) {log.error("初始化SessionFactory失敗!", ex); throw new ExceptionInInitializerError(ex); } }//建立線程局部變量session,用來保存Hibernate的Session public static final ThreadLocal session = new ThreadLocal(); /***獲取當前線程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get();//若是Session尚未打開,則新開一個Session if (s == null) { s = sessionFactory.openSession();session.set(s); //將新開的Session保存到線程局部變量中 } return s; } public static void closeSession() throws HibernateException {//獲取線程局部變量,並強制轉換爲Session類型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); }}
[if !supportLists]3. [endif]在Runnable中建立ThreadLocal
在線程類內部建立ThreadLocal,基本步驟以下:
[if !supportLists]①、[endif]在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來保存線程間須要隔離處理的對象xxx。
[if !supportLists]②、[endif]在ThreadDemo類中,建立一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象爲null時候,應該new()一個隔離訪問類型的對象,並強制轉換爲要應用的類型
[if !supportLists]③、[endif]在ThreadDemo類的run()方法中,經過調用getXxx()方法獲取要操做的數據,這樣能夠保證每一個線程對應一個數據對象,在任什麼時候刻都操做的是這個對象。
public class ThreadLocalTest implements Runnable{ ThreadLocal studenThreadLocal = new ThreadLocal(); @Override public void run() { String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running..."); Random random = new Random(); int age = random.nextInt(100); System.out.println(currentThreadName + " is set age: " + age); Studen studen = getStudent(); //經過這個方法,爲每一個線程都獨立的new一個student對象,每一個線程的的student對象均可以設置不一樣的值 studen.setAge(age); System.out.println(currentThreadName + " is first get age: " + studen.getAge()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( currentThreadName + " is second get age: " + studen.getAge()); } private Studen getStudent() { Studen studen = studenThreadLocal.get(); if (null == studen) { studen = new Studen(); studenThreadLocal.set(studen); } return studen; } public static void main(String[] args) { ThreadLocalTest t = new ThreadLocalTest(); Thread t1 = new Thread(t,"Thread A"); Thread t2 = new Thread(t,"Thread B"); t1.start(); t2.start(); } }class Studen{ int age; public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
[if !supportLists]( 5 ) [endif]多線程共享數據
在Java傳統線程機制中的共享數據方式,大體能夠簡單分兩種狀況:
[if !supportLists]Ø [endif]多個線程行爲一致,共同操做一個數據源。也就是每一個線程執行的代碼相同,能夠使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,賣票系統就能夠這麼作。
[if !supportLists]Ø [endif]多個線程行爲不一致,共同操做一個數據源。也就是每一個線程執行的代碼不一樣,這時候須要用不一樣的Runnable對象。例如,銀行存取款。
下面咱們經過兩個示例代碼來分別說明這兩種方式。
[if !supportLists]1. [endif]多個線程行爲一致共同操做一個數據
若是每一個線程執行的代碼相同,能夠使用同一個Runnable對象,這個Runnable對象中有那個共享數據,例如,買票系統就能夠這麼作。
/**
*共享數據類
**/
class ShareData{
private int num = 10 ;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
*多線程類
**/
class RunnableCusToInc implements Runnable{
private ShareData shareData;
public RunnableCusToInc(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
}
/**
*測試方法
**/
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
}
}
}
[if !supportLists]2. [endif]多個線程行爲不一致共同操做一個數據
若是每一個線程執行的代碼不一樣,這時候須要用不一樣的Runnable對象,有以下兩種方式來實現這些Runnable對象之間的數據共享:
[if !supportLists]1) [endif]將共享數據封裝在另一個對象中,而後將這個對象逐一傳遞給各個Runnable對象。每一個線程對共享數據的操做方法也分配到那個對象身上去完成,這樣容易實現針對該數據進行的各個操做的互斥和通訊。
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
if(i%2 == 0){
new Thread(new RunnableCusToInc(shareData),"Thread "+ i).start();
}else{
new Thread(new RunnableCusToDec(shareData),"Thread "+ i).start();
}
}
}
//封裝共享數據類
class RunnableCusToInc implements Runnable{
//封裝共享數據
private ShareData shareData;
public RunnableCusToInc(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
}
//封裝共享數據類
class RunnableCusToDec implements Runnable{
//封裝共享數據
private ShareData shareData;
public RunnableCusToDec(ShareData data) {
this.shareData = data;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.dec();
}
}
}
/**
*共享數據類
**/
class ShareData{
private int num = 10 ;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
[if !supportLists]2) [endif]將這些Runnable對象做爲某一個類中的內部類,共享數據做爲這個外部類中的成員變量,每一個線程對共享數據的操做方法也分配給外部類,以便實現對共享數據進行的各個操做的互斥和通訊,做爲內部類的各個Runnable對象調用外部類的這些方法。
public static void main(String[] args) {
//公共數據
final ShareData shareData = new ShareData();
for (int i = 0; i < 4; i++) {
if(i%2 == 0){
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.inc();
}
}
},"Thread "+ i).start();
}else{
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
shareData.dec();
}
}
},"Thread "+ i).start();
}
}
}
class ShareData{
private int num = 10 ;
public synchronized void inc() {
num++;
System.out.println(Thread.currentThread().getName()+": invoke inc method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void dec() {
num--;
System.err.println(Thread.currentThread().getName()+": invoke dec method num =" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
補充:上面兩種方式的組合:將共享數據封裝在另一個對象中,每一個線程對共享數據的操做方法也分配到那個對象身上去完成,對象做爲這個外部類中的成員變量或方法中的局部變量,每一個線程的Runnable對象做爲外部類中的成員內部類或局部內部類。
總之,要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較容易實現它們之間的同步互斥和通訊。
(二)多線程基礎知識--線程併發庫(2017-12-11-wl)
Java 5 添加了一個新的包到 Java 平臺,java.util.concurrent 包。這個包包含有一系列可以讓 Java 的併發編程變得更加簡單輕鬆的類。在這個包被添加之前,你須要本身去動手實現本身的相關工具類。下面帶你認識下java.util.concurrent包裏的這些類,而後你能夠嘗試着如何在項目中使用它們。本文中將使用Java 6 版本,我不肯定這和 Java 5 版本里的是否有一些差別。我不會去解釋關於 Java 併發的核心問題 – 其背後的原理,也就是說,若是你對那些東西感興趣,參考《Java 併發指南》。
[if !supportLists]( 1 ) [endif]Java的線程併發庫介紹
Java5的多線程並有兩個大發庫在java.util.concurrent包及子包中,子包主要的包有一下兩個
[if !supportLists]1) [endif]java.util.concurrent包 (多線程併發庫)
[if !supportLists]Ø [endif] java.util.concurrent 包含許多線程安全、測試良好、高性能的併發構建塊。不客氣地說,建立 java.util.concurrent 的目的就是要實現 Collection 框架對數據結構所執行的併發操做。經過提供一組可靠的、高性能併發構建塊,開發人員能夠提升併發類的線程安全、可伸縮性、性能、可讀性和可靠性,後面、咱們會作介紹。
[if !supportLists]Ø [endif]若是一些類名看起來類似,多是由於java.util.concurrent 中的許多概念源自 Doug Lea 的 util.concurrent 庫。
[if !supportLists]2) [endif]java.util.concurrent.atomic包 (多線程的原子性操做提供的工具類)
[if !supportLists]Ø [endif]查看atomic包文檔頁下面的介紹,它能夠對多線程的基本數據、數組中的基本數據和對象中的基本數據進行多線程的操做(AtomicInteger、AtomicIntegerArray、AtomicIntegerFieldUpDater…)
[if !supportLists]Ø [endif]經過以下兩個方法快速理解atomic包的意義:
[if !supportLists]n [endif]AtomicInteger類的boolean compareAndSet(expectedValue, updateValue);
[if !supportLists]n [endif]AtomicIntegerArray類的int addAndGet(int i, int delta);
[if !supportLists]Ø [endif]順帶解釋volatile類型的做用,須要查看java語言規範。
[if !supportLists]n [endif]volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的最的值。(具備可見性)
[if !supportLists]n [endif]volatile沒有原子性。
[if !supportLists]3) [endif]java.util.concurrent.lock包 (多線程的鎖機制)
爲鎖和等待條件提供一個框架的接口和類,它不一樣於內置同步和監視器。該框架容許更靈活地使用鎖和條件。本包下有三大接口,下面簡單介紹下:
[if !supportLists]Ø [endif]Lock接口:支持那些語義不一樣(重入、公平等)的鎖規則,能夠在非阻塞式結構的上下文(包括hand-over-hand 和鎖重排算法)中使用這些規則。主要的實現是ReentrantLock。
[if !supportLists]Ø [endif]ReadWriteLock接口:以相似方式定義了一些讀取者能夠共享而寫入者獨佔的鎖。此包只提供了一個實現,即ReentrantReadWriteLock,由於它適用於大部分的標準用法上下文。但程序員能夠建立本身的、適用於非標準要求的實現。
[if !supportLists]Ø [endif]Condition接口:描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用Object.wait 訪問的隱式監視器相似,但提供了更強大的功能。須要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。爲了不兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不一樣。
[if !supportLists]( 2 ) [endif]Java的併發庫入門
下面咱們將分別介紹java.util.concurrent包下的經常使用類的使用。
[if !supportLists]1) [endif]java.util.concurrent包
java.util.concurrent包描述:
在併發編程中很經常使用的實用工具類。此包包括了幾個小的、已標準化的可擴展框架,以及一些提供有用功能的類。此包下有一些組件,其中包括:
[if !supportLists]l [endif]執行程序(線程池)
[if !supportLists]l [endif]併發隊列
[if !supportLists]l [endif]同步器
[if !supportLists]l [endif]併發Collocation
下面咱們將java.util.concurrent包下的組件逐一簡單介紹:
[if !supportLists]A. [endif]執行程序
[if !supportLists]Ø [endif]Executors線程池工廠類
首次咱們來講下線程池的做用:
線程池做用就是限制系統中執行線程的數量。 根據系統的環境狀況,能夠自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了形成系統擁擠效率不高。用線程池控制線程數量,其餘線程 排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務須要運行時,若是線程 池中有等待的工做線程,就能夠開始運行了;不然進入等待隊列。
爲何要用線程池:
[if !supportLists]l [endif]減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務
[if !supportLists]l [endif]能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於由於消耗過多的內存,而把服務器累趴下(每一個線程須要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)
Executors詳解:
Java裏面線程池的頂級接口是Executor,可是嚴格意義上講Executor並非一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。ThreadPoolExecutor是Executors類的底層實現。咱們先介紹下Executors。
線程池的基本思想仍是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源。
Java5中併發庫中,線程池建立線程大體能夠分爲下面三種:
//建立固定大小的線程池
ExecutorService fPool = Executors.newFixedThreadPool(3);
//建立緩存大小的線程池
ExecutorService cPool = Executors.newCachedThreadPool();
//建立單一的線程池
ExecutorService sPool = Executors.newSingleThreadExecutor();
下面咱們經過簡單示例來分別說明:
[if !supportLists]l [endif]固定大小鏈接池
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
/**
* Java線程:線程池-
*
* @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//建立一個可重用固定線程數的線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//建立實現了Runnable接口對象,Thread對象固然也實現了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//將線程放入池中進行執行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//關閉線程池
pool.shutdown();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在執行。。。");
}
}
運行結果:
pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-2正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-2正在執行。。。
從上面的運行來看,咱們Thread類都是在線程池中運行的,線程池在執行execute方法來執行Thread類中的run方法。無論execute執行幾回,線程池始終都會使用2個線程來處理。不會再去建立出其餘線程來處理run方法執行。這就是固定大小線程池。
[if !supportLists]l [endif]單任務鏈接池
咱們將上面的代碼
//建立一個可重用固定線程數的線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
改成:
//建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
ExecutorService pool = Executors.newSingleThreadExecutor();
運行結果:
pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。
運行結果看出,單任務線程池在執行execute方法來執行Thread類中的run方法。無論execute執行幾回,線程池始終都會使用單個線程來處理。
補充:在java的多線程中,一但線程關閉,就會成爲死線程。關閉後死線程就沒有辦法在啓動了。再次啓動就會出現異常信息:Exception in thread "main" java.lang.IllegalThreadStateException。那麼如何解決這個問題呢?
咱們這裏就能夠使用Executors.newSingleThreadExecutor()來再次啓動一個線程。(面試)
[if !supportLists]l [endif]可變鏈接池
//建立一個可重用固定線程數的線程池
ExecutorService pool = Executors.newFixedThreadPool(2);
改成:
//建立一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。
ExecutorService pool = Executors.newCachedThreadPool();
運行結果:
pool-1-thread-5正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-4正在執行。。。 pool-1-thread-3正在執行。。。 pool-1-thread-2正在執行。。。
運行結果看出,可變任務線程池在執行execute方法來執行Thread類中的run方法。這裏execute執行屢次,線程池就會建立出多個線程來處理Thread類中run方法。全部咱們看到鏈接池會根據執行的狀況,在程序運行時建立多個線程來處理,這裏就是可變鏈接池的特色。
那麼在上面的三種建立方式,Executors還能夠在執行某個線程時,定時操做。那麼下面咱們經過代碼簡單演示下。
[if !supportLists]l [endif]延遲鏈接池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Java線程:線程池-
*
* @author Administrator 2009-11-4 23:30:44
*/
public class Test {
public static void main(String[] args) {
//建立一個線程池,它可安排在給定延遲後運行命令或者按期地執行。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
//建立實現了Runnable接口對象,Thread對象固然也實現了Runnable接口
Thread t1 = new MyThread();
Thread t2 = new MyThread();
Thread t3 = new MyThread();
Thread t4 = new MyThread();
Thread t5 = new MyThread();
//將線程放入池中進行執行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
//使用定時執行風格的方法
pool.schedule(t4, 10, TimeUnit.MILLISECONDS);//t4和t5在10秒後執行
pool.schedule(t5, 10, TimeUnit.MILLISECONDS);
//關閉線程池
pool.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在執行。。。");
}
}
運行結果:
pool-1-thread-1正在執行。。。 pool-1-thread-2正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-1正在執行。。。 pool-1-thread-2正在執行。。。
[if !supportLists]Ø [endif]ExecutorService執行器服務
java.util.concurrent.ExecutorService 接口表示一個異步執行機制,使咱們可以在後臺執行任務。所以一個 ExecutorService 很相似於一個線程池。實際上,存在於 java.util.concurrent 包裏的 ExecutorService 實現就是一個線程池實現。
ExecutorService例子:
如下是一個簡單的ExecutorService 例子:
//線程工廠類建立出線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//執行一個線程任務
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
//線程池關閉
executorService.shutdown();
上面代碼首先使用newFixedThreadPool() 工廠方法建立一個 ExecutorService。這裏建立了一個十個線程執行任務的線程池。而後,將一個 Runnable 接口的匿名實現類傳遞給 execute() 方法。這將致使 ExecutorService 中的某個線程執行該 Runnable。這裏能夠當作一個任務分派,示例代碼中的任務分派咱們能夠理解爲:
一個線程將一個任務委派給一個ExecutorService 去異步執行。
一旦該線程將任務委派給ExecutorService,該線程將繼續它本身的執行,獨立於該任務的執行。
以下圖:
ExecutorService實現:
既然ExecutorService 是個接口,若是你想用它的話就得去使用它的實現類之一。
java.util.concurrent 包提供了 ExecutorService 接口的如下實現類:
[if !supportLists]l [endif]ThreadPoolExecutor
[if !supportLists]l [endif]ScheduledThreadPoolExecutor
ExecutorService建立:
ExecutorService 的建立依賴於你使用的具體實現。可是你也能夠使用 Executors 工廠類來建立 ExecutorService 實例。代碼示例:
ExecutorService executorService1 = Executors.newSingleThreadExecutor(); //以前Executors已介紹
ExecutorService executorService2 = Executors.newFixedThreadPool(10);
ExecutorService executorService3 = Executors.newScheduledThreadPool(10);
ExecutorService使用:
有幾種不一樣的方式來將任務委託給ExecutorService 去執行:
[if !supportLists]l [endif]execute(Runnable)
[if !supportLists]l [endif]submit(Runnable)
[if !supportLists]l [endif]submit(Callable)
[if !supportLists]l [endif]invokeAny(…)
[if !supportLists]l [endif]invokeAll(…)
接下來咱們挨個看一下這些方法。
[if !supportLists]ü [endif]execute(Runnable)
execute(Runnable) 方法要求一個 java.lang.Runnable 對象,而後對它進行異步執行。如下是使用 ExecutorService 執行一個 Runnable 的示例:
//從Executors中得到ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor();
//執行 ExecutorService 中的方法
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
//線程池關閉
executorService.shutdown();
特色:沒有辦法得知被執行的Runnable 的執行結果。若是有須要的話你得使用一個 Callable(如下將作介紹)。
[if !supportLists]ü [endif]submit(Runnable)
submit(Runnable) 方法也要求一個 Runnable 實現類,但它返回一個 Future 對象。這個 Future 對象能夠用來檢查 Runnable 是否已經執行完畢。如下是 ExecutorService submit() 示例:
//從Executors中得到ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get(); //得到執行完run方法後的返回值,這裏使用的Runnable,因此這裏沒有返回值,返回的是null。
executorService.shutdown();
[if !supportLists]ü [endif]submit(Runnable)
submit(Callable) 方法相似於 submit(Runnable) 方法,除了它所要求的參數類型以外。Callable 實例除了它的 call() 方法可以返回一個結果以外和一個 Runnable 很相像。Runnable.run() 不可以返回一個結果。Callable 的結果能夠經過 submit(Callable) 方法返回的 Future 對象進行獲取。
如下是一個ExecutorService Callable 示例:
//從Executors中得到ExecutorService
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
executorService.shutdown();
輸出:
Asynchronous Callable
future.get() = Callable Result
[if !supportLists]ü [endif]invokeAny()
invokeAny() 方法要求一系列的 Callable 或者其子接口的實例對象。調用這個方法並不會返回一個 Future,但它返回其中一個 Callable 對象的結果。沒法保證返回的是哪一個 Callable 的結果 – 只能代表其中一個已執行結束。
若是其中一個任務執行結束(或者拋了一個異常),其餘 Callable 將被取消。如下是示例代碼:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set> callables = new HashSet>();
callables.add(new Callable() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
上述代碼將會打印出給定Callable 集合中的一個的執行結果。我本身試着執行了它幾回,結果始終在變。有時是 「Task 1″,有時是 「Task 2″ 等等。
[if !supportLists]ü [endif]invokeAll()
invokeAll() 方法將調用你在集合中傳給 ExecutorService 的全部 Callable 對象。invokeAll() 返回一系列的 Future 對象,經過它們你能夠獲取每一個 Callable 的執行結果。記住,一個任務可能會因爲一個異常而結束,所以它可能沒有"成功"。沒法經過一個 Future 對象來告知咱們是兩種結束中的哪種。
如下是一個代碼示例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set> callables = new HashSet>();
callables.add(new Callable() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable() {
public String call() throws Exception {
return "Task 3";
}
});
List> futures = executorService.invokeAll(callables);
for(Future future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();
輸出結果:
future.get = Task 3
future.get = Task 1
future.get = Task 2
Executors關閉:
使用shutdown和shutdownNow能夠關閉線程池
二者的區別:
shutdown只是將空閒的線程interrupt() 了,shutdown()以前提交的任務能夠繼續執行直到結束。
shutdownNow 是interrupt全部線程, 所以大部分線程將馬上被中斷。之因此是大部分,而不是所有 ,是由於interrupt()方法能力有限。
[if !supportLists]Ø [endif]ThreadPoolExecutor線程池執行者
java.util.concurrent.ThreadPoolExecutor 是 ExecutorService 接口的一個實現。ThreadPoolExecutor 使用其內部池中的線程執行給定任務(Callable 或者 Runnable)。
ThreadPoolExecutor 包含的線程池可以包含不一樣數量的線程。池中線程的數量由如下變量決定:
[if !supportLists]l [endif]corePoolSize
[if !supportLists]l [endif]maximumPoolSize
當一個任務委託給線程池時,若是池中線程數量低於corePoolSize,一個新的線程將被建立,即便池中可能尚有空閒線程。若是內部任務隊列已滿,並且有至少 corePoolSize 正在運行,可是運行線程的數量低於 maximumPoolSize,一個新的線程將被建立去執行該任務。
ThreadPoolExecutor 圖解:
建立ThreadPoolExecutor:
int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 5000;
ExecutorService threadPoolExecutor =
new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()
);
構造方法參數列表解釋:
corePoolSize -池中所保存的線程數,包括空閒線程。
maximumPoolSize -池中容許的最大線程數。
keepAliveTime -當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
unit - keepAliveTime參數的時間單位。
workQueue -執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。
[if !supportLists]Ø [endif]ScheduledPoolExecutor定時線程池執行者
java.util.concurrent.ScheduledExecutorService 是一個 ExecutorService, 它可以將任務延後執行,或者間隔固定時間屢次執行。 任務由一個工做者線程異步執行,而不是由提交任務給 ScheduledExecutorService 的那個線程執行。
ScheduledPoolExecutor例子:
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(5);
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
public Object call() throws Exception {
System.out.println("Executed!");
return "Called!";
}
},
5,
TimeUnit.SECONDS);//5秒後執行
首先一個內置5 個線程的 ScheduledExecutorService 被建立。以後一個 Callable 接口的匿名類示例被建立而後傳遞給 schedule() 方法。後邊的倆參數定義了 Callable 將在 5 秒鐘以後被執行。
ScheduledExecutorService的實現:
ScheduledExecutorService 是一個接口,你要用它的話就得使用 java.util.concurrent 包裏對它的某個實現類。ScheduledExecutorService 具備如下實現類:ScheduledThreadPoolExecutor
建立一個ScheduledExecutorService:
如何建立一個ScheduledExecutorService 取決於你採用的它的實現類。可是你也能夠使用 Executors 工廠類來建立一個 ScheduledExecutorService 實例。好比:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
ScheduledExecutorService的使用:
一旦你建立了一個ScheduledExecutorService,你能夠經過調用它的如下方法:
[if !supportLists]l [endif]schedule (Callable task, long delay, TimeUnit timeunit)
[if !supportLists]l [endif]schedule (Runnable task, long delay, TimeUnit timeunit)
[if !supportLists]l [endif]scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
[if !supportLists]l [endif]scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)
下面咱們就簡單看一下這些方法。
[if !supportLists]ü [endif]schedule (Callable task, long delay, TimeUnit timeunit)
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(5);
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
public Object call() throws Exception {
System.out.println("Executed!");
return "Called!";
}
},
5,
TimeUnit.SECONDS);
System.out.println("result = " + scheduledFuture.get());
scheduledExecutorService.shutdown();
輸出結果:
Executed! result = Called!
[if !supportLists]ü [endif]schedule (Runnable task, long delay, TimeUnit timeunit)
這一方法規劃一個任務將被按期執行。該任務將會在首個initialDelay 以後獲得執行,而後每一個 period 時間以後重複執行。
若是給定任務的執行拋出了異常,該任務將再也不執行。若是沒有任何異常的話,這個任務將會持續循環執行到ScheduledExecutorService 被關閉。
若是一個任務佔用了比計劃的時間間隔更長的時候,下一次執行將在當前執行結束執行纔開始。計劃任務在同一時間不會有多個線程同時執行。
[if !supportLists]ü [endif]scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)
這一方法規劃一個任務將被按期執行。該任務將會在首個initialDelay 以後獲得執行,而後每一個 period 時間以後重複執行。
若是給定任務的執行拋出了異常,該任務將再也不執行。若是沒有任何異常的話,這個任務將會持續循環執行到ScheduledExecutorService 被關閉。
若是一個任務佔用了比計劃的時間間隔更長的時候,下一次執行將在當前執行結束執行纔開始。計劃任務在同一時間不會有多個線程同時執行。
[if !supportLists]ü [endif]scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)
除了period 有不一樣的解釋以外這個方法和 scheduleAtFixedRate() 很是像。scheduleAtFixedRate() 方法中,period 被解釋爲前一個執行的開始和下一個執行的開始之間的間隔時間。而在本方法中,period 則被解釋爲前一個執行的結束和下一個執行的結束之間的間隔。所以這個延遲是執行結束之間的間隔,而不是執行開始之間的間隔。
ScheduledExecutorService的關閉:
正如ExecutorService,在你使用結束以後你須要把 ScheduledExecutorService 關閉掉。不然他將致使 JVM 繼續運行,即便全部其餘線程已經全被關閉。
你能夠使用從ExecutorService 接口繼承來的 shutdown() 或 shutdownNow() 方法將 ScheduledExecutorService 關閉。參見 ExecutorService 關閉部分以獲取更多信息。
[if !supportLists]Ø [endif]ForkJoinPool合併和分叉(線程池)
ForkJoinPool 在 Java 7 中被引入。它和 ExecutorService 很類似,除了一點不一樣。ForkJoinPool 讓咱們能夠很方便地把任務分裂成幾個更小的任務,這些分裂出來的任務也將會提交給 ForkJoinPool。任務能夠繼續分割成更小的子任務,只要它還能分割。可能聽起來有些抽象,所以本節中咱們將會解釋 ForkJoinPool 是如何工做的,還有任務分割是如何進行的。
合併和分叉的解釋:
在咱們開始看ForkJoinPool 以前咱們先來簡要解釋一下分叉和合並的原理。分叉和合並原理包含兩個遞歸進行的步驟。兩個步驟分別是分叉步驟和合並步驟。
分叉:
一個使用了分叉和合並原理的任務能夠將本身分叉(分割)爲更小的子任務,這些子任務能夠被併發執行。以下圖所示:
經過把本身分割成多個子任務,每一個子任務能夠由不一樣的CPU 並行執行,或者被同一個 CPU 上的不一樣線程執行。只有當給的任務過大,把它分割成幾個子任務纔有意義。把任務分割成子任務有必定開銷,所以對於小型任務,這個分割的消耗可能比每一個子任務併發執行的消耗還要大。
何時把一個任務分割成子任務是有意義的,這個界限也稱做一個閥值。這要看每一個任務對有意義閥值的決定。很大程度上取決於它要作的工做的種類。
合併:
當一個任務將本身分割成若干子任務以後,該任務將進入等待全部子任務的結束之中。一旦子任務執行結束,該任務能夠把全部結果合併到同一個結果。圖示以下:
固然,並不是全部類型的任務都會返回一個結果。若是這個任務並不返回一個結果,它只需等待全部子任務執行完畢。也就不須要結果的合併啦。
因此咱們能夠將ForkJoinPool 是一個特殊的線程池,它的設計是爲了更好的配合 分叉-和-合併 任務分割的工做。ForkJoinPool 也在 java.util.concurrent 包中,其完整類名爲 java.util.concurrent.ForkJoinPool。
建立一個ForkJoinPool:
你能夠經過其構造子建立一個ForkJoinPool。做爲傳遞給 ForkJoinPool 構造子的一個參數,你能夠定義你指望的並行級別。並行級別表示你想要傳遞給 ForkJoinPool 的任務所需的線程或 CPU 數量。如下是一個 ForkJoinPool 示例:
//建立了一個並行級別爲 4 的 ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
提交任務到ForkJoinPool:
就像提交任務到ExecutorService 那樣,把任務提交到 ForkJoinPool。你能夠提交兩種類型的任務。一種是沒有任何返回值的(一個 「行動」),另外一種是有返回值的(一個」任務」)。這兩種類型分別由 RecursiveAction 和 RecursiveTask 表示。接下來介紹如何使用這兩種類型的任務,以及如何對它們進行提交。
RecursiveAction:
RecursiveAction 是一種沒有任何返回值的任務。它只是作一些工做,好比寫數據到磁盤,而後就退出了。一個 RecursiveAction 能夠把本身的工做分割成更小的幾塊,這樣它們能夠由獨立的線程或者 CPU 執行。你能夠經過繼承來實現一個 RecursiveAction。示例以下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveAction;
public class MyRecursiveAction extends RecursiveAction {
private long workLoad = 0;
public MyRecursiveAction(long workLoad) {
this.workLoad = workLoad;
}
@Override
protected void compute() {
//if work is above threshold, break tasks up into smaller tasks
//翻譯:若是工做超過門檻,把任務分解成更小的任務
if(this.workLoad > 16) {
System.out.println("Splitting workLoad : " + this.workLoad);
List subtasks =
new ArrayList();
subtasks.addAll(createSubtasks());
for(RecursiveAction subtask : subtasks){
subtask.fork();
}
} else {
System.out.println("Doing workLoad myself: " + this.workLoad);
}
}
private List createSubtasks() {
List subtasks =
new ArrayList();
MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2);
MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2);
subtasks.add(subtask1);
subtasks.add(subtask2);
return subtasks;
}
}
例子很簡單。MyRecursiveAction 將一個虛構的 workLoad 做爲參數傳給本身的構造子。若是 workLoad 高於一個特定閥值,該工做將被分割爲幾個子工做,子工做繼續分割。若是 workLoad 低於特定閥值,該工做將由 MyRecursiveAction 本身執行。你能夠這樣規劃一個 MyRecursiveAction 的執行:
//建立了一個並行級別爲 4 的 ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
//建立一個沒有返回值的任務
MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24);
//ForkJoinPool執行任務
forkJoinPool.invoke(myRecursiveAction);
運行結果:
Splitting workLoad : 24
Doing workLoad myself: 12
RecursiveTask:
RecursiveTask 是一種會返回結果的任務。它能夠將本身的工做分割爲若干更小任務,並將這些子任務的執行結果合併到一個集體結果。能夠有幾個水平的分割和合並。如下是一個 RecursiveTask 示例:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
public class MyRecursiveTask extends RecursiveTask {
private long workLoad = 0;
public MyRecursiveTask(long workLoad) {
this.workLoad = workLoad;
}
protected Long compute() {
//if work is above threshold, break tasks up into smaller tasks
if(this.workLoad > 16) {
System.out.println("Splitting workLoad : " + this.workLoad);
List subtasks =
new ArrayList();
subtasks.addAll(createSubtasks());
for(MyRecursiveTask subtask : subtasks){
subtask.fork();
}
long result = 0;
for(MyRecursiveTask subtask : subtasks) {
result += subtask.join();
}
return result;
} else {
System.out.println("Doing workLoad myself: " + this.workLoad);
return workLoad * 3;
}
}
private List createSubtasks() {
List subtasks =
new ArrayList();
MyRecursiveTask subtask1 = new MyRecursiveTask(this.workLoad / 2);
MyRecursiveTask subtask2 = new MyRecursiveTask(this.workLoad / 2);
subtasks.add(subtask1);
subtasks.add(subtask2);
return subtasks;
}
}
除了有一個結果返回以外,這個示例和RecursiveAction 的例子很像。MyRecursiveTask 類繼承自 RecursiveTask,這也就意味着它將返回一個 Long 類型的結果。
MyRecursiveTask 示例也會將工做分割爲子任務,並經過 fork() 方法對這些子任務計劃執行。此外,本示例還經過調用每一個子任務的 join() 方法收集它們返回的結果。子任務的結果隨後被合併到一個更大的結果,並最終將其返回。對於不一樣級別的遞歸,這種子任務的結果合併可能會發生遞歸。
你能夠這樣規劃一個RecursiveTask:
//建立了一個並行級別爲 4 的 ForkJoinPool
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
//建立一個有返回值的任務
MyRecursiveTask myRecursiveTask = new MyRecursiveTask(128);
//線程池執行並返回結果
long mergedResult = forkJoinPool.invoke(myRecursiveTask);
System.out.println("mergedResult = " + mergedResult);
注意: ForkJoinPool.invoke() 方法的調用來獲取最終執行結果的。
[if !supportLists]B. [endif]併發隊列-阻塞隊列
經常使用的併發隊列有阻塞隊列和非阻塞隊列,前者使用鎖實現,後者則使用CAS非阻塞算法實現。
PS:至於非阻塞隊列是靠CAS非阻塞算法,在這裏再也不介紹,你們只用知道,Java非阻塞隊列是使用CAS算法來實現的就能夠。感興趣的童鞋能夠維基網上自行學習.
下面咱們先介紹阻塞隊列。
阻塞隊列:
阻塞隊列(BlockingQueue)是Java util.concurrent包下重要的數據結構,BlockingQueue提供了線程安全的隊列訪問方式:當阻塞隊列進行插入數據時,若是隊列已滿,線程將會阻塞等待直到隊列非滿;從阻塞隊列取數據時,若是隊列已空,線程將會阻塞等待直到隊列非空。併發包下不少高級同步類的實現都是基於BlockingQueue實現的。
[if !supportLists]Ø [endif]BlockingQueue阻塞隊列
BlockingQueue 一般用於一個線程生產對象,而另一個線程消費這些對象的場景。下圖是對這個原理的闡述:
一個線程往裏邊放,另一個線程從裏邊取的一個BlockingQueue。
一個線程將會持續生產新對象並將其插入到隊列之中,直到隊列達到它所能容納的臨界點。也就是說,它是有限的。若是該阻塞隊列到達了其臨界點,負責生產的線程將會在往裏邊插入新對象時發生阻塞。它會一直處於阻塞之中,直到負責消費的線程從隊列中拿走一個對象。負責消費的線程將會一直從該阻塞隊列中拿出對象。若是消費線程嘗試去從一個空的隊列中提取對象的話,這個消費線程將會處於阻塞之中,直到一個生產線程把一個對象丟進隊列。
BlockingQueue的方法:
BlockingQueue 具備 4 組不一樣的方法用於插入、移除以及對隊列中的元素進行檢查。若是請求的操做不能獲得當即執行的話,每一個方法的表現也不一樣。這些方法以下:
阻塞隊列提供了四種處理方法:
方法\處理方式拋出異常返回特殊值一直阻塞超時退出
插入方法add(e)offer(e)put(e)offer(e,time,unit)
移除方法remove()poll()take()poll(time,unit)
檢查方法element()peek()不可用不可用
四組不一樣的行爲方式解釋:
拋異常:若是試圖的操做沒法當即執行,拋一個異常。
特定值:若是試圖的操做沒法當即執行,返回一個特定的值(經常是 true / false)。
阻塞:若是試圖的操做沒法當即執行,該方法調用將會發生阻塞,直到可以執行。
超時:若是試圖的操做沒法當即執行,該方法調用將會發生阻塞,直到可以執行,但等待時間不會超過給定值。返回一個特定值以告知該操做是否成功(典型的是true / false)。
沒法向一個BlockingQueue 中插入 null。若是你試圖插入 null,BlockingQueue 將會拋出一個 NullPointerException.
BlockingQueue的實現類:
BlockingQueue 是個接口,你須要使用它的實現之一來使用BlockingQueue,Java.util.concurrent包下具備如下 BlockingQueue 接口的實現類:
[if !supportLists]l [endif]ArrayBlockingQueue:ArrayBlockingQueue 是一個有界的阻塞隊列,其內部實現是將對象放到一個數組裏。有界也就意味着,它不可以存儲無限多數量的元素。它有一個同一時間可以存儲元素數量的上限。你能夠在對其初始化的時候設定這個上限,但以後就沒法對這個上限進行修改了(譯者注:由於它是基於數組實現的,也就具備數組的特性:一旦初始化,大小就沒法修改)。
[if !supportLists]l [endif]DelayQueue:DelayQueue 對元素進行持有直到一個特定的延遲到期。注入其中的元素必須實現 java.util.concurrent.Delayed 接口。
[if !supportLists]l [endif]LinkedBlockingQueue:LinkedBlockingQueue 內部以一個鏈式結構(連接節點)對其元素進行存儲。若是須要的話,這一鏈式結構能夠選擇一個上限。若是沒有定義上限,將使用 Integer.MAX_VALUE 做爲上限。
[if !supportLists]l [endif]PriorityBlockingQueue:PriorityBlockingQueue 是一個無界的併發隊列。它使用了和類 java.util.PriorityQueue 同樣的排序規則。你沒法向這個隊列中插入 null 值。全部插入到 PriorityBlockingQueue 的元素必須實現 java.lang.Comparable 接口。所以該隊列中元素的排序就取決於你本身的 Comparable 實現。
[if !supportLists]l [endif]SynchronousQueue:SynchronousQueue 是一個特殊的隊列,它的內部同時只可以容納單個元素。若是該隊列已有一元素的話,試圖向隊列中插入一個新元素的線程將會阻塞,直到另外一個線程將該元素從隊列中抽走。一樣,若是該隊列爲空,試圖向隊列中抽取一個元素的線程將會阻塞,直到另外一個線程向隊列中插入了一條新的元素。據此,把這個類稱做一個隊列顯然是誇大其詞了。它更多像是一個匯合點。
[if !supportLists]Ø [endif]ArrayBlockingQueue阻塞隊列
ArrayBlockingQueue類圖
如上圖ArrayBlockingQueue內部有個數組items用來存放隊列元素,putindex下標標示入隊元素下標,takeIndex是出隊下標,count統計隊列元素個數,從定義可知道並無使用volatile修飾,這是由於訪問這些變量使用都是在鎖塊內,並不存在可見性問題。另外有個獨佔鎖lock用來對出入隊操做加鎖,這致使同時只有一個線程能夠訪問入隊出隊,另外notEmpty,notFull條件變量用來進行出入隊的同步。
另外構造函數必須傳入隊列大小參數,因此爲有界隊列,默認是Lock爲非公平鎖。
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
ps:
所謂公平鎖:就是在併發環境中,每一個線程在獲取鎖時會先查看此鎖維護的等待隊列,若是爲空,或者當前線程線程是等待隊列的第一個,就佔有鎖,不然就會加入到等待隊列中,之後會按照FIFO的規則從隊列中取到本身。
非公平鎖:比較粗魯,上來就直接嘗試佔有鎖,若是嘗試失敗,就再採用相似公平鎖那種方式
ArrayBlockingQueue方法
[if !supportLists]ü [endif]offer方法
在隊尾插入元素,若是隊列滿則返回false,否者入隊返回true。
public boolean offer(E e) {
//e爲null,則拋出NullPointerException異常
checkNotNull(e);
//獲取獨佔鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
//若是隊列滿則返回false
if (count == items.length)
return false;
else {
//否者插入元素
insert(e);
return true;
}
} finally {
//釋放鎖
lock.unlock();
}
}
private void insert(E x) {
//元素入隊
items[putIndex] = x;
//計算下一個元素應該存放的下標
putIndex = inc(putIndex);
++count;
notEmpty.signal();
}
//循環隊列,計算下標
final int inc(int i) {
return (++i == items.length) ? 0 : i;
}
這裏因爲在操做共享變量前加了鎖,因此不存在內存不可見問題,加過鎖後獲取的共享變量都是從主內存獲取的,而不是在CPU緩存或者寄存器裏面的值,釋放鎖後修改的共享變量值會刷新會主內存中。
另外這個隊列是使用循環數組實現,因此計算下一個元素存放下標時候有些特殊。另外insert後調用 notEmpty.signal();是爲了激活調用notEmpty.await()阻塞後放入notEmpty條件隊列中的線程。
[if !supportLists]ü [endif]Put操做
在隊列尾部添加元素,若是隊列滿則等待隊列有空位置插入後返回。
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//獲取可被中斷鎖
lock.lockInterruptibly();
try {
//若是隊列滿,則把當前線程放入notFull管理的條件隊列
while (count == items.length)
notFull.await();
//插入元素
insert(e);
} finally {
lock.unlock();
}
}
須要注意的是若是隊列滿了那麼當前線程會阻塞,知道出隊操做調用了notFull.signal方法激活該線程。代碼邏輯很簡單,可是這裏須要思考一個問題爲啥調用lockInterruptibly方法而不是Lock方法。個人理解是由於調用了條件變量的await()方法,而await()方法會在中斷標誌設置後拋出InterruptedException異常後退出,因此還不如在加鎖時候先看中斷標誌是否是被設置了,若是設置了直接拋出InterruptedException異常,就不用再去獲取鎖了。而後看了其餘併發類裏面凡是調用了await的方法獲取鎖時候都是使用的lockInterruptibly方法而不是Lock也驗證了這個想法。
[if !supportLists]ü [endif]Poll操做
從隊頭獲取並移除元素,隊列爲空,則返回null。
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//當前隊列爲空則返回null,否者
return (count == 0) ? null : extract();
} finally {
lock.unlock();
}
}
private E extract() {
final Object[] items = this.items;
//獲取元素值
E x = this.cast(items[takeIndex]);
//數組中值值爲null;
items[takeIndex] = null;
//隊頭指針計算,隊列元素個數減一
takeIndex = inc(takeIndex);
--count;
//發送信號激活notFull條件隊列裏面的線程
notFull.signal();
return x;
}
[if !supportLists]ü [endif]Take操做
從隊頭獲取元素,若是隊列爲空則阻塞直到隊列有元素。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//隊列爲空,則等待,直到隊列有元素
while (count == 0)
notEmpty.await();
return extract();
} finally {
lock.unlock();
}
}
須要注意的是若是隊列爲空,當前線程會被掛起放到notEmpty的條件隊列裏面,直到入隊操做執行調用notEmpty.signal後當前線程纔會被激活,await纔會返回。
[if !supportLists]ü [endif]Peek操做
返回隊列頭元素但不移除該元素,隊列爲空,返回null。
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//隊列爲空返回null,否者返回頭元素
return (count == 0) ? null : itemAt(takeIndex);
} finally {
lock.unlock();
}
}
final E itemAt(int i) {
return this.cast(items[i]);
}
[if !supportLists]ü [endif]Size操做
獲取隊列元素個數,很是精確由於計算size時候加了獨佔鎖,其餘線程不能入隊或者出隊或者刪除元素。
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
[if !supportLists]ü [endif]ArrayBlockingQueue小結
ArrayBlockingQueue經過使用全局獨佔鎖實現同時只能有一個線程進行入隊或者出隊操做,這個鎖的粒度比較大,有點相似在方法上添加synchronized的意味。其中offer,poll操做經過簡單的加鎖進行入隊出隊操做,而put,take則使用了條件變量實現若是隊列滿則等待,若是隊列空則等待,而後分別在出隊和入隊操做中發送信號激活等待線程實現同步。另外相比LinkedBlockingQueue,ArrayBlockingQueue的size操做的結果是精確的,由於計算前加了全局鎖。
ArrayBlockingQueue示例
需求:在多線程操做下,一個數組中最多隻能存入3個元素。多放入不能夠存入數組,或等待某線程對數組中某個元素取走才能放入,要求使用java的多線程來實現。(面試)
代碼實現:
public class BlockingQueueTest {
public static void main(String[] args) {
final BlockingQueue queue = new ArrayBlockingQueue(3);
for(int i=0;i<2;i++){
new Thread(){
public void run(){
while(true){
try {
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "準備放數據!");
queue.put(1);
System.out.println(Thread.currentThread().getName() + "已經放了數據," +
"隊列目前有" + queue.size() + "個數據");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
new Thread(){
public void run(){
while(true){
try {
//將此處的睡眠時間分別改成100和1000,觀察運行結果
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "準備取數據!");
System.err.println(queue.take());
System.out.println(Thread.currentThread().getName() + "已經取走數據," +
"隊列目前有" + queue.size() + "個數據");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
輸出結果:
Thread-0準備放數據!
Thread-0已經放了數據,隊列目前有1個數據
Thread-0準備放數據!
Thread-0已經放了數據,隊列目前有2個數據
Thread-1準備放數據!
Thread-1已經放了數據,隊列目前有3個數據
Thread-2準備取數據!
Thread-2已經取走數據,隊列目前有3個數據
Thread-0準備放數據!
Thread-1準備放數據!
Thread-2準備取數據!
Thread-2已經取走數據,隊列目前有3個數據
…………………
[if !supportLists]Ø [endif]LinkedBlockingQueue阻塞隊列
LinkedBlockingQueue類圖
LinkedBlockingQueue中也有兩個Node分別用來存放首尾節點,而且裏面有個初始值爲0的原子變量count用來記錄隊列元素個數,另外裏面有兩個ReentrantLock的獨佔鎖,分別用來控制元素入隊和出隊加鎖,其中takeLock用來控制同時只有一個線程能夠從隊列獲取元素,其餘線程必須等待,putLock控制同時只能有一個線程能夠獲取鎖去添加元素,其餘線程必須等待。另外notEmpty和notFull用來實現入隊和出隊的同步。 另外因爲出入隊是兩個非公平獨佔鎖,因此能夠同時又一個線程入隊和一個線程出隊,其實這個是個生產者-消費者模型,以下類圖:
/**經過take取出進行加鎖、取出 */
private final ReentrantLock takeLock = new ReentrantLock();
/**等待中的隊列等待取出 */
private final Condition notEmpty = takeLock.newCondition();
/*經過put放置進行加鎖、放置*/
private final ReentrantLock putLock = new ReentrantLock();
/**等待中的隊列等待放置 */
private final Condition notFull = putLock.newCondition();
/*記錄集合中的個數(計數器) */
private final AtomicInteger count = new AtomicInteger(0);
隊列的容量:
//隊列初始容量,Integer最大值
public static final int MAX_VALUE = 0x7fffffff;
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//初始化首尾節點
last = head = new Node(null);
}
如圖默認隊列容量爲0x7fffffff;用戶也能夠本身指定容量。
LinkedBlockingQueue方法
ps:下面介紹LinkedBlockingQueue用到不少Lock對象。詳細能夠查找Lock對象的介紹
[if !supportLists]ü [endif]帶時間的Offer操做-生產者
在ArrayBlockingQueue中已經簡單介紹了Offer()方法,LinkedBlocking的Offer方法相似,在此就不過多去介紹。此次咱們從介紹下帶時間的Offer方法
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//空元素拋空指針異常
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//獲取可被中斷鎖,只有一個線程克獲取
putLock.lockInterruptibly();
try {
//若是隊列滿則進入循環
while (count.get() == capacity) {
//nanos<=0直接返回
if (nanos <= 0)
return false;
//否者調用await進行等待,超時則返回<=0(1)
nanos = notFull.awaitNanos(nanos);
}
//await在超時時間內返回則添加元素(2)
enqueue(new Node(e));
c = count.getAndIncrement();
//隊列不滿則激活其餘等待入隊線程(3)
if (c + 1 < capacity)
notFull.signal();
} finally {
//釋放鎖
putLock.unlock();
}
//c==0說明隊列裏面有一個元素,這時候喚醒出隊線程(4)
if (c == 0)
signalNotEmpty();
return true;
}
private void enqueue(Node node) {
last = last.next = node;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
[if !supportLists]ü [endif]帶時間的poll操做-消費者
獲取並移除隊首元素,在指定的時間內去輪詢隊列看有沒有首元素有則返回,否者超時後返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//出隊線程獲取獨佔鎖
takeLock.lockInterruptibly();
try {
//循環直到隊列不爲空
while (count.get() == 0) {
//超時直接返回null
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
//出隊,計數器減一
x = dequeue();
c = count.getAndDecrement();
//若是出隊前隊列不爲空則發送信號,激活其餘阻塞的出隊線程
if (c > 1)
notEmpty.signal();
} finally {
//釋放鎖
takeLock.unlock();
}
//當前隊列容量爲最大值-1則激活入隊線程。
if (c == capacity)
signalNotFull();
return x;
}
首先獲取獨佔鎖,而後進入循環噹噹前隊列有元素纔會退出循環,或者超時了,直接返回null。
超時前退出循環後,就從隊列移除元素,而後計數器減去一,若是減去1前隊列元素大於1則說明當前移除後隊列還有元素,那麼就發信號激活其餘可能阻塞到當前條件信號的線程。
最後若是減去1前隊列元素個數=最大值,那麼移除一個後會騰出一個空間來,這時候能夠激活可能存在的入隊阻塞線程。
[if !supportLists]ü [endif]put操做-生產者
與帶超時時間的poll相似不一樣在於put時候若是當前隊列滿了它會一直等待其餘線程調用notFull.signal纔會被喚醒。
[if !supportLists]ü [endif]take操做-消費者
與帶超時時間的poll相似不一樣在於take時候若是當前隊列空了它會一直等待其餘線程調用notEmpty.signal()纔會被喚醒。
[if !supportLists]ü [endif]size操做-消費者
當前隊列元素個數,如代碼直接使用原子變量count獲取。
public int size() {
return count.get();
}
[if !supportLists]ü [endif]peek操做
獲取可是不移除當前隊列的頭元素,沒有則返回null。
public E peek() {
//隊列空,則返回null
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
[if !supportLists]ü [endif]remove操做
刪除隊列裏面的一個元素,有則刪除返回true,沒有則返回false,在刪除操做時候因爲要遍歷隊列因此加了雙重鎖,也就是在刪除過程當中不容許入隊也不容許出隊操做。
public boolean remove(Object o) {
if (o == null) return false;
//雙重加鎖
fullyLock();
try {
//遍歷隊列找則刪除返回true
for (Node trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
//找不到返回false
return false;
} finally {
//解鎖
fullyUnlock();
}
}
void fullyLock() {
putLock.lock();
takeLock.lock();
}
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
void unlink(Node p, Node trail) {
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
//若是當前隊列滿,刪除後,也不忘記最快的喚醒等待的線程
if (count.getAndDecrement() == capacity)
notFull.signal();
}
[if !supportLists]ü [endif]開源框架的使用
tomcat中任務隊列TaskQueue。
類結構圖:
可知TaskQueue繼承了LinkedBlockingQueue而且泛化類型固定了爲Runnalbe.重寫了offer,poll,take方法。
tomcat中有個線程池ThreadPoolExecutor,在NIOEndPoint中當acceptor線程接受到請求後,會把任務放入隊列,而後poller 線程從隊列裏面獲取任務,而後就把任務放入線程池執行。這個ThreadPoolExecutor中的的一個參數就是TaskQueue。
先看看ThreadPoolExecutor的參數若是是普通LinkedBlockingQueue是怎麼樣的執行邏輯: 當調用線程池方法execute() 方法添加一個任務時:
[if !supportLists]l [endif]若是當前運行的線程數量小於corePoolSize,則建立新線程運行該任務
[if !supportLists]l [endif]若是當前運行的線程數量大於或等於corePoolSize,則將這個任務放入阻塞隊列。
[if !supportLists]l [endif]若是當前隊列滿了,而且當前運行的線程數量小於maximumPoolSize,則建立新線程運行該任務;
[if !supportLists]l [endif]若是當前隊列滿了,而且當前運行的線程數量大於或等於maximumPoolSize,那麼線程池將會拋出RejectedExecutionException異常。
若是線程執行完了當前任務,那麼會去隊列裏面獲取一個任務來執行,若是任務執行完了,而且當前線程數大於corePoolSize,那麼會根據線程空閒時間keepAliveTime回收一些線程保持線程池corePoolSize個線程。
首先看下線程池中exectue添加任務時候的邏輯:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//當前工做線程個數小於core個數則開新線程執行(1)
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//放入隊列(2)
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//若是隊列滿了則開新線程,可是個數要不超過最大值,超過則返回false
//而後執行reject handler(3)
else if (!addWorker(command, false))
reject(command);
}
可知噹噹前工做線程個數爲corePoolSize後,若是在來任務會把任務添加到隊列,隊列滿了或者入隊失敗了則開啓新線程。
而後看看TaskQueue中重寫的offer方法的邏輯:
public boolean offer(Runnable o) {
//若是parent爲null則直接調用父類方法
if (parent==null) return super.offer(o);
//若是當前線程池中線程個數達到最大,則無條件調用父類方法
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
//若是當前提交的任務小於當前線程池線程數,說明線程用不完,不必從新開線程
if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
//若是當前線程池線程個數>core個數可是小於最大個數,則開新線程代替放入隊列
if (parent.getPoolSize()
//到了這裏,無條件調用父類
return super.offer(o);
}
可知parent.getPoolSize()
LinkedBlockingQueue安全分析總結
仔細思考下阻塞隊列是如何實現併發安全的維護隊列鏈表的,先分析下簡單的狀況就是當隊列裏面有多個元素時候,因爲同時只有一個線程(經過獨佔鎖putLock實現)入隊元素而且是操做last節點(,而同時只有一個出隊線程(經過獨佔鎖takeLock實現)操做head節點,因此不存在併發安全問題。
考慮當隊列爲空的時候隊列狀態爲:
這時候假如一個線程調用了take方法,因爲隊列爲空,因此count.get()==0因此當前線程會調用notEmpty.await()把本身掛起,而且放入notEmpty的條件隊列,而且釋放當前條件變量關聯的經過takeLock.lockInterruptibly()獲取的獨佔鎖。因爲釋放了鎖,因此這時候其餘線程調用take時候就會經過takeLock.lockInterruptibly()獲取獨佔鎖,而後一樣阻塞到notEmpty.await(),一樣會被放入notEmpty的條件隊列,也就說在隊列爲空的狀況下可能會有多個線程由於調用take被放入了notEmpty的條件隊列。
這時候若是有一個線程調用了put方法,那麼就會調用enqueue操做,該操做會在last節點後面添加新元素而且設置last爲新節點。而後count.getAndIncrement()先獲取當前隊列元個數爲0保存到c,而後自增count爲1,因爲c==0因此調用signalNotEmpty激活notEmpty的條件隊列裏面的阻塞時間最長的線程,這時候take中調用notEmpty.await()的線程會被激活await內部會從新去獲取獨佔鎖獲取成功則返回,否者被放入AQS的阻塞隊列,若是獲取成功,那麼count.get() >0由於可能多個線程put了,因此調用dequeue從隊列獲取元素(這時候必定能夠獲取到),而後調用c = count.getAndDecrement() 把當前計數返回後並減去1,若是c>1 說明當前隊列還有其餘元素,那麼就調用 notEmpty.signal()去激活 notEmpty的條件隊列裏面的其餘阻塞線程。
考慮當隊列滿的時候:
當隊列滿的時候調用put方法時候,會因爲notFull.await()當前線程被阻塞放入notFull管理的條件隊列裏面,同理可能會有多個調用put方法的線程都放到了notFull的條件隊列裏面。
這時候若是有一個線程調用了take方法,調用dequeue()出隊一個元素,c = count.getAndDecrement();count值減一;c==capacity;如今隊列有一個空的位置,因此調用signalNotFull()激活notFull條件隊列裏面等待最久的一個線程。
LinkedBlockingQueue簡單示例
併發庫中的BlockingQueue是一個比較好玩的類,顧名思義,就是阻塞隊列。該類主要提供了兩個方法put()和take(),前者將一個對象放到隊列中,若是隊列已經滿了,就等待直到有空閒節點;後者從head取一個對象,若是沒有對象,就等待直到有可取的對象。
下面的例子比較簡單,一個讀線程,用於將要處理的文件對象添加到阻塞隊列中,另外四個寫線程用於取出文件對象,爲了模擬寫操做耗時長的特色,特讓線程睡眠一段隨機長度的時間。另外,該Demo也使用到了線程池和原子整型 (AtomicInteger),AtomicInteger能夠在併發狀況下達到原子化更新,避免使用了synchronized,並且性能很是高。由 於阻塞隊列的put和take操做會阻塞,爲了使線程退出,特在隊列中添加了一個「標識」,算法中也叫「哨兵」,當發現這個哨兵後,寫線程就退出。
固然線程池也要顯式退出了。
package concurrent; import java.io.File; import java.io.FileFilter; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; public class TestBlockingQueue { static long randomTime() { return (long) (Math.random() * 1000); } public static void main(String[] args) { //能容納100個文件 final BlockingQueue queue = new LinkedBlockingQueue(100); //線程池 final ExecutorService exec = Executors.newFixedThreadPool(5); final File root = new File("F:\\JavaLib"); //完成標誌 final File exitFile = new File(""); //讀個數 final AtomicInteger rc = new AtomicInteger(); //寫個數 final AtomicInteger wc = new AtomicInteger(); //讀線程 Runnable read = new Runnable() { public void run() { scanFile(root); scanFile(exitFile); } public void scanFile(File file) { if (file.isDirectory()) { File[] files = file.listFiles(new FileFilter() { public boolean accept(File pathname) { return pathname.isDirectory() || pathname.getPath().endsWith(".java"); } }); for (File one : files) scanFile(one); } else { try { int index = rc.incrementAndGet(); System.out.println("Read0: " + index + " " + file.getPath()); queue.put(file); } catch (InterruptedException e) { } } } }; exec.submit(read);//四個寫線程 for (int index = 0; index < 4; index++) { // write thread final int NO = index; Runnable write = new Runnable() { String threadName = "Write" + NO; public void run() { while (true) { try { Thread.sleep(randomTime()); int index = wc.incrementAndGet(); File file = queue.take();//隊列已經無對象 if (file == exitFile) {//再次添加"標誌",以讓其餘線程正常退出 queue.put(exitFile); break; } System.out.println(threadName + ": " + index + " " + file.getPath()); } catch (InterruptedException e) { } } } }; exec.submit(write); } exec.shutdown(); }}
[if !supportLists]Ø [endif]PriorityBlockingQueue無界阻塞優先級隊列
PriorityBlockingQueue是帶優先級的無界阻塞隊列,每次出隊都返回優先級最高的元素,是二叉樹最小堆的實現,研究過數組方式存放最小堆節點的都知道,直接遍歷隊列元素是無序的。
PriorityBlockingQueue類圖結構
如圖PriorityBlockingQueue內部有個數組queue用來存放隊列元素,size用來存放隊列元素個數,allocationSpinLockOffset是用來在擴容隊列時候作cas的,目的是保證只有一個線程能夠進行擴容。
因爲這是一個優先級隊列因此有個比較器comparator用來比較元素大小。lock獨佔鎖對象用來控制同時只能有一個線程能夠進行入隊出隊操做。notEmpty條件變量用來實現take方法阻塞模式。這裏沒有notFull 條件變量是由於這裏的put操做是非阻塞的,爲啥要設計爲非阻塞的是由於這是無界隊列。最後PriorityQueue q用來搞序列化的。
以下構造函數,默認隊列容量爲11,默認比較器爲null;
private static final int DEFAULT_INITIAL_CAPACITY = 11;
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
}
public PriorityBlockingQueue(int initialCapacity) {
this(initialCapacity, null);
}
public PriorityBlockingQueue(int initialCapacity,
Comparator comparator) {
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
this.comparator = comparator;
this.queue = new Object[initialCapacity];
}
PriorityBlockingQueue方法
[if !supportLists]ü [endif]Offer操做
在隊列插入一個元素,因爲是無界隊列,因此一直爲成功返回true;
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
int n, cap;
Object[] array;
//若是當前元素個數>=隊列容量,則擴容(1)
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap);
try {
Comparator cmp = comparator;
//默認比較器爲null
if (cmp == null)(2)
siftUpComparable(n, e, array);
else
//自定義比較器(3)
siftUpUsingComparator(n, e, array, cmp);
//隊列元素增長1,而且激活notEmpty的條件隊列裏面的一個阻塞線程
size = n + 1;(9)
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
主流程比較簡單,下面看看兩個主要函數
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); //must release and then re-acquire main lock
Object[] newArray = null;
//cas成功則擴容(4)
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) {
try {
//oldGap<64則擴容新增oldcap+2,否者擴容50%,而且最大爲MAX_ARRAY_SIZE
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
int minCap = oldCap + 1;
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
newCap = MAX_ARRAY_SIZE;
}
if (newCap > oldCap && queue == array)
newArray = new Object[newCap];
} finally {
allocationSpinLock = 0;
}
}
//第一個線程cas成功後,第二個線程會進入這個地方,而後第二個線程讓出cpu,儘可能讓第一個線程執行下面點獲取鎖,可是這得不到確定的保證。(5)
if (newArray == null) // back off if another thread is allocating
Thread.yield();
lock.lock();(6)
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
tryGrow目的是擴容,這裏要思考下爲啥在擴容前要先釋放鎖,而後使用cas控制只有一個線程能夠擴容成功。個人理解是爲了性能,由於擴容時候是須要花時間的,若是這些操做時候還佔用鎖那麼其餘線程在這個時候是不能進行出隊操做的,也不能進行入隊操做,這大大下降了併發性。
因此在擴容前釋放鎖,這容許其餘出隊線程能夠進行出隊操做,可是因爲釋放了鎖,因此也容許在擴容時候進行入隊操做,這就會致使多個線程進行擴容會出現問題,因此這裏使用了一個spinlock用cas控制只有一個線程能夠進行擴容,失敗的線程調用Thread.yield()讓出cpu,目的意在讓擴容線程擴容後優先調用lock.lock從新獲取鎖,可是這得不到必定的保證,有可能調用Thread.yield()的線程先獲取了鎖。
那copy元素數據到新數組爲啥放到獲取鎖後面那?緣由應該是由於可見性問題,由於queue並無被volatile修飾。另外有可能在擴容時候進行了出隊操做,若是直接拷貝可能看到的數組元素不是最新的。而經過調用Lock後,獲取的數組則是最新的,而且在釋放鎖前 數組內容不會變化。
具體建堆算法:
private static void siftUpComparable(int k, T x, Object[] array) {
Comparable key = (Comparable) x;
//隊列元素個數>0則判斷插入位置,否者直接入隊(7)
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = array[parent];
if (key.compareTo((T) e) >= 0)
break;
array[k] = e;
k = parent;
}
array[k] = key;(8)
}
下面用圖說話模擬下過程:假設隊列容量爲2
[if !supportLists]· [endif]第一次offer(2)時候
執行(1)爲false因此執行(2),因爲k=n=size=0;因此執行(8)元素入隊,然執行(9)size+1;如今隊列狀態:
[if !supportLists]· [endif]第二次offer(4)時候
執行(1)爲false,因此執行(2)因爲k=1,因此進入while循環,parent=0;e=2;key=4;key>e因此break;而後把4存到數據下標爲1的地方,這時候隊列狀態爲:
[if !supportLists]· [endif]第三次offer(4)時候
執行(1)爲true,因此調用tryGrow,因爲2<64因此newCap=2 + (2+2)=6;而後建立新數組並拷貝,而後調用siftUpComparable;k=2>0進入循環 parent=0;e=2;key=6;key>e因此break;而後把6放入下標爲2的地方,如今隊列狀態:
[if !supportLists]· [endif]第四次offer(1)時候
執行(1)爲false,因此執行(2)因爲k=3,因此進入while循環,parent=0;e=2;key=1; key
[if !supportLists]ü [endif]Poll操做
在隊列頭部獲取並移除一個元素,若是隊列爲空,則返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
主要看dequeue
private E dequeue() {
//隊列爲空,則返回null
int n = size - 1;
if (n < 0)
return null;
else {
//獲取隊頭元素(1)
Object[] array = queue;
E result = (E) array[0];
//獲取對尾元素,並值null(2)
E x = (E) array[n];
array[n] = null;
Comparator cmp = comparator;
if (cmp == null)//cmp=null則調用這個,把對尾元素位置插入到0位置,而且調整堆爲最小堆(3)
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n;(4)
return result;
}
}
private static void siftDownComparable(int k, T x, Object[] array,
int n) {
if (n > 0) {
Comparable key = (Comparable)x;
int half = n >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = array[child];(5)
int right = child + 1;(6)
if (right < n &&
((Comparable) c).compareTo((T) array[right]) > 0)(7)
c = array[child = right];
if (key.compareTo((T) c) <= 0)(8)
break;
array[k] = c;
k = child;
}
array[k] = key;(9)
}
}
下面用圖說話模擬下過程:
[if !supportLists]· [endif]第一次調用poll()
首先執行(1) result=1;而後執行(2)x=2;這時候隊列狀態
而後執行(3)後狀態爲:
執行(4)後的結果:
下面重點說說siftDownComparable這個屌屌的創建最小堆的算法:
首先說下思想,其中k一開始爲0,x爲數組裏面最後一個元素,因爲第0個元素爲樹根,被出隊時候要被搞掉,因此建堆要從它的左右孩子節點找一個最小的值來當樹根,子樹根被搞掉後,會找子樹的左右孩子最小的元素來代替,直到樹節點爲止,還不明白,不要緊,看圖說話:假如當前隊列元素:
那麼對於樹爲:
這時候若是調用了poll();那麼result=2;x=11;如今樹爲:
而後看leftChildVal = 4;rightChildVal = 6; 4<6;因此c=4;也就是獲取根節點的左右孩子值小的那一個; 而後看11>4也就是key>c;而後把c放入樹根,如今樹爲:
而後看根的左邊孩子4爲根的子樹咱們要爲這個字樹找一個根節點。
看leftChildVal = 8;rightChildVal = 10; 8<10;因此c=8;也就是獲取根節點的左右孩子值小的那一個; 而後看11>8也就是key>c;而後把c放入樹根,如今樹爲:
這時候k=3;half=3因此推出循環,執行(9)後結果爲:
這時候隊列爲:
[if !supportLists]ü [endif]Put操做
內部調用的offer,因爲是無界隊列,因此不須要阻塞
public void put(E e) {
offer(e); // never need to block
}
[if !supportLists]ü [endif]Take操做
獲取隊列頭元素,若是隊列爲空則阻塞。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
E result;
try {
//若是隊列爲空,則阻塞,把當前線程放入notEmpty的條件隊列
while ( (result = dequeue()) == null)
notEmpty.await();
} finally {
lock.unlock();
}
return result;
}
這裏是阻塞實現,阻塞後直到入隊操做調用notEmpty.signal 纔會返回。
[if !supportLists]ü [endif]Size操做
獲取隊列元個數,因爲加了獨佔鎖因此返回結果是精確的
public int size() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return size;
} finally {
lock.unlock();
}
}
PriorityBlockingQueue小結
PriorityBlockingQueue相似於ArrayBlockingQueue內部使用一個獨佔鎖來控制同時只有一個線程能夠進行入隊和出隊,另外前者只使用了一個notEmpty條件變量而沒有notFull這是由於前者是無界隊列,當put時候永遠不會處於await因此也不須要被喚醒。
PriorityBlockingQueue始終保證出隊的元素是優先級最高的元素,而且能夠定製優先級的規則,內部經過使用一個二叉樹最小堆算法來維護內部數組,這個數組是可擴容的,噹噹前元素個數>=最大容量時候會經過算法擴容。
值得注意的是爲了不在擴容操做時候其餘線程不能進行出隊操做,實現上使用了先釋放鎖,而後經過cas保證同時只有一個線程能夠擴容成功。
PriorityBlockingQueue示例
PriorityBlockingQueue類是JDK提供的優先級隊列 自己是線程安全的 內部使用顯示鎖 保證線程安全。
PriorityBlockingQueue存儲的對象必須是實現Comparable接口的 由於PriorityBlockingQueue隊列會根據內部存儲的每個元素的compareTo方法比較每一個元素的大小。這樣在take出來的時候會根據優先級 將優先級最小的最早取出。
下面是示例代碼
public static PriorityBlockingQueue queue = new PriorityBlockingQueue();
public static void main(String[] args) {
queue.add(new User(1,"wu"));
queue.add(new User(5,"wu5"));
queue.add(new User(23,"wu23"));
queue.add(new User(55,"wu55"));
queue.add(new User(9,"wu9"));
queue.add(new User(3,"wu3"));
for (User user : queue) {
try {
System.out.println(queue.take().name);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//靜態內部類
static class User implements Comparable{
public User(int age,String name) {
this.age = age;
this.name = name;
}
int age;
String name;
@Override
public int compareTo(User o) {
return this.age > o.age ? -1 : 1;
}
}
[if !supportLists]Ø [endif]SychronousQueue同步隊列
SynchronousQueue是一個比較特別的隊列,因爲在線程池方面有所應用,爲了更好的理解線程池的實現原理,此隊列源碼中充斥着大量的CAS語句,理解起來是有些難度的,爲了方便往後回顧,本篇文章會以簡潔的圖形化方式展現該隊列底層的實現原理。
SychronousQueue簡單實用
經典的生產者-消費者模式,操做流程是這樣的:
有多個生產者,能夠併發生產產品,把產品置入隊列中,若是隊列滿了,生產者就會阻塞;
有多個消費者,併發從隊列中獲取產品,若是隊列空了,消費者就會阻塞;
以下面的示意圖所示:
SynchronousQueue 也是一個隊列來的,但它的特別之處在於它內部沒有容器,一個生產線程,當它生產產品(即put的時候),若是當前沒有人想要消費產品(即當前沒有線程執行take),今生產線程必須阻塞,等待一個消費線程調用take操做,take操做將會喚醒該生產線程,同時消費線程會獲取生產線程的產品(即數據傳遞),這樣的一個過程稱爲一次配對過程(固然也能夠先take後put,原理是同樣的)。
咱們用一個簡單的代碼來驗證一下,以下所示:
package com.concurrent;
import java.util.concurrent.SynchronousQueue;
public class SynchronousQueueDemo {
public static void main(String[] args) throws InterruptedException {
final SynchronousQueue queue = new SynchronousQueue();
Thread putThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("put thread start");
try {
queue.put(1);
} catch (InterruptedException e) {
}
System.out.println("put thread end");
}
});
Thread takeThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("take thread start");
try {
System.out.println("take from putThread: " + queue.take());
} catch (InterruptedException e) {
}
System.out.println("take thread end");
}
});
putThread.start();
Thread.sleep(1000);
takeThread.start();
}
}
一種輸出結果以下:
put thread start
take thread start
take from putThread: 1
put thread end
take thread end
從結果能夠看出,put線程執行queue.put(1) 後就被阻塞了,只有take線程進行了消費,put線程才能夠返回。能夠認爲這是一種線程與線程間一對一傳遞消息的模型。
SychronousQueue實現原理
不像ArrayBlockingQueue、LinkedBlockingDeque之類的阻塞隊列依賴AQS實現併發操做,SynchronousQueue直接使用CAS實現線程的安全訪問。
隊列的實現策略一般分爲公平模式和非公平模式,接下來將分別進行說明。
公平模式下的模型:
公平模式下,底層實現使用的是TransferQueue這個內部隊列,它有一個head和tail指針,用於指向當前正在等待匹配的線程節點。 初始化時,TransferQueue的狀態以下:
接着咱們進行一些操做:
一、線程put1執行 put(1)操做,因爲當前沒有配對的消費線程,因此put1線程入隊列,自旋一小會後睡眠等待,這時隊列狀態以下:
二、接着,線程put2執行了put(2)操做,跟前面同樣,put2線程入隊列,自旋一小會後睡眠等待,這時隊列狀態以下:
[if !supportLists]三、[endif]這時候,來了一個線程take1,執行了 take操做,因爲tail指向put2線程,put2線程跟take1線程配對了(一put一take),這時take1線程不須要入隊,可是請注意了,這時候,要喚醒的線程並非put2,而是put1。
爲什麼?你們應該知道咱們如今講的是公平策略,所謂公平就是誰先入隊了,誰就優先被喚醒,咱們的例子明顯是put1應該優先被喚醒。至於讀者可能會有一個疑問,明明是take1線程跟put2線程匹配上了,結果是put1線程被喚醒消費,怎麼確保take1線程必定能夠和次首節點(head.next)也是匹配的呢?其實你們能夠拿個紙畫一畫,就會發現真的就是這樣的。
公平策略總結下來就是:隊尾匹配隊頭出隊。
執行後put1線程被喚醒,take1線程的 take()方法返回了1(put1線程的數據),這樣就實現了線程間的一對一通訊,這時候內部狀態以下:
四、最後,再來一個線程take2,執行take操做,這時候只有put2線程在等候,並且兩個線程匹配上了,線程put2被喚醒, take2線程take操做返回了2(線程put2的數據),這時候隊列又回到了起點,以下所示:
以上即是公平模式下,SynchronousQueue的實現模型。總結下來就是:隊尾匹配隊頭出隊,先進先出,體現公平原則。
非公平模式下的模型:
咱們仍是使用跟公平模式下同樣的操做流程,對比兩種策略下有何不一樣。非公平模式底層的實現使用的是TransferStack,一個棧,實現中用head指針指向棧頂,接着咱們看看它的實現模型:
一、線程put1執行 put(1)操做,因爲當前沒有配對的消費線程,因此put1線程入棧,自旋一小會後睡眠等待,這時棧狀態以下:
[if !supportLists]二、[endif]接着,線程put2再次執行了put(2)操做,跟前面同樣,put2線程入棧,自旋一小會後睡眠等待,這時棧狀態以下:
[if !supportLists]三、[endif]這時候,來了一個線程take1,執行了take操做,這時候發現棧頂爲put2線程,匹配成功,可是實現會先把take1線程入棧,而後take1線程循環執行匹配put2線程邏輯,一旦發現沒有併發衝突,就會把棧頂指針直接指向 put1線程
步驟一:
步驟二:
[if !supportLists]四、[endif]最後,再來一個線程take2,執行take操做,這跟步驟3的邏輯基本是一致的,take2線程入棧,
而後在循環中匹配put1線程,最終所有匹配完畢,棧變爲空,恢復初始狀態,以下圖所示:
步驟一:
步驟二:
能夠從上面流程看出,雖然put1線程先入棧了,可是倒是後匹配,這就是非公平的由來。
SychronousQueue總結
SynchronousQueue因爲其獨有的線程一一配對通訊機制,在大部分日常開發中,可能都不太會用到,但線程池技術中會有所使用,因爲內部沒有使用AQS,而是直接使用CAS,因此代碼理解起來會比較困難,但這並不妨礙咱們理解底層的實現模型,在理解了模型的基礎上,有興趣的話再查閱源碼,就會有方向感,看起來也會比較容易,但願本文有所借鑑意義。
[if !supportLists]Ø [endif]DeplayQueue延時無界阻塞隊列
在談到DelayQueue的使用和原理的時候,咱們首先介紹一下DelayQueue,DelayQueue是一個無界阻塞隊列,只有在延遲期滿時才能從中提取元素。該隊列的頭部是延遲期滿後保存時間最長的Delayed 元素。
DelayQueue阻塞隊列在咱們系統開發中也經常會用到,例如:緩存系統的設計,緩存中的對象,超過了空閒時間,須要從緩存中移出;任務調度系統,可以準確的把握任務的執行時間。咱們可能須要經過線程處理不少時間上要求很嚴格的數據,若是使用普通的線程,咱們就須要遍歷全部的對象,一個一個的檢 查看數據是否過時等,首先這樣在執行上的效率不會過高,其次就是這種設計的風格也大大的影響了數據的精度。一個須要12:00點執行的任務可能12:01 才執行,這樣對數據要求很高的系統有更大的弊端。由此咱們能夠使用DelayQueue。
下面將會對DelayQueue作一個介紹,而後舉個例子。而且提供一個Delayed接口的實現和Sample代碼。DelayQueue是一個BlockingQueue,其特化的參數是Delayed。(不瞭解BlockingQueue的同窗,先去了解BlockingQueue再看本文)
Delayed擴展了Comparable接口,比較的基準爲延時的時間值,Delayed接口的實現類getDelay的返回值應爲固定值(final)。DelayQueue內部是使用PriorityQueue實現的。
DelayQueue = BlockingQueue +PriorityQueue + Delayed
DelayQueue定義和原理
DelayQueue的關鍵元素BlockingQueue、PriorityQueue、Delayed。能夠這麼說,DelayQueue是一個使用優先隊列(PriorityQueue)實現的BlockingQueue,優先隊列的比較基準值是時間。
他們的基本定義以下
public interface Comparable {
public int compareTo(T o);
}
public interface Delayed extends Comparable {
long getDelay(TimeUnit unit);
}
public class DelayQueue implements BlockingQueue {
private final PriorityQueue q = new PriorityQueue();
}
DelayQueue內部的實現使用了一個優先隊列。當調用DelayQueue的offer方法時,把Delayed對象加入到優先隊列q中。以下:
public boolean offer(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
E first = q.peek();
q.offer(e);
if (first == null || e.compareTo(first) < 0)
available.signalAll();
return true;
} finally {
lock.unlock();
}
}
DelayQueue的take方法,把優先隊列q的first拿出來(peek),若是沒有達到延時閥值,則進行await處理。以下:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
E first = q.peek();
if (first == null) {
available.await();
} else {
long delay = first.getDelay(TimeUnit.NANOSECONDS);
if (delay > 0) {
long tl = available.awaitNanos(delay);
} else {
E x = q.poll();
assert x != null;
if (q.size() != 0)
available.signalAll(); // wake up other takers
return x;
}
}
}
} finally {
lock.unlock();
}
}
DelayQueue實例應用
Ps:爲了具備調用行爲,存放到DelayDeque的元素必須繼承Delayed接口。Delayed接口使對象成爲延遲對象,它使存放在DelayQueue類中的對象具備了激活日期。該接口強制執行下列兩個方法。
一下將使用Delay作一個緩存的實現。其中共包括三個類
[if !supportLists]n [endif]Pair
[if !supportLists]n [endif]DelayItem
[if !supportLists]n [endif]Cache
Pair類:
public class Pair {
public K first;
public V second;
public Pair() {}
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
}
一下是對Delay接口的實現:
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class DelayItem implements Delayed {
/** Base of nanosecond timings, to avoid wrapping */
private static final long NANO_ORIGIN = System.nanoTime();
/**
* Returns nanosecond time offset by origin
*/
final static long now() {
return System.nanoTime() - NANO_ORIGIN;
}
/**
* Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied
* entries.
*/
private static final AtomicLong sequencer = new AtomicLong(0);
/** Sequence number to break ties FIFO */
private final long sequenceNumber;
/** The time the task is enabled to execute in nanoTime units */
private final long time;
private final T item;
public DelayItem(T submit, long timeout) {
this.time = now() + timeout;
this.item = submit;
this.sequenceNumber = sequencer.getAndIncrement();
}
public T getItem() {
return this.item;
}
public long getDelay(TimeUnit unit) {
long d = unit.convert(time - now(), TimeUnit.NANOSECONDS);
return d;
}
public int compareTo(Delayed other) {
if (other == this) // compare zero ONLY if same object
return 0;
if (other instanceof DelayItem) {
DelayItem x = (DelayItem) other;
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS));
return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
}
}
如下是Cache的實現,包括了put和get方法
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Cache {
private static final Logger LOG = Logger.getLogger(Cache.class.getName());
private ConcurrentMap cacheObjMap = new ConcurrentHashMap();
private DelayQueue>> q = new DelayQueue>>();
private Thread daemonThread;
public Cache() {
Runnable daemonTask = new Runnable() {
public void run() {
daemonCheck();
}
};
daemonThread = new Thread(daemonTask);
daemonThread.setDaemon(true);
daemonThread.setName("Cache Daemon");
daemonThread.start();
}
private void daemonCheck() {
if (LOG.isLoggable(Level.INFO))
LOG.info("cache service started.");
for (;;) {
try {
DelayItem> delayItem = q.take();
if (delayItem != null) {
//超時對象處理
Pair pair = delayItem.getItem();
cacheObjMap.remove(pair.first, pair.second); // compare and remove
}
} catch (InterruptedException e) {
if (LOG.isLoggable(Level.SEVERE))
LOG.log(Level.SEVERE, e.getMessage(), e);
break;
}
}
if (LOG.isLoggable(Level.INFO))
LOG.info("cache service stopped.");
}
//添加緩存對象
public void put(K key, V value, long time, TimeUnit unit) {
V oldValue = cacheObjMap.put(key, value);
if (oldValue != null)
q.remove(key);
long nanoTime = TimeUnit.NANOSECONDS.convert(time, unit);
q.put(new DelayItem>(new Pair(key, value), nanoTime));
}
public V get(K key) {
return cacheObjMap.get(key);
}
測試main方法:
//測試入口函數
public static void main(String[] args) throws Exception {
Cache cache = new Cache();
cache.put(1, "aaaa", 3, TimeUnit.SECONDS);
Thread.sleep(1000 * 2);
{
String str = cache.get(1);
System.out.println(str);
}
Thread.sleep(1000 * 2);
{
String str = cache.get(1);
System.out.println(str);
}
}
輸出結果爲:
aaaa
null
咱們看到上面的結果,若是超過延時的時間,那麼緩存中數據就會自動丟失,得到就爲null。
[if !supportLists]C. [endif]併發(Collection)隊列-非阻塞隊列
[if !supportLists]Ø [endif]非阻塞隊列
首先咱們要簡單的理解下什麼是非阻塞隊列:
與阻塞隊列相反,非阻塞隊列的執行並不會被阻塞,不管是消費者的出隊,仍是生產者的入隊。
在底層,非阻塞隊列使用的是CAS(compare andswap)來實現線程執行的非阻塞。
非阻塞隊列簡單操做
與阻塞隊列相同,非阻塞隊列中的經常使用方法,也是出隊和入隊。
入隊方法:
[if !supportLists]n [endif]add():底層調用offer();
[if !supportLists]n [endif]offer():Queue接口繼承下來的方法,實現隊列的入隊操做,不會阻礙線程的執行,插入成功返回true;
出隊方法:
[if !supportLists]n [endif]poll():移動頭結點指針,返回頭結點元素,並將頭結點元素出隊;隊列爲空,則返回null;
[if !supportLists]n [endif]peek():移動頭結點指針,返回頭結點元素,並不會將頭結點元素出隊;隊列爲空,則返回null;
[if !supportLists]Ø [endif]非阻塞算法CAS
首先咱們須要瞭解悲觀鎖和樂觀鎖
悲觀鎖:假定併發環境是悲觀的,若是發生併發衝突,就會破壞一致性,因此要經過獨佔鎖完全禁止衝突發生。有一個經典比喻,「若是你不鎖門,那麼搗蛋鬼就回闖入並搞得一團糟」,因此「你只能一次打開門放進一我的,才能時刻盯緊他」。樂觀鎖:假定併發環境是樂觀的,即,雖然會有併發衝突,但衝突可發現且不會形成損害,因此,能夠不加任何保護,等發現併發衝突後再決定放棄操做仍是重試。可類比的比喻爲,「若是你不鎖門,那麼雖然搗蛋鬼會闖入,但他們一旦打算破壞你就能知道」,因此「你大能夠放進全部人,等發現他們想破壞的時候再作決定」。
一般認爲樂觀鎖的性能比悲觀所更高,特別是在某些複雜的場景。這主要因爲悲觀鎖在加鎖的同時,也會把某些不會形成破壞的操做保護起來;而樂觀鎖的競爭則只發生在最小的併發衝突處,若是用悲觀鎖來理解,就是「鎖的粒度最小」。但樂觀鎖的設計每每比較複雜,所以,複雜場景下仍是多用悲觀鎖。
首先保證正確性,有必要的話,再去追求性能。
CAS
樂觀鎖的實現每每須要硬件的支持,多數處理器都都實現了一個CAS指令,實現「Compare And Swap」的語義(這裏的swap是「換入」,也就是set),構成了基本的樂觀鎖。
CAS包含3個操做數:
[if !supportLists]n [endif]須要讀寫的內存位置V
[if !supportLists]n [endif]進行比較的值A
[if !supportLists]n [endif]擬寫入的新值B
當且僅當位置V的值等於A時,CAS纔會經過原子方式用新值B來更新位置V的值;不然不會執行任何操做。不管位置V的值是否等於A,都將返回V原有的值。
一個有意思的事實是,「使用CAS控制併發」與「使用樂觀鎖」並不等價。CAS只是一種手段,既能夠實現樂觀鎖,也能夠實現悲觀鎖。樂觀、悲觀只是一種併發控制的策略。下文將分別用CAS實現悲觀鎖和樂觀鎖?
[if !supportLists]Ø [endif]ConcurrentLinkedQueue非阻塞無界鏈表隊列
ConcurrentLinkedQueue是一個線程安全的隊列,基於鏈表結構實現,是一個無界隊列,理論上來講隊列的長度能夠無限擴大。
與其餘隊列相同,ConcurrentLinkedQueue也採用的是先進先出(FIFO)入隊規則,對元素進行排序。當咱們向隊列中添加元素時,新插入的元素會插入到隊列的尾部;而當咱們獲取一個元素時,它會從隊列的頭部中取出。
由於ConcurrentLinkedQueue是鏈表結構,因此當入隊時,插入的元素依次向後延伸,造成鏈表;而出隊時,則從鏈表的第一個元素開始獲取,依次遞增;
不知道,我這樣形容可否讓你對鏈表的入隊、出隊產生一個大概的思路!
ConcurrentLinkedQuere簡單示例
值得注意的是,在使用ConcurrentLinkedQueue時,若是涉及到隊列是否爲空的判斷,切記不可以使用size()==0的作法,由於在size()方法中,是經過遍歷整個鏈表來實現的,在隊列元素不少的時候,size()方法十分消耗性能和時間,只是單純的判斷隊列爲空使用isEmpty()便可!!!
public class ConcurrentLinkedQueueTest {
public static int threadCount = 10;
public static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();
static class Offer implements Runnable {
public void run() {
//不建議使用queue.size()==0,影響效率。能夠使用!queue.isEmpty()
if(queue.size()==0){
String ele = new Random().nextInt(Integer.MAX_VALUE)+"";
queue.offer(ele);
System.out.println("入隊元素爲"+ele);
}
}
}
static class Poll implements Runnable {
public void run() {
if(!queue.isEmpty()){
String ele = queue.poll();
System.out.println("出隊元素爲"+ele);
}
}
}
public static void main(String[] agrs) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
for(int x=0;x
executorService.submit(new Offer());
executorService.submit(new Poll());
}
executorService.shutdown();
}
}
一種輸出:
入隊元素爲313732926
出隊元素爲313732926
入隊元素爲812655435
出隊元素爲812655435
入隊元素爲1893079357
出隊元素爲1893079357
入隊元素爲1137820958
出隊元素爲1137820958
入隊元素爲1965962048
出隊元素爲1965962048
出隊元素爲685567162
入隊元素爲685567162
出隊元素爲1441081163
入隊元素爲1441081163
出隊元素爲1627184732
入隊元素爲1627184732
ConcurrentLinkedQuere類圖
如圖ConcurrentLinkedQueue中有兩個volatile類型的Node節點分別用來存在列表的首尾節點,其中head節點存放鏈表第一個item爲null的節點,tail則並非總指向最後一個節點。Node節點內部則維護一個變量item用來存放節點的值,next用來存放下一個節點,從而連接爲一個單向無界列表。
public ConcurrentLinkedQueue() {
head = tail = new Node(null);
}
如上代碼初始化時候會構建一個item爲NULL的空節點做爲鏈表的首尾節點。ConcurrentLinkedQuere方法
[if !supportLists]ü [endif]Offer操做
offer操做是在鏈表末尾添加一個元素,下面看看實現原理。
public boolean offer(E e) {
//e爲null則拋出空指針異常
checkNotNull(e);
//構造Node節點構造函數內部調用unsafe.putObject,後面統一講
final Node newNode = new Node(e);
//從尾節點插入
for (Node t = tail, p = t;;) {
Node q = p.next;
//若是q=null說明p是尾節點則插入
if (q == null) {
//cas插入(1)
if (p.casNext(null, newNode)) {
//cas成功說明新增節點已經被放入鏈表,而後設置當前尾節點(包含head,1,3,5.。。個節點爲尾節點)
if (p != t) // hop two nodes at a time
casTail(t, newNode); // Failure is OK.
return true;
}
// Lost CAS race to another thread; re-read next
}
else if (p == q)//(2)
//多線程操做時候,因爲poll時候會把老的head變爲自引用,而後head的next變爲新head,因此這裏須要
//從新找新的head,由於新的head後面的節點纔是激活的節點
p = (t != (t = tail)) ? t : head;
else
//尋找尾節點(3)
p = (p != t && t != (t = tail)) ? t : q;
}
}
從構造函數知道一開始有個item爲null的哨兵節點,而且head和tail都是指向這個節點,而後當一個線程調用offer時候首先
如圖首先查找尾節點,q==null,p就是尾節點,因此執行p.casNext經過cas設置p的next爲新增節點,這時候p==t因此不從新設置尾節點爲當前新節點。因爲多線程能夠調用offer方法,因此可能兩個線程同時執行到了(1)進行cas,那麼只有一個會成功(假如線程1成功了),成功後的鏈表爲:
失敗的線程會循環一次這時候指針爲:
這時候會執行(3)因此p=q,而後在循環後指針位置爲:
因此沒有其餘線程干擾的狀況下會執行(1)執行cas把新增節點插入到尾部,沒有干擾的狀況下線程2 cas會成功,而後去更新尾節點tail,因爲p!=t因此更新。這時候鏈表和指針爲:
假如線程2cas時候線程3也在執行,那麼線程3會失敗,循環一次後,線程3的節點狀態爲:
這時候p!=t ;而且t的原始值爲told,t的新值爲tnew ,因此told!=tnew,因此 p=tnew=tail;
而後在循環一下後節點狀態:
q==null因此執行(1)。
如今就差p==q這個分支尚未走,這個要在執行poll操做後纔會出現這個狀況。poll後會存在下面的狀態
這個時候添加元素時候指針分佈爲:
因此會執行(2)分支 結果 p=head而後循環,循環後指針分佈:
因此執行(1),而後p!=t因此設置tail節點。如今分佈圖:
自引用的節點會被垃圾回收掉。
[if !supportLists]ü [endif]add操做
add操做是在鏈表末尾添加一個元素,下面看看實現原理。其實內部調用的仍是offer
public boolean add(E e) {
return offer(e);
}
[if !supportLists]ü [endif]poll操做
poll操做是在鏈表頭部獲取而且移除一個元素,下面看看實現原理。
public E poll() {
restartFromHead:
//死循環
for (;;) {
//死循環
for (Node h = head, p = h, q;;) {
//保存當前節點值
E item = p.item;
//當前節點有值則cas變爲null(1)
if (item != null && p.casItem(item, null)) {
//cas成功標誌當前節點以及從鏈表中移除
if (p != h) //相似tail間隔2設置一次頭節點(2)
updateHead(h, ((q = p.next) != null) ? q : p);
return item;
}
//當前隊列爲空則返回null(3)
else if ((q = p.next) == null) {
updateHead(h, p);
return null;
}
//自引用了,則從新找新的隊列頭節點(4)
else if (p == q)
continue restartFromHead;
else//(5)
p = q;
}
}
}
final void updateHead(Node h, Node p) {
if (h != p && casHead(h, p))
h.lazySetNext(h);
}
當隊列爲空時候:
可知執行(3)這時候有兩種狀況,第一沒有其餘線程添加元素時候(3)結果爲true而後由於h!=p爲false因此直接返回null。第二在執行q=p.next前,其餘線程已經添加了一個元素到隊列,這時候(3)返回false,而後執行(5)p=q,而後循環後節點分佈:
這時候執行(1)分支,進行cas把當前節點值值爲null,同時只有一個線程會成功,cas成功 標示該節點從隊列中移除了,而後p!=h,調用updateHead方法,參數爲h,p;h!=p因此把p變爲當前鏈表head節點,而後h節點的next指向本身。如今狀態爲:
cas失敗 後 會再次循環,這時候分佈圖爲:
這時候執行(3)返回null.
如今還有個分支(4)沒有執行過,那麼何時會執行那?
這時候執行(1)分支,進行cas把當前節點值值爲null,同時只有一個線程A會成功,cas成功 標示該節點從隊列中移除了,而後p!=h,調用updateHead方法,假如執行updateHead前另一個線程B開始poll這時候它p指向爲原來的head節點,而後當前線程A執行updateHead這時候B線程鏈表狀態爲:
因此會執行(4)從新跳到外層循環,獲取當前head,如今狀態爲:
[if !supportLists]ü [endif]peek操做
peek操做是獲取鏈表頭部一個元素(只讀取不移除),下面看看實現原理。代碼與poll相似,只是少了castItem.而且peek操做會改變head指向,offer後head指向哨兵節點,第一次peek後head會指向第一個真的節點元素。
public E peek() {
restartFromHead:
for (;;) {
for (Node h = head, p = h, q;;) {
E item = p.item;
if (item != null || (q = p.next) == null) {
updateHead(h, p);
return item;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
[if !supportLists]ü [endif]size操做
獲取當前隊列元素個數,在併發環境下不是頗有用,由於使用CAS沒有加鎖因此從調用size函數到返回結果期間有可能增刪元素,致使統計的元素個數不精確。
public int size() {
int count = 0;
for (Node p = first(); p != null; p = succ(p))
if (p.item != null)
//最大返回Integer.MAX_VALUE
if (++count == Integer.MAX_VALUE)
break;
return count;
}
//獲取第一個隊列元素(哨兵元素不算),沒有則爲null
Node first() {
restartFromHead:
for (;;) {
for (Node h = head, p = h, q;;) {
boolean hasItem = (p.item != null);
if (hasItem || (q = p.next) == null) {
updateHead(h, p);
return hasItem ? p : null;
}
else if (p == q)
continue restartFromHead;
else
p = q;
}
}
}
//獲取當前節點的next元素,若是是自引入節點則返回真正頭節點
final Node succ(Node p) {
Node next = p.next;
return (p == next) ? head : next;
}
[if !supportLists]ü [endif]remove操做
若是隊列裏面存在該元素則刪除給元素,若是存在多個則刪除第一個,並返回true,否者返回false
public boolean remove(Object o) {
//查找元素爲空,直接返回false
if (o == null) return false;
Node pred = null;
for (Node p = first(); p != null; p = succ(p)) {
E item = p.item;
//相等則使用cas值null,同時一個線程成功,失敗的線程循環查找隊列中其餘元素是否有匹配的。
if (item != null &&
o.equals(item) &&
p.casItem(item, null)) {
//獲取next元素
Node next = succ(p);
//若是有前驅節點,而且next不爲空則連接前驅節點到next,
if (pred != null && next != null)
pred.casNext(p, next);
return true;
}
pred = p;
}
return false;
}
[if !supportLists]ü [endif]contains操做
判斷隊列裏面是否含有指定對象,因爲是遍歷整個隊列,因此相似size 不是那麼精確,有可能調用該方法時候元素還在隊列裏面,可是遍歷過程當中才把該元素刪除了,那麼就會返回false.
public boolean contains(Object o) {
if (o == null) return false;
for (Node p = first(); p != null; p = succ(p)) {
E item = p.item;
if (item != null && o.equals(item))
return true;
}
return false;
}
ConcurrentLinkedQuere的offer方法有意思的問題
offer中有個 判斷 t != (t = tail)假如 t=node1;tail=node2;而且node1!=node2那麼這個判斷是true仍是false那,答案是true,這個判斷是看當前t是否是和tail相等,相等則返回true否者爲false,可是不管結果是啥執行後t的值都是tail。
下面從字節碼來分析下爲啥。
[if !supportLists]· [endif]一個例子
public static void main(String[] args) {
int t = 2;
int tail = 3;
System.out.println(t != (t = tail));
}
結果爲:true
[if !supportLists]· [endif]字節碼文件
C:\Users\Simple\Desktop\TeacherCode\Crm_Test\build\classes\com\itheima\crm\util>javap -c Test001
警告:二進制文件Test001包含com.itheima.crm.util.Test001
Compiled from "Test001.java"
public class com.itheima.crm.util.Test001 {
public com.itheima.crm.util.Test001();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_2
1: istore_1
2: iconst_3
3: istore_2
4: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
7: iload_1
8: iload_2
9: dup
10: istore_1
11: if_icmpeq 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #22 // Method java/io/PrintStream.println:(Z)V
22: return
}
咱們從上面標黃的字節碼文件中分析
一開始棧爲空:
[if !supportLists]· [endif]第0行指令做用是把值2入棧棧頂元素爲2
[if !supportLists]· [endif]第1行指令做用是將棧頂int類型值保存到局部變量t中
[if !supportLists]· [endif]第2行指令做用是把值3入棧棧頂元素爲3
[if !supportLists]· [endif]第3行指令做用是將棧頂int類型值保存到局部變量tail中。
[if !supportLists]· [endif]第4調用打印命令
[if !supportLists]· [endif]第7行指令做用是把變量t中的值入棧
[if !supportLists]· [endif]第8行指令做用是把變量tail中的值入棧
[if !supportLists]· [endif]如今棧裏面的元素爲三、2,而且3位於棧頂
[if !supportLists]· [endif]第9行指令做用是當前棧頂元素入棧,因此如今棧內容3,3,2
[if !supportLists]· [endif]第10行指令做用是把棧頂元素存放到t,如今棧內容3,2
[if !supportLists]· [endif]第11行指令做用是判斷棧頂兩個元素值,相等則跳轉 18。因爲如今棧頂嚴肅爲3,2不相等因此返回true.
[if !supportLists]· [endif]第14行指令做用是把1入棧
[if !supportLists]· [endif]而後回頭分析下!=是雙目運算符,應該是首先把左邊的操做數入棧,而後在去計算了右側操做數。
ConcurrentLinkedQuere總結
ConcurrentLinkedQueue使用CAS非阻塞算法實現使用CAS解決了當前節點與next節點之間的安全連接和對當前節點值的賦值。因爲使用CAS沒有使用鎖,因此獲取size的時候有可能進行offer,poll或者remove操做,致使獲取的元素個數不精確,因此在併發狀況下size函數不是頗有用。另外第一次peek或者first時候會把head指向第一個真正的隊列元素。
下面總結下如何實現線程安全的,可知入隊出隊函數都是操做volatile變量:head,tail。因此要保證隊列線程安全只須要保證對這兩個Node操做的可見性和原子性,因爲volatile自己保證可見性,因此只須要看下多線程下若是保證對着兩個變量操做的原子性。
對於offer操做是在tail後面添加元素,也就是調用tail.casNext方法,而這個方法是使用的CAS操做,只有一個線程會成功,而後失敗的線程會循環一下,從新獲取tail,而後執行casNext方法。對於poll也是這樣的。
[if !supportLists]Ø [endif]ConcurrentHashMap非阻塞Hash集合
ConcurrentHashMap是Java併發包中提供的一個線程安全且高效的HashMap實現,ConcurrentHashMap在併發編程的場景中使用頻率很是之高,本文就來分析下ConcurrentHashMap的實現原理,並對其實現原理進行分析。
ConcurrentLinkedQuere類圖
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap相似,是一種數組和鏈表結構, 一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。
ConcurrentLinkedQuere實現原理
衆所周知,哈希表是中很是高效,複雜度爲O(1)的數據結構,在Java開發中,咱們最多見到最頻繁使用的就是HashMap和HashTable,可是在線程競爭激烈的併發場景中使用都不夠合理。
HashMap :先說HashMap,HashMap是線程不安全的,在併發環境下,可能會造成環狀鏈表(擴容時可能形成,具體緣由自行百度google或查看源碼分析),致使get操做時,cpu空轉,因此,在併發環境中使用HashMap是很是危險的。
HashTable : HashTable和HashMap的實現原理幾乎同樣,差異無非是1.HashTable不容許key和value爲null;2.HashTable是線程安全的。可是HashTable線程安全的策略實現代價卻太大了,簡單粗暴,get/put全部相關操做都是synchronized的,這至關於給整個哈希表加了一把大鎖,多線程訪問時候,只要有一個線程訪問或操做該對象,那其餘線程只能阻塞,至關於將全部的操做串行化,在競爭激烈的併發場景中性能就會很是差。以下圖
HashTable性能差主要是因爲全部操做須要競爭同一把鎖,而若是容器中有多把鎖,每一把鎖鎖一段數據,這樣在多線程訪問時不一樣段的數據時,就不會存在鎖競爭了,這樣即可以有效地提升併發效率。這就是ConcurrentHashMap所採用的"分段鎖"思想
ConcurrentLinkedQuere源碼解析
ConcurrentHashMap採用了很是精妙的"分段鎖"策略,ConcurrentHashMap的主幹是個Segment數組。
final Segment[] segments;
Segment繼承了ReentrantLock,因此它就是一種可重入鎖(ReentrantLock)。在ConcurrentHashMap,一個Segment就是一個子哈希表,Segment裏維護了一個HashEntry數組,併發環境下,對於不一樣Segment的數據進行操做是不用考慮鎖競爭的。(就按默認的ConcurrentLeve爲16來說,理論上就容許16個線程併發執行,有木有很酷)
因此,對於同一個Segment的操做才需考慮線程同步,不一樣的Segment則無需考慮。Segment相似於HashMap,一個Segment維護着一個HashEntry數組
transient volatile HashEntry[] table;
HashEntry是目前咱們提到的最小的邏輯處理單元了。一個ConcurrentHashMap維護一個Segment數組,一個Segment維護一個HashEntry數組。
static final class HashEntry {
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
//其餘省略
}
咱們說Segment相似哈希表,那麼一些屬性就跟咱們以前提到的HashMap差不離,好比負載因子loadFactor,好比閾值threshold等等,看下Segment的構造方法
Segment(float lf, int threshold, HashEntry[] tab) {
this.loadFactor = lf;//負載因子
this.threshold = threshold;//閾值
this.table = tab;//主幹數組即HashEntry數組
}
咱們來看下ConcurrentHashMap的構造方法
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//MAX_SEGMENTS爲1<<16=65536,也就是最大併發數爲65536
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
//2的sshif次方等於ssize,例:ssize=16,sshift=4;ssize=32,sshif=5
int sshift = 0;
//ssize爲segments數組長度,根據concurrentLevel計算得出
int ssize = 1;
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//segmentShift和segmentMask這兩個變量在定位segment時會用到,後面會詳細講
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//計算cap的大小,即Segment中HashEntry的數組長度,cap也必定爲2的n次方.
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
//建立segments數組並初始化第一個Segment,其他的Segment延遲初始化
Segment s0 =
new Segment(loadFactor, (int)(cap * loadFactor),
(HashEntry[])new HashEntry[cap]);
Segment[] ss = (Segment[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0);
this.segments = ss;
}
初始化方法有三個參數,若是用戶不指定則會使用默認值,initialCapacity爲16,loadFactor爲0.75(負載因子,擴容時須要參考),concurrentLevel爲16。
從上面的代碼能夠看出來,Segment數組的大小ssize是由concurrentLevel來決定的,可是卻不必定等於concurrentLevel,ssize必定是大於或等於concurrentLevel的最小的2的次冪。好比:默認狀況下concurrentLevel是16,則ssize爲16;若concurrentLevel爲14,ssize爲16;若concurrentLevel爲17,則ssize爲32。爲何Segment的數組大小必定是2的次冪?其實主要是便於經過按位與的散列算法來定位Segment的index。
其實,put方法對segment也會有所體現
public V put(K key, V value) {
Segment s;
//concurrentHashMap不容許key/value爲空
if (value == null)
throw new NullPointerException();
//hash函數對key的hashCode從新散列,避免差勁的不合理的hashcode,保證散列均勻
int hash = hash(key);
//返回的hash值無符號右移segmentShift位與段掩碼進行位運算,定位segment
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
從源碼看出,put的主要邏輯也就兩步:
1.定位segment並確保定位的Segment已初始化
2.調用Segment的put方法。
Ps:關於segmentShift和segmentMask
segmentShift和segmentMask這兩個全局變量的主要做用是用來定位Segment,int j =(hash >>> segmentShift) & segmentMask。
segmentMask:段掩碼,假如segments數組長度爲16,則段掩碼爲16-1=15;segments長度爲32,段掩碼爲32-1=31。這樣獲得的全部bit位都爲1,能夠更好地保證散列的均勻性
segmentShift:2的sshift次方等於ssize,segmentShift=32-sshift。若segments長度爲16,segmentShift=32-4=28;若segments長度爲32,segmentShift=32-5=27。而計算得出的hash值最大爲32位,無符號右移segmentShift,則意味着只保留高几位(其他位是沒用的),而後與段掩碼segmentMask位運算來定位Segment。
ConcurrentLinkedQuere方法
[if !supportLists]ü [endif]Get操做
public V get(Object key) {
Segment s;
HashEntry[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;//先定位Segment,再定位HashEntry
if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
get方法無需加鎖,因爲其中涉及到的共享變量都使用volatile修飾,volatile能夠保證內存可見性,因此不會讀取到過時數據。
來看下concurrentHashMap代理到Segment上的put方法,Segment中的put方法是要加鎖的。只不過是鎖粒度細了而已。
[if !supportLists]ü [endif]Put操做
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value);//tryLock不成功時會遍歷定位到的HashEnry位置的鏈表(遍歷主要是爲了使CPU緩存鏈表),若找不到,則建立HashEntry。tryLock必定次數後(MAX_SCAN_RETRIES變量決定),則lock。若遍歷過程當中,因爲其餘線程的操做致使鏈表頭結點變化,則須要從新遍歷。
V oldValue;
try {
HashEntry[] tab = table;
int index = (tab.length - 1) & hash;//定位HashEntry,能夠看到,這個hash值在定位Segment時和在Segment中定位HashEntry都會用到,只不過定位Segment時只用到高几位。
HashEntry first = entryAt(tab, index);
for (HashEntry e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry(hash, key, value, first);
int c = count + 1; //若c超出閾值threshold,須要擴容並rehash。擴容後的容量是當前容量的2倍。這樣能夠最大程度避免以前散列好的entry從新散列,具體在另外一篇文章中有詳細分析,不贅述。擴容並rehash的這個過程是比較消耗資源的。
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
ConcurrentLinkedQuere總結
ConcurrentHashMap做爲一種線程安全且高效的哈希表的解決方案,尤爲其中的"分段鎖"的方案,相比HashTable的全表鎖在性能上的提高很是之大。本文對ConcurrentHashMap的實現原理進行了詳細分析,並解讀了部分源碼,但願能幫助到有須要的童鞋。
[if !supportLists]Ø [endif]ConcurrentSkipListMap非阻塞Hash跳錶集合
你們都是知道TreeMap,它是使用樹形結構的方式進行存儲數據的線程不安全的Map集合(有序的哈希表),而且能夠對Map中的Key進行排序,Key中存儲的數據須要實現Comparator接口或使用CompareAble接口的子類來實現排序。
ConcurrentSkipListMap也是和TreeMap,它們都是有序的哈希表。可是,它們是有區別的:
第一,它們的線程安全機制不一樣,TreeMap是非線程安全的,而ConcurrentSkipListMap是線程安全的。第二,ConcurrentSkipListMap是經過跳錶實現的,而TreeMap是經過紅黑樹實現的。
那如今咱們須要知道說明是跳錶。
什麼是SkipList
Skip list(跳錶)是一種能夠代替平衡樹的數據結構,默認是按照Key值升序的。Skip list讓已排序的數據分佈在多層鏈表中,以0-1隨機數決定一個數據的向上攀升與否,經過「空間來換取時間」的一個算法,在每一個節點中增長了向前的指針,在插入、刪除、查找時能夠忽略一些不可能涉及到的結點,從而提升了效率。
從機率上保持數據結構的平衡比顯示的保持數據結構平衡要簡單的多。對於大多數應用,用Skip list要比用樹算法相對簡單。因爲Skip list比較簡單,實現起來會比較容易,雖然和平衡樹有着相同的時間複雜度(O(logn)),可是skip list的常數項會相對小不少。Skip list在空間上也比較節省。一個節點平均只須要1.333個指針(甚至更少)。
下圖爲Skip list結構圖(以7,14,21,32,37,71,85序列爲例)
SkipList性質
[if !supportLists](1) [endif]由不少層結構組成,level是經過必定的機率隨機產生的。(2) 每一層都是一個有序的鏈表,默認是升序,也能夠根據建立映射時所提供的Comparator進行排序,具體取決於使用的構造方法。(3) 最底層(Level 1)的鏈表包含全部元素。(4) 若是一個元素出如今Level i 的鏈表中,則它在Level i 之下的鏈表也都會出現。(5) 每一個節點包含兩個指針,一個指向同一鏈表中的下一個元素,一個指向下面一層的元素。
什麼是ConcurrentSkipListMap
ConcurrentSkipListMap提供了一種線程安全的併發訪問的排序映射表。內部是SkipList(跳錶)結構實現,在理論上可以在O(log(n))時間內完成查找、插入、刪除操做。 注意,調用ConcurrentSkipListMap的size時,因爲多個線程能夠同時對映射表進行操做,因此映射表須要遍歷整個鏈表才能返回元素個數,這個操做是個O(log(n))的操做。
ConcurrentSkipListMap數據結構
ConcurrentSkipListMap的數據結構,以下圖所示:
說明:能夠看到ConcurrentSkipListMap的數據結構使用的是跳錶,每個HeadIndex、Index結點都會包含一個對Node的引用,同一垂直方向上的Index、HeadIndex結點都包含了最底層的Node結點的引用。而且層級越高,該層級的結點(HeadIndex和Index)數越少。Node結點之間使用單鏈表結構。
ConcurrentSkipListMap源碼分析
ConcurrentSkipListMap主要用到了Node和Index兩種節點的存儲方式,經過volatile關鍵字實現了併發的操做
static final class Node {
final K key;
volatile Object value;//value值
volatile Node next;//next引用
……
}
static class Index {
final Node node;
final Index down;//downy引用
volatile Index right;//右邊引用
……
}
[if !supportLists]ü [endif]ConcurrentSkipListMap查找操做
經過SkipList的方式進行查找操做:(下圖以「查找91」進行說明:)
紅色虛線,表示查找的路徑,藍色向右箭頭表示right引用;黑色向下箭頭表示down引用;
下面就是源碼中的實現(get方法是經過doGet方法來時實現的)
public V get(Object key) {
return doGet(key);
}
//doGet的實現
private V doGet(Object okey) {
Comparable key = comparable(okey);
Node bound = null;
Index q = head;//把頭結點做爲當前節點的前驅節點
Index r = q.right;//前驅節點的右節點做爲當前節點
Node n;
K k;
int c;
for (;;) {//遍歷
Index d;
//依次遍歷right節點
if (r != null && (n = r.node) != bound && (k = n.key) != null) {
if ((c = key.compareTo(k)) > 0) {//因爲key都是升序排列的,全部當前關鍵字大於所要
查找的key時繼續向右遍歷
q = r;
r = r.right;
continue;
} else if (c == 0) {
//若是找到了相等的key節點,則返回該Node的value若是value爲空多是其餘併發delete
致使的,因而經過另外一種
//遍歷findNode的方式再查找
Object v = n.value;
return (v != null)? (V)v : getUsingFindNode(key);
} else
bound = n;
}
//若是一個鏈表中right沒能找到key對應的value,則調整到其down的引用處繼續查找
if ((d = q.down) != null) {
q = d;
r = d.right;
} else
break;
}
//若是經過上面的遍歷方式,還沒能找到key對應的value,再經過Node.next的方式進行查找
for (n = q.node.next; n != null; n = n.next) {
if ((k = n.key) != null) {
if ((c = key.compareTo(k)) == 0) {
Object v = n.value;
return (v != null)? (V)v : getUsingFindNode(key);
} else if (c < 0)
break;
}
}
return null;
}
[if !supportLists]ü [endif]ConcurrentSkipListMap刪除操做
經過SkipList的方式進行刪除操做:(下圖以「刪除23」進行說明:)
紅色虛線,表示查找的路徑,藍色向右箭頭表示right引用;黑色向下箭頭表示down引用;
下面就是源碼中的實現(remove方法是經過doRemove方法來時實現的)
//remove操做,經過doRemove實現,把全部level中出現關鍵字key的地方都delete掉
public V remove(Object key) {
return doRemove(key, null);
}
final V doRemove(Object okey, Object value) {
Comparable key = comparable(okey);
for (;;) {
Node b = findPredecessor(key);//獲得key的前驅(就是比key小的最大節點)
Node n = b.next;//前驅節點的next引用
for (;;) {//遍歷
if (n == null)//若是next引用爲空,直接返回
return null;
Node f = n.next;
if (n != b.next) //若是兩次得到的b.next不是相同的Node,就跳轉
到第一層循環從新得到b和n
break;
Object v = n.value;
if (v == null) { //當n被其餘線程delete的時候,其value==null,此時作輔助處理,並從新獲取b和n
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) //當其前驅被delet的時候直接跳出,從新獲取b和n
break;
int c = key.compareTo(n.key);
if (c < 0)
return null;
if (c > 0) {//當key較大時就繼續遍歷
b = n;
n = f;
continue;
}
if (value != null && !value.equals(v))
return null;
if (!n.casValue(v, null))
break;
if (!n.appendMarker(f) || !b.casNext(n, f))//casNext方法就是經過比較和設置b(前驅)
的next節點的方式來實現刪除操做
findNode(key); //經過嘗試findNode的方式繼續find
else {
findPredecessor(key); // Clean index
if (head.right == null) //若是head的right引用爲空,則表示不存在該level
tryReduceLevel();
}
return (V)v;
}
}
}
[if !supportLists]ü [endif]ConcurrentSkipListMap插入操做
經過SkipList的方式進行插入操做:(下圖以「添加55」的兩種狀況,進行說明:)
在level=2(該level存在)的狀況下添加55的圖示:只需在level<=2的合適位置插入55便可
在level=4(該level不存在,圖示level4是新建的)的狀況下添加55的狀況:首先新建level4,而後在level<=4的合適位置插入55。
下面就是源碼中的實現(put方法是經過doPut方法來時實現的)
//put操做,經過doPut實現
public V put(K key, V value) {
if (value == null)
throw new NullPointerException();
return doPut(key, value, false);
}
private V doPut(K kkey, V value, boolean onlyIfAbsent) {
Comparable key = comparable(kkey);
for (;;) {
Node b = findPredecessor(key);//前驅
Node n = b.next;
//定位的過程就是和get操做類似
for (;;) {
if (n != null) {
Node f = n.next;
if (n != b.next) //先後值不一致的狀況下,跳轉到第一層循環從新得到b和n
break;;
Object v = n.value;
if (v == null) { // n被delete的狀況下
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b被delete的狀況,從新獲取b和n
break;
int c = key.compareTo(n.key);
if (c > 0) {
b = n;
n = f;
continue;
}
if (c == 0) {
if (onlyIfAbsent || n.casValue(v, value))
return (V)v;
else
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
Node z = new Node(kkey, value, n);
if (!b.casNext(n, z))
break; // restart if lost race to append to b
int level = randomLevel();//獲得一個隨機的level做爲該key-value插入的最高level
if (level > 0)
insertIndex(z, level);//進行插入操做
return null;
}
}
}
/**
*得到一個隨機的level值
*/
private int randomLevel() {
int x = randomSeed;
x ^= x << 13;
x ^= x >>> 17;
randomSeed = x ^= x << 5;
if ((x & 0x8001) != 0) // test highest and lowest bits
return 0;
int level = 1;
while (((x >>>= 1) & 1) != 0) ++level;
return level;
}
//執行插入操做:如上圖所示,有兩種可能的狀況:
//1.當level存在時,對level<=n都執行insert操做
//2.當level不存在(大於目前的最大level)時,首先添加新的level,而後在執行操做1
private void insertIndex(Node z, int level) {
HeadIndex h = head;
int max = h.level;
if (level <= max) {//狀況1
Index idx = null;
for (int i = 1; i <= level; ++i)//首先獲得一個包含1~level個級別的down關係的鏈表,
最後的inx爲最高level
idx = new Index(z, idx, null);
addIndex(idx, h, level);//把最高level的idx傳給addIndex方法
} else { //狀況2 增長一個新的級別
level = max + 1;
Index[] idxs = (Index[])new Index[level+1];
Index idx = null;
for (int i = 1; i <= level; ++i)//該步驟和狀況1相似
idxs[i] = idx = new Index(z, idx, null);
HeadIndex oldh;
int k;
for (;;) {
oldh = head;
int oldLevel = oldh.level;
if (level <= oldLevel) { // lost race to add level
k = level;
break;
}
HeadIndex newh = oldh;
Node oldbase = oldh.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex(oldbase, newh, idxs[j], j);//建立新的
if (casHead(oldh, newh)) {
k = oldLevel;
break;
}
}
addIndex(idxs[k], oldh, k);
}
}
/**
*在1~indexlevel層中插入數據
*/
private void addIndex(Index idx, HeadIndex h, int indexLevel) {
// insertionLevel表明要插入的level,該值會在indexLevel~1間遍歷一遍
int insertionLevel = indexLevel;
Comparable key = comparable(idx.node.key);
if (key == null) throw new NullPointerException();
//和get操做相似,不一樣的就是查找的同時在各個level上加入了對應的key
for (;;) {
int j = h.level;
Index q = h;
Index r = q.right;
Index t = idx;
for (;;) {
if (r != null) {
Node n = r.node;
// compare before deletion check avoids needing recheck
int c = key.compareTo(n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {//在該層level中執行插入操做
// Don't insert index if node already deleted
if (t.indexesDeletedNode()) {
findNode(key); // cleans up
return;
}
if (!q.link(r, t))//執行link操做,其實就是inset的實現部分
break; // restart
if (--insertionLevel == 0) {
// need final deletion check before return
if (t.indexesDeletedNode())
findNode(key);
return;
}
}
if (--j >= insertionLevel && j < indexLevel)//key移動到下一層level
t = t.down;
q = q.down;
r = q.right;
}
}
}
ConcurrentLinkedQuere示例
下面咱們看下面示例輸出的結果
import java.util.*;import java.util.concurrent.*;
/*
* ConcurrentSkipListMap是「線程安全」的哈希表,而TreeMap是非線程安全的。
*
*下面是「多個線程同時操做而且遍歷map」的示例
* (01)當map是ConcurrentSkipListMap對象時,程序能正常運行。
* (02)當map是TreeMap對象時,程序會產生ConcurrentModificationException異常。
*
*/public class ConcurrentSkipListMapDemo1 {
// TODO: map是TreeMap對象時,程序會出錯。
//private static Map map = new TreeMap();
private static Map map = new ConcurrentSkipListMap();
public static void main(String[] args) {
//同時啓動兩個線程對map進行操做!
new MyThread("a").start();
new MyThread("b").start();
}
private static void printAll() {
String key, value;
Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
Map.Entry entry = (Map.Entry)iter.next();
key = (String)entry.getKey();
value = (String)entry.getValue();
System.out.print("("+key+", "+value+"), ");
}
System.out.println();
}
private static class MyThread extends Thread {
MyThread(String name) {
super(name);
}
@Override
public void run() {
int i = 0;
while (i++ < 6) {
// 「線程名」 + "序號"
String val = Thread.currentThread().getName()+i;
map.put(val, "0");
//經過「Iterator」遍歷map。
printAll();
}
}
}
}
某一次的運行結果:
(a1, 0), (a1, 0), (b1, 0), (b1, 0),
(a1, 0), (b1, 0), (b2, 0),
(a1, 0), (a1, 0), (a2, 0), (a2, 0), (b1, 0), (b1, 0), (b2, 0), (b2, 0), (b3, 0),
(b3, 0), (a1, 0),
(a2, 0), (a3, 0), (a1, 0), (b1, 0), (a2, 0), (b2, 0), (a3, 0), (b3, 0), (b1, 0), (b4, 0),
(b2, 0), (a1, 0), (b3, 0), (a2, 0), (b4, 0),
(a3, 0), (a1, 0), (a4, 0), (a2, 0), (b1, 0), (a3, 0), (b2, 0), (a4, 0), (b3, 0), (b1, 0), (b4, 0), (b2, 0), (b5, 0),
(b3, 0), (a1, 0), (b4, 0), (a2, 0), (b5, 0),
(a3, 0), (a1, 0), (a4, 0), (a2, 0), (a5, 0), (a3, 0), (b1, 0), (a4, 0), (b2, 0), (a5, 0), (b3, 0), (b1, 0), (b4, 0), (b2, 0), (b5, 0), (b3, 0), (b6, 0),
(b4, 0), (a1, 0), (b5, 0), (a2, 0), (b6, 0),
(a3, 0), (a4, 0), (a5, 0), (a6, 0), (b1, 0), (b2, 0), (b3, 0), (b4, 0), (b5, 0), (b6, 0),
結果說明:
示例程序中,啓動兩個線程(線程a和線程b)分別對ConcurrentSkipListMap進行操做。以線程a而言,它會先獲取「線程名」+「序號」,而後將該字符串做爲key,將「0」做爲value,插入到ConcurrentSkipListMap中;接着,遍歷並輸出ConcurrentSkipListMap中的所有元素。 線程b的操做和線程a同樣,只不過線程b的名字和線程a的名字不一樣。 當map是ConcurrentSkipListMap對象時,程序能正常運行。若是將map改成TreeMap時,程序會產生ConcurrentModificationException異常。
[if !supportLists]2) [endif]java.util.concurrent.atomic包
[if !supportLists]Ø [endif]AtomicBoolean原子性布爾
AtomicBoolean是java.util.concurrent.atomic包下的原子變量,這個包裏面提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具備排他性,即當某個線程進入方法,執行其中的指令時,不會被其餘線程打斷,而別的線程就像自旋鎖同樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另外一個線程進入,這只是一種邏輯上的理解。其實是藉助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。
AtomicBoolean,在這個Boolean值的變化的時候不容許在之間插入,保持操做的原子性。
下面將解釋重點方法並舉例:
boolean compareAndSet(expectedValue, updateValue),這個方法主要兩個做用:
[if !supportLists]1. [endif]比較AtomicBoolean和expect的值,若是一致,執行方法內的語句。其實就是一個if語句
[if !supportLists]2. [endif]把AtomicBoolean的值設成update,比較最要的是這兩件事是一鼓作氣的,這連個動做之間不會被打斷,任何內部或者外部的語句都不可能在兩個動做之間運行。爲多線程的控制提供瞭解決的方案
下面咱們從代碼上解釋:
首先咱們看下在不使用AtomicBoolean狀況下,代碼的運行狀況:
package zmx.atomic.test;
import java.util.concurrent.TimeUnit;
public class BarWorker implements Runnable {
//靜態變量
private static boolean exists = false;
private String name;
public BarWorker(String name) {
this.name = name;
}
@Override
public void run() {
if (!exists) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
// do nothing
}
exists = true;
System.out.println(name + " enter");
try {
System.out.println(name + " working");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(name + " leave");
exists = false;
} else {
System.out.println(name + " give up");
}
}
public static void main(String[] args) {
BarWorker bar1 = new BarWorker("bar1");
BarWorker bar2 = new BarWorker("bar2");
new Thread(bar1).start();
new Thread(bar2).start();
}
}
運行結果:
bar1 enter
bar2 enter
bar1 working
bar2 working
bar1 leave
bar2 leave
從上面的運行結果咱們可看到,兩個線程運行時,都對靜態變量exists同時作操做,並無保證exists靜態變量的原子性,也就是一個線程在對靜態變量exists進行操做到時候,其餘線程必須等待或不做爲。等待一個線程操做完後,才能對其進行操做。
下面咱們將靜態變量使用AtomicBoolean來進行操做
package zmx.atomic.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class BarWorker2 implements Runnable {
//靜態變量使用AtomicBoolean 進行操做
private static AtomicBoolean exists = new AtomicBoolean(false);
private String name;
public BarWorker2(String name) {
this.name = name;
}
@Override
public void run() {
if (exists.compareAndSet(false, true)) {
System.out.println(name + " enter");
try {
System.out.println(name + " working");
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// do nothing
}
System.out.println(name + " leave");
exists.set(false);
} else {
System.out.println(name + " give up");
}
}
public static void main(String[] args) {
BarWorker2 bar1 = new BarWorker2("bar1");
BarWorker2 bar2 = new BarWorker2("bar2");
new Thread(bar1).start();
new Thread(bar2).start();
}
}
運行結果:
bar1 enter
bar1 working
bar2 give up
bar1 leave
能夠從上面的運行結果看出僅僅一個線程進行工做,由於exists.compareAndSet(false, true)提供了原子性操做,比較和賦值操做組成了一個原子操做, 中間不會提供可乘之機。使得一個線程操做,其餘線程等待或不做爲。
下面咱們簡單介紹下AtomicBoolean的API
建立一個AtomicBoolean
你能夠這樣建立一個AtomicBoolean:
AtomicBoolean atomicBoolean = new AtomicBoolean();
以上示例新建了一個默認值爲false 的 AtomicBoolean。若是你想要爲 AtomicBoolean 實例設置一個顯式的初始值,那麼你能夠將初始值傳給 AtomicBoolean 的構造子:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
得到 AtomicBoolean的值
你能夠經過使用get() 方法來獲取一個 AtomicBoolean 的值。示例以下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean value = atomicBoolean.get();
設置AtomicBoolean的值
你能夠經過使用set() 方法來設置一個 AtomicBoolean 的值。示例以下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
atomicBoolean.set(false);
以上代碼執行後AtomicBoolean 的值爲 false。
交換AtomicBoolean的值
你能夠經過getAndSet() 方法來交換一個 AtomicBoolean 實例的值。getAndSet() 方法將返回 AtomicBoolean 當前的值,並將爲 AtomicBoolean 設置一個新值。示例以下:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean oldValue = atomicBoolean.getAndSet(false);
以上代碼執行後oldValue 變量的值爲 true,atomicBoolean 實例將持有 false 值。代碼成功將 AtomicBoolean 當前值 ture 交換爲 false。
比較並設置AtomicBoolean的值
compareAndSet() 方法容許你對 AtomicBoolean 的當前值與一個指望值進行比較,若是當前值等於指望值的 話,將會對AtomicBoolean 設定一個新值。compareAndSet() 方法是原子性的,所以在同一時間以內有單個 線程執行它。所以compareAndSet() 方法可被用於一些相似於鎖的同步的簡單實現。如下是一個 compareAndSet() 示例:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean expectedValue = true;
boolean newValue = false;
boolean wasNewValueSet = atomicBoolean.compareAndSet(
expectedValue, newValue);
本示例對AtomicBoolean 的當前值與 true 值進行比較,若是相等,將 AtomicBoolean 的值更新爲 false
[if !supportLists]Ø [endif]AtomicInteger原子性整型
AtomicInteger,一個提供原子操做的Integer的類。在Java語言中,++i和i++操做並非線程安全的,在使用的時候,不可避免的會用到synchronized關鍵字。而AtomicInteger則經過一種線程安全的加減操做接口。
咱們先來看看AtomicInteger給咱們提供了什麼方法:
public final int get() //獲取當前的值
public final int getAndSet(int newValue)//獲取當前的值,並設置新的值
public final int getAndIncrement()//獲取當前的值,並自增
public final int getAndDecrement() //獲取當前的值,並自減
public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值
下面經過兩個簡單的例子來看一下AtomicInteger 的優點在哪: 普通線程同步:
class Test2 {
private volatile int count = 0;
public synchronized void increment() {
count++; //若要線程安全執行執行count++,須要加鎖
}
public int getCount() {
return count;
}
}
使用AtomicInteger:
class Test2 {
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
//使用AtomicInteger以後,不須要加鎖,也能夠實現線程安全。
public int getCount() {
return count.get();
}
}
從上面的例子中咱們能夠看出:使用AtomicInteger是很是的安全的.並且由於AtomicInteger由硬件提供原子操做指令實現的。在非激烈競爭的狀況下,開銷更小,速度更快。AtomicInteger是使用非阻塞算法來實現併發控制的。AtomicInteger的關鍵域只有一下3個:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset=
unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex);
}
}
private volatile int value;
這裏,unsafe是java提供的得到對對象內存地址訪問的類,註釋已經清楚的寫出了,它的做用就是在更新操做時提供「比較並替換」的做用。實際上就是AtomicInteger中的一個工具。valueOffset是用來記錄value自己在內存的便宜地址的,這個記錄,也主要是爲了在更新操做在內存中找到value的位置,方便比較。 注意:value是用來存儲整數的時間變量,這裏被聲明爲volatile,就是爲了保證在更新操做時,當前線程能夠拿到value最新的值(併發環境下,value可能已經被其餘線程更新了)。
優勢:最大的好處就是能夠避免多線程的優先級倒置和死鎖狀況的發生,提高在高併發處理下的性能。
下面咱們簡單介紹下AtomicInteger的API
建立一個AtomicInteger
建立一個AtomicInteger 示例以下:
AtomicInteger atomicInteger = new AtomicInteger();
本示例將建立一個初始值爲0 的 AtomicInteger。若是你想要建立一個給定初始值的 AtomicInteger,你能夠這樣:
AtomicInteger atomicInteger = new AtomicInteger(123);
本示例將123 做爲參數傳給 AtomicInteger 的構造子,它將設置 AtomicInteger 實例的初始值爲 123。
得到AtomicInteger的值
你能夠使用get() 方法獲取 AtomicInteger 實例的值。示例以下:
AtomicInteger atomicInteger = new AtomicInteger(123);
int theValue = atomicInteger.get();
設置AtomicInteger的值
你能夠經過set() 方法對 AtomicInteger 的值進行從新設置。如下是 AtomicInteger.set() 示例:
AtomicInteger atomicInteger = new AtomicInteger(123);
atomicInteger.set(234);
以上示例建立了一個初始值爲123 的 AtomicInteger,而在第二行將其值更新爲 234。
比較並設置AtomicInteger的值
AtomicInteger 類也經過了一個原子性的 compareAndSet() 方法。這一方法將 AtomicInteger 實例的當前值與指望值進行比較,若是兩者相等,爲 AtomicInteger 實例設置一個新值。AtomicInteger.compareAndSet() 代碼示例:
AtomicInteger atomicInteger = new AtomicInteger(123);
int expectedValue = 123;
int newValue = 234;
atomicInteger.compareAndSet(expectedValue, newValue);
本示例首先新建一個初始值爲123 的 AtomicInteger 實例。而後將 AtomicInteger 與指望值 123 進行比較,若是相等,將 AtomicInteger 的值更新爲 234。
增長AtomicInteger的值
AtomicInteger 類包含有一些方法,經過它們你能夠增長 AtomicInteger 的值,並獲取其值。這些方法以下:
public final int addAndGet(int addValue)//在原來的數值上增長新的值,並返回新值
public final int getAndIncrement()//獲取當前的值,並自增
public final int incrementAndget() //自減,並得到自減後的值
public final int getAndAdd(int delta) //獲取當前的值,並加上預期的值
第一個addAndGet() 方法給 AtomicInteger 增長了一個值,而後返回增長後的值。getAndAdd() 方法爲 AtomicInteger 增長了一個值,但返回的是增長之前的 AtomicInteger 的值。具體使用哪個取決於你的應用場景。如下是這兩種方法的示例:
AtomicInteger atomicInteger = new AtomicInteger();
System.out.println(atomicInteger.getAndAdd(10));
System.out.println(atomicInteger.addAndGet(10));
本示例將打印出0 和 20。例子中,第二行拿到的是加 10 以前的 AtomicInteger 的值。加 10 以前的值是 0。第三行將 AtomicInteger 的值再加 10,並返回加操做以後的值。該值如今是爲 20。你固然也能夠使用這倆方法爲 AtomicInteger 添加負值。結果實際是一個減法操做。getAndIncrement() 和 incrementAndGet() 方法相似於 getAndAdd() 和 addAndGet(),但每次只將 AtomicInteger 的值加 1。
減少AtomicInteger的值
AtomicInteger 類還提供了一些減少 AtomicInteger 的值的原子性方法。這些方法是:
public final int decrementAndGet()
public final int getAndDecrement()
decrementAndGet() 將 AtomicInteger 的值減一,並返回減一後的值。getAndDecrement() 也將 AtomicInteger 的值減一,但它返回的是減一以前的值。
[if !supportLists]Ø [endif]AtomicIntegerArray原子性整型數組
java.util.concurrent.atomic.AtomicIntegerArray類提供了能夠以原子方式讀取和寫入的底層int數組的操做,還包含高級原子操做。 AtomicIntegerArray支持對底層int數組變量的原子操做。 它具備獲取和設置方法,如在變量上的讀取和寫入。 也就是說,一個集合與同一變量上的任何後續get相關聯。 原子compareAndSet方法也具備這些內存一致性功能。
AtomicIntegerArray本質上是對int[]類型的封裝。使用Unsafe類經過CAS的方式控制int[]在多線程下的安全性。它提供瞭如下幾個核心API:
//得到數組第i個下標的元素
public final int get(int i)
//得到數組的長度
public final int length()
//將數組第i個下標設置爲newValue,並返回舊的值
public final int getAndSet(int i, int newValue)
//進行CAS操做,若是第i個下標的元素等於expect,則設置爲update,設置成功返回true
public final boolean compareAndSet(int i, int expect, int update)
//將第i個下標的元素加1
public final int getAndIncrement(int i)
//將第i個下標的元素減1
public final int getAndDecrement(int i)
//將第i個下標的元素增長delta(delta能夠是負數)
public final int getAndAdd(int i, int delta)
下面給出一個簡單的示例,展現AtomicIntegerArray使用:
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
public void run(){
for(int k=0;k<10000;k++)
arr.getAndIncrement(k%arr.length());
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] ts=new Thread[10];
for(int k=0;k<10;k++){
ts[k]=new Thread(new AddThread());
}
for(int k=0;k<10;k++){ts[k].start();}
for(int k=0;k<10;k++){ts[k].join();}
System.out.println(arr);
}
}
輸出結果:
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
上述代碼第2行,申明瞭一個內含10個元素的數組。第3行定義的線程對數組內10個元素進行累加操做,每一個元素各加1000次。第11行,開啓10個這樣的線程。所以,能夠預測,若是線程安全,數組內10個元素的值必然都是10000。反之,若是線程不安全,則部分或者所有數值會小於10000。
[if !supportLists]Ø [endif]AtomicLong、AtomicLongArray原子性整型數組
AtomicLong、AtomicLongArray的API跟AtomicInteger、AtomicIntegerArray在使用方法都是差很少的。區別在於用前者是使用原子方式更新的long值和long數組,後者是使用原子方式更新的Integer值和Integer數組。二者的相同處在於它們此類確實擴展了Number,容許那些處理基於數字類的工具和實用工具進行統一訪問。在實際開發中,它們分別用於不一樣的場景。這個就具體狀況具體分析了,下面將舉例說明AtomicLong的使用場景(使用AtomicLong生成自增加ID),其餘就不在過多介紹。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
public class AtomicLongTest {
/**
* @param args
*/
public static void main(String[] args) {
final AtomicLong orderIdGenerator = new AtomicLong(0);
final List orders = Collections
.synchronizedList(new ArrayList());
for (int i = 0; i < 10; i++) {
Thread orderCreationThread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
long orderId = orderIdGenerator.incrementAndGet();
Item order = new Item(Thread.currentThread().getName(),
orderId);
orders.add(order);
}
}
});
orderCreationThread.setName("Order Creation Thread " + i);
orderCreationThread.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Set orderIds = new HashSet();
for (Item order : orders) {
orderIds.add(order.getID());
System.out.println("Order name:" + order.getItemName()
+"----"+"Order id:" + order.getID());
}
}
}
class Item {
String itemName;
long id;
Item(String n, long id) {
this.itemName = n;
this.id = id;
}
public String getItemName() {
return itemName;
}
public long getID() {
return id;
}
}
輸出:
Order name:Order Creation Thread 0----Order id:1
Order name:Order Creation Thread 1----Order id:2
Order name:Order Creation Thread 0----Order id:4
Order name:Order Creation Thread 1----Order id:5
Order name:Order Creation Thread 3----Order id:3
Order name:Order Creation Thread 0----Order id:7
Order name:Order Creation Thread 1----Order id:6
........
Order name:Order Creation Thread 2----Order id:100
從運行結果咱們看到,無論是哪一個線程。它們得到的ID是不會重複的,保證的ID生成的原子性,避免了線程安全上的問題。
[if !supportLists]3) [endif]java.util.concurrent.lock包
待續...
[if !supportLists]1. [endif]多線程的建立方式(2017-11-23-wzz)
(1)、繼承Thread類:但Thread本質上也是實現了Runnable接口的一個實例,它表明一個線程的實例,而且,啓動線程的惟一方法就是經過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,經過本身的類直接extend Thread,並複寫run()方法,就能夠啓動新線程並執行本身定義的run()方法。例如:繼承Thread類實現多線程,並在合適的地方啓動線程
[if !supportLists]1.[endif]public class MyThread extends Thread {
[if !supportLists]2.[endif] public void run() {
[if !supportLists]3.[endif] System.out.println("MyThread.run()");
[if !supportLists]4.[endif] }
[if !supportLists]5.[endif]}
[if !supportLists]6.[endif]MyThread myThread1 = new MyThread();
[if !supportLists]7.[endif]MyThread myThread2 = new MyThread();
[if !supportLists]8.[endif]myThread1.start();
[if !supportLists]9.[endif]myThread2.start();
(2)、實現Runnable接口的方式實現多線程,而且實例化Thread,傳入本身的Thread實例,調用run( )方法
[if !supportLists]1.[endif]public class MyThread implements Runnable {
[if !supportLists]2.[endif] public void run() {
[if !supportLists]3.[endif] System.out.println("MyThread.run()");
[if !supportLists]4.[endif] }
[if !supportLists]5.[endif]}
[if !supportLists]6.[endif]MyThread myThread = new MyThread();
[if !supportLists]7.[endif]Thread thread = new Thread(myThread);
[if !supportLists]8.[endif]thread.start();
[if !supportLists](3)[endif]、使用ExecutorService、Callable、Future實現有返回結果的多線程:ExecutorService、Callable、Future這個對象實際上都是屬於Executor框架中的功能類。想要詳細瞭解Executor框架的能夠訪問http://www.javaeye.com/topic/366591,這裏面對該框架作了很詳細的解釋。返回結果的線程是在JDK1.5中引入的新特徵,確實很實用,有了這種特徵我就不須要再爲了獲得返回值而大費周折了,並且即使實現了也可能漏洞百出。可返回值的任務必須實現Callable接口,相似的,無返回值的任務必須Runnable接口。執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了,再結合線程池接口ExecutorService就能夠實現傳說中有返回結果的多線程了。下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題能夠直接使用。代碼以下:
[if !supportLists]1.[endif]import java.util.concurrent.*;
[if !supportLists]2.[endif]import java.util.Date;
[if !supportLists]3.[endif]import java.util.List;
[if !supportLists]4.[endif]import java.util.ArrayList;
[if !supportLists]5.[endif]
[if !supportLists]6.[endif]/**
[if !supportLists]7.[endif]*有返回值的線程
[if !supportLists]8.[endif]*/
[if !supportLists]9.[endif]@SuppressWarnings("unchecked")
[if !supportLists]10.[endif]public class Test {
[if !supportLists]11.[endif]public static void main(String[] args) throws ExecutionException,
[if !supportLists]12.[endif] InterruptedException {
[if !supportLists]13.[endif]System.out.println("----程序開始運行----");
[if !supportLists]14.[endif] Date date1 = new Date();
[if !supportLists]15.[endif]
[if !supportLists]16.[endif] int taskSize = 5;
[if !supportLists]17.[endif]//建立一個線程池
[if !supportLists]18.[endif] ExecutorService pool = Executors.newFixedThreadPool(taskSize);
[if !supportLists]19.[endif]//建立多個有返回值的任務
[if !supportLists]20.[endif] List list = new ArrayList();
[if !supportLists]21.[endif] for (int i = 0; i < taskSize; i++) {
[if !supportLists]22.[endif] Callable c = new MyCallable(i + " ");
[if !supportLists]23.[endif]//執行任務並獲取Future對象
[if !supportLists]24.[endif] Future f = pool.submit(c);
[if !supportLists]25.[endif] // System.out.println(">>>" + f.get().toString());
[if !supportLists]26.[endif] list.add(f);
[if !supportLists]27.[endif] }
[if !supportLists]28.[endif]//關閉線程池
[if !supportLists]29.[endif] pool.shutdown();
[if !supportLists]30.[endif]
[if !supportLists]31.[endif]//獲取全部併發任務的運行結果
[if !supportLists]32.[endif] for (Future f : list) {
[if !supportLists]33.[endif]//從Future對象上獲取任務的返回值,並輸出到控制檯
[if !supportLists]34.[endif] System.out.println(">>>" + f.get().toString());
[if !supportLists]35.[endif] }
[if !supportLists]36.[endif]
[if !supportLists]37.[endif] Date date2 = new Date();
[if !supportLists]38.[endif]System.out.println("----程序結束運行----,程序運行時間【"
[if !supportLists]39.[endif]+ (date2.getTime() - date1.getTime()) + "毫秒】");
[if !supportLists]40.[endif]}
[if !supportLists]41.[endif]}
[if !supportLists]42.[endif]
[if !supportLists]43.[endif]class MyCallable implements Callable {
[if !supportLists]44.[endif]private String taskNum;
[if !supportLists]45.[endif]
[if !supportLists]46.[endif]MyCallable(String taskNum) {
[if !supportLists]47.[endif] this.taskNum = taskNum;
[if !supportLists]48.[endif]}
[if !supportLists]49.[endif]
[if !supportLists]50.[endif]public Object call() throws Exception {
[if !supportLists]51.[endif]System.out.println(">>>" + taskNum + "任務啓動");
[if !supportLists]52.[endif] Date dateTmp1 = new Date();
[if !supportLists]53.[endif] Thread.sleep(1000);
[if !supportLists]54.[endif] Date dateTmp2 = new Date();
[if !supportLists]55.[endif] long time = dateTmp2.getTime() - dateTmp1.getTime();
[if !supportLists]56.[endif]System.out.println(">>>" + taskNum + "任務終止");
[if !supportLists]57.[endif]return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";
[if !supportLists]58.[endif]}
[if !supportLists]59.[endif]}
[if !supportLists]2. [endif]在java中wait和sleep方法的不一樣?
最大的不一樣是在等待時wait會釋放鎖,而sleep一直持有鎖。wait一般被用於線程間交互,sleep一般被用於暫停執行。
[if !supportLists]3. [endif]synchronized和volatile關鍵字的做用
一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾以後,那麼就具有了兩層語義:
1)保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。
2)禁止進行指令重排序。
volatile本質是在告訴jvm當前變量在寄存器(工做內存)中的值是不肯定的,須要從主存中讀取;
synchronized則是鎖定當前變量,只有當前線程能夠訪問該變量,其餘線程被阻塞住。
[if !supportLists]1.[endif]volatile僅能使用在變量級別;
synchronized則能夠使用在變量、方法、和類級別的
[if !supportLists]2.[endif]volatile僅能實現變量的修改可見性,並不能保證原子性;
synchronized則能夠保證變量的修改可見性和原子性
[if !supportLists]3.[endif]volatile不會形成線程的阻塞;
synchronized可能會形成線程的阻塞。
[if !supportLists]4.[endif]volatile標記的變量不會被編譯器優化;
synchronized標記的變量能夠被編譯器優化
[if !supportLists]4. [endif]分析線程併發訪問代碼解釋緣由
[if !supportLists]1. [endif]public class Counter {
[if !supportLists]2. [endif] private volatile int count = 0;
[if !supportLists]3. [endif] public void inc(){
[if !supportLists]4. [endif] try {
[if !supportLists]5. [endif] Thread.sleep(3);
[if !supportLists]6. [endif] } catch (InterruptedException e) {
[if !supportLists]7. [endif] e.printStackTrace();
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif] count++;
[if !supportLists]10. [endif] }
[if !supportLists]11. [endif] @Override
[if !supportLists]12. [endif] public String toString() {
[if !supportLists]13. [endif] return "[count=" + count + "]";
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
[if !supportLists]16. [endif]//---------------------------------華麗的分割線-----------------------------
[if !supportLists]17. [endif]public class VolatileTest {
[if !supportLists]18. [endif] public static void main(String[] args) {
[if !supportLists]19. [endif] final Counter counter = new Counter();
[if !supportLists]20. [endif] for(int i=0;i<1000;i++){
[if !supportLists]21. [endif] new Thread(new Runnable() {
[if !supportLists]22. [endif] @Override
[if !supportLists]23. [endif] public void run() {
[if !supportLists]24. [endif] counter.inc();
[if !supportLists]25. [endif] }
[if !supportLists]26. [endif] }).start();
[if !supportLists]27. [endif] }
[if !supportLists]28. [endif] System.out.println(counter);
[if !supportLists]29. [endif] }
[if !supportLists]30. [endif]}
上面的代碼執行完後輸出的結果肯定爲1000嗎?
答案是不必定,或者不等於1000。這是爲何嗎?
在java 的內存模型中每個線程運行時都有一個線程棧,線程棧保存了線程運行時候變量值信息。當線程訪問某一個對象時候值的時候,首先經過對象的引用找到對應在堆內存的變量的值,而後把堆內存變量的具體值load到線程本地內存中,創建一個變量副本,以後線程就再也不和對象在堆內存變量值有任何關係,而是直接修改副本變量的值,在修改完以後的某一個時刻(線程退出以前),自動把線程變量副本的值回寫到對象在堆中變量。這樣在堆中的對象的值就產生變化了。
也就是說上面主函數中開啓了1000個子線程,每一個線程都有一個變量副本,每一個線程修改變量只是臨時修改了本身的副本,當線程結束時再將修改的值寫入在主內存中,這樣就出現了線程安全問題。所以結果就不可能等於1000了,通常都會小於1000。
上面的解釋用一張圖表示以下:
(圖片來自網絡,非本人所繪)
[if !supportLists]5. [endif]什麼是線程池,如何使用?
線程池就是事先將多個線程對象放到一個容器中,當使用的時候就不用new線程而是直接去池中拿線程便可,節省了開闢子線程的時間,提升的代碼執行效率。
在JDK的java.util.concurrent.Executors中提供了生成多種線程池的靜態方法。
[if !supportLists]1. [endif]ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
[if !supportLists]2. [endif]ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
[if !supportLists]3. [endif]ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
[if !supportLists]4. [endif]ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
而後調用他們的execute方法便可。
[if !supportLists]6. [endif]經常使用的線程池有哪些?(2017-11-23-wzz)
newSingleThreadExecutor:建立一個單線程的線程池,此線程池保證全部任務的執行順序按照任務的提交順序執行。
newFixedThreadPool:建立固定大小的線程池,每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。
newCachedThreadPool:建立一個可緩存的線程池,此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。
newScheduledThreadPool:建立一個大小無限的線程池,此線程池支持定時以及週期性執行任務的需求。
newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。
[if !supportLists]7. [endif]請敘述一下您對線程池的理解?(2015-11-25)
(若是問到了這樣的問題,能夠展開的說一下線程池如何用、線程池的好處、線程池的啓動策略)
合理利用線程池可以帶來三個好處。
第一:下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。
第二:提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。
第三:提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。
[if !supportLists]8. [endif]線程池的啓動策略?(2015-11-25)
官方對線程池的執行過程描述以下:
[if !supportLists]26. [endif] /*
[if !supportLists]27. [endif] * Proceed in 3 steps:
[if !supportLists]28. [endif] *
[if !supportLists]29. [endif] * 1. If fewer than corePoolSize threads are running, try to
[if !supportLists]30. [endif] * start a new thread with the given command as its first
[if !supportLists]31. [endif] * task. The call to addWorker atomically checks runState and
[if !supportLists]32. [endif] * workerCount, and so prevents false alarms that would add
[if !supportLists]33. [endif] * threads when it shouldn't, by returning false.
[if !supportLists]34. [endif] *
[if !supportLists]35. [endif] * 2. If a task can be successfully queued, then we still need
[if !supportLists]36. [endif] * to double-check whether we should have added a thread
[if !supportLists]37. [endif] * (because existing ones died since last checking) or that
[if !supportLists]38. [endif] * the pool shut down since entry into this method. So we
[if !supportLists]39. [endif] * recheck state and if necessary roll back the enqueuing if
[if !supportLists]40. [endif] * stopped, or start a new thread if there are none.
[if !supportLists]41. [endif] *
[if !supportLists]42. [endif] * 3. If we cannot queue task, then we try to add a new
[if !supportLists]43. [endif] * thread. If it fails, we know we are shut down or saturated
[if !supportLists]44. [endif] * and so reject the task.
[if !supportLists]45. [endif] */
一、線程池剛建立時,裏面沒有一個線程。任務隊列是做爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會立刻執行它們。
二、當調用execute() 方法添加一個任務時,線程池會作以下判斷:
a. 若是正在運行的線程數量小於 corePoolSize,那麼立刻建立線程運行這個任務;
b. 若是正在運行的線程數量大於或等於 corePoolSize,那麼將這個任務放入隊列。
c. 若是這時候隊列滿了,並且正在運行的線程數量小於 maximumPoolSize,那麼仍是要建立線程運行這個任務;
d. 若是隊列滿了,並且正在運行的線程數量大於或等於 maximumPoolSize,那麼線程池會拋出異常,告訴調用者「我不能再接受任務了」。
三、當一個線程完成任務時,它會從隊列中取下一個任務來執行。
四、當一個線程無事可作,超過必定的時間(keepAliveTime)時,線程池會判斷,若是當前運行的線程數大於 corePoolSize,那麼這個線程就被停掉。因此線程池的全部任務完成後,它最終會收縮到 corePoolSize 的大小。
[if !supportLists]9. [endif]如何控制某個方法容許併發訪問線程的個數?(2015-11-30)
[if !supportLists]1. [endif]package com.yange;
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]import java.util.concurrent.Semaphore;
[if !supportLists]4. [endif]/**
[if !supportLists]5. [endif] *
[if !supportLists]6. [endif] * @author wzy 2015-11-30
[if !supportLists]7. [endif] *
[if !supportLists]8. [endif] */
[if !supportLists]9. [endif]public class SemaphoreTest {
[if !supportLists]10. [endif] /*
[if !supportLists]11. [endif]* permits the initial number of permits available. This value may be negative,
[if !supportLists]12. [endif]in which case releases must occur before any acquires will be granted.
[if !supportLists]13. [endif]fair true if this semaphore will guarantee first-in first-out granting of
[if !supportLists]14. [endif]permits under contention, else false
[if !supportLists]15. [endif]*/
[if !supportLists]16. [endif] static Semaphore semaphore = new Semaphore(5,true);
[if !supportLists]17. [endif] public static void main(String[] args) {
[if !supportLists]18. [endif] for(int i=0;i<100;i++){
[if !supportLists]19. [endif] new Thread(new Runnable() {
[if !supportLists]20. [endif]
[if !supportLists]21. [endif] @Override
[if !supportLists]22. [endif] public void run() {
[if !supportLists]23. [endif] test();
[if !supportLists]24. [endif] }
[if !supportLists]25. [endif] }).start();
[if !supportLists]26. [endif] }
[if !supportLists]27. [endif]
[if !supportLists]28. [endif] }
[if !supportLists]29. [endif]
[if !supportLists]30. [endif] public static void test(){
[if !supportLists]31. [endif] try {
[if !supportLists]32. [endif]//申請一個請求
[if !supportLists]33. [endif] semaphore.acquire();
[if !supportLists]34. [endif] } catch (InterruptedException e1) {
[if !supportLists]35. [endif] e1.printStackTrace();
[if !supportLists]36. [endif] }
[if !supportLists]37. [endif] System.out.println(Thread.currentThread().getName()+"進來了");
[if !supportLists]38. [endif] try {
[if !supportLists]39. [endif] Thread.sleep(1000);
[if !supportLists]40. [endif] } catch (InterruptedException e) {
[if !supportLists]41. [endif] e.printStackTrace();
[if !supportLists]42. [endif] }
[if !supportLists]43. [endif] System.out.println(Thread.currentThread().getName()+"走了");
[if !supportLists]44. [endif]//釋放一個請求
[if !supportLists]45. [endif] semaphore.release();
[if !supportLists]46. [endif] }
[if !supportLists]47. [endif]}
能夠使用Semaphore控制,第16行的構造函數建立了一個Semaphore對象,而且初始化了5個信號。這樣的效果是控件test方法最多隻能有5個線程併發訪問,對於5個線程時就排隊等待,走一個來一下。第33行,請求一個信號(消費一個信號),若是信號被用完了則等待,第45行釋放一個信號,釋放的信號新的線程就能夠使用了。
[if !supportLists]10. [endif]三個線程a、b、c併發運行,b,c須要a線程的數據怎麼實現(上海3期學員提供)
根據問題的描述,我將問題用如下代碼演示,ThreadA、ThreadB、ThreadC,ThreadA用於初始化數據num,只有當num初始化完成以後再讓ThreadB和ThreadC獲取到初始化後的變量num。
分析過程以下:
考慮到多線程的不肯定性,所以咱們不能確保ThreadA就必定先於ThreadB和ThreadC前執行,就算ThreadA先執行了,咱們也沒法保證ThreadA何時才能將變量num給初始化完成。所以咱們必須讓ThreadB和ThreadC去等待ThreadA完成任何後發出的消息。
如今須要解決兩個難題,一是讓ThreadB和ThreadC等待ThreadA先執行完,二是ThreadA執行完以後給ThreadB和ThreadC發送消息。
解決上面的難題我能想到的兩種方案,一是使用純Java API的Semaphore類來控制線程的等待和釋放,二是使用Android提供的Handler消息機制。
[if !supportLists]1. [endif]package com.example;
[if !supportLists]2. [endif]/**
[if !supportLists]3. [endif]*三個線程a、b、c併發運行,b,c須要a線程的數據怎麼實現(上海3期學員提供)
[if !supportLists]4. [endif] *
[if !supportLists]5. [endif] */
[if !supportLists]6. [endif]public class ThreadCommunication {
[if !supportLists]7. [endif] private static int num;//定義一個變量做爲數據
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] public static void main(String[] args) {
[if !supportLists]10. [endif]
[if !supportLists]11. [endif] Thread threadA = new Thread(new Runnable() {
[if !supportLists]12. [endif]
[if !supportLists]13. [endif] @Override
[if !supportLists]14. [endif] public void run() {
[if !supportLists]15. [endif] try {
[if !supportLists]16. [endif] //模擬耗時操做以後初始化變量num
[if !supportLists]17. [endif] Thread.sleep(1000);
[if !supportLists]18. [endif] num = 1;
[if !supportLists]19. [endif]
[if !supportLists]20. [endif] } catch (InterruptedException e) {
[if !supportLists]21. [endif] e.printStackTrace();
[if !supportLists]22. [endif] }
[if !supportLists]23. [endif] }
[if !supportLists]24. [endif] });
[if !supportLists]25. [endif] Thread threadB = new Thread(new Runnable() {
[if !supportLists]26. [endif]
[if !supportLists]27. [endif] @Override
[if !supportLists]28. [endif] public void run() {
[if !supportLists]29. [endif] System.out.println(Thread.currentThread().getName()+"獲取到num的值爲:"+num);
[if !supportLists]30. [endif] }
[if !supportLists]31. [endif] });
[if !supportLists]32. [endif] Thread threadC = new Thread(new Runnable() {
[if !supportLists]33. [endif]
[if !supportLists]34. [endif] @Override
[if !supportLists]35. [endif] public void run() {
[if !supportLists]36. [endif] System.out.println(Thread.currentThread().getName()+"獲取到num的值爲:"+num);
[if !supportLists]37. [endif] }
[if !supportLists]38. [endif] });
[if !supportLists]39. [endif] //同時開啓3個線程
[if !supportLists]40. [endif] threadA.start();
[if !supportLists]41. [endif] threadB.start();
[if !supportLists]42. [endif] threadC.start();
[if !supportLists]43. [endif]
[if !supportLists]44. [endif] }
[if !supportLists]45. [endif]}
[if !supportLists]46. [endif]
解決方案一:
[if !supportLists]1. [endif]public class ThreadCommunication {
[if !supportLists]2. [endif] private static int num;
[if !supportLists]3. [endif] /**
[if !supportLists]4. [endif] *定義一個信號量,該類內部維持了多個線程鎖,能夠阻塞多個線程,釋放多個線程,
[if !supportLists]5. [endif]線程的阻塞和釋放是經過permit概念來實現的
[if !supportLists]6. [endif] *線程經過semaphore.acquire()方法獲取permit,若是當前semaphore有permit則分配給該線程,
[if !supportLists]7. [endif]若是沒有則阻塞該線程直到semaphore
[if !supportLists]8. [endif] *調用release()方法釋放permit。
[if !supportLists]9. [endif] *構造函數中參數:permit(容許) 個數,
[if !supportLists]10. [endif] */
[if !supportLists]11. [endif] private static Semaphore semaphore = new Semaphore(0);
[if !supportLists]12. [endif] public static void main(String[] args) {
[if !supportLists]13. [endif]
[if !supportLists]14. [endif] Thread threadA = new Thread(new Runnable() {
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] @Override
[if !supportLists]17. [endif] public void run() {
[if !supportLists]18. [endif] try {
[if !supportLists]19. [endif] //模擬耗時操做以後初始化變量num
[if !supportLists]20. [endif] Thread.sleep(1000);
[if !supportLists]21. [endif] num = 1;
[if !supportLists]22. [endif] //初始化完參數後釋放兩個permit
[if !supportLists]23. [endif] semaphore.release(2);
[if !supportLists]24. [endif]
[if !supportLists]25. [endif] } catch (InterruptedException e) {
[if !supportLists]26. [endif] e.printStackTrace();
[if !supportLists]27. [endif] }
[if !supportLists]28. [endif] }
[if !supportLists]29. [endif] });
[if !supportLists]30. [endif] Thread threadB = new Thread(new Runnable() {
[if !supportLists]31. [endif]
[if !supportLists]32. [endif] @Override
[if !supportLists]33. [endif] public void run() {
[if !supportLists]34. [endif] try {
[if !supportLists]35. [endif] //獲取permit,若是semaphore沒有可用的permit則等待,若是有則消耗一個
[if !supportLists]36. [endif] semaphore.acquire();
[if !supportLists]37. [endif] } catch (InterruptedException e) {
[if !supportLists]38. [endif] e.printStackTrace();
[if !supportLists]39. [endif] }
[if !supportLists]40. [endif] System.out.println(Thread.currentThread().getName()+"獲取到num的值爲:"+num);
[if !supportLists]41. [endif] }
[if !supportLists]42. [endif] });
[if !supportLists]43. [endif] Thread threadC = new Thread(new Runnable() {
[if !supportLists]44. [endif]
[if !supportLists]45. [endif] @Override
[if !supportLists]46. [endif] public void run() {
[if !supportLists]47. [endif] try {
[if !supportLists]48. [endif] //獲取permit,若是semaphore沒有可用的permit則等待,若是有則消耗一個
[if !supportLists]49. [endif] semaphore.acquire();
[if !supportLists]50. [endif] } catch (InterruptedException e) {
[if !supportLists]51. [endif] e.printStackTrace();
[if !supportLists]52. [endif] }
[if !supportLists]53. [endif] System.out.println(Thread.currentThread().getName()+"獲取到num的值爲:"+num);
[if !supportLists]54. [endif] }
[if !supportLists]55. [endif] });
[if !supportLists]56. [endif] //同時開啓3個線程
[if !supportLists]57. [endif] threadA.start();
[if !supportLists]58. [endif] threadB.start();
[if !supportLists]59. [endif] threadC.start();
[if !supportLists]60. [endif]
[if !supportLists]61. [endif] }
[if !supportLists]62. [endif]}
[if !supportLists]11. [endif]同一個類中的2個方法都加了同步鎖,多個線程能同時訪問同一個類中的這兩個方法嗎?(2017-2-24)
這個問題須要考慮到Lock與synchronized 兩種實現鎖的不一樣情形。由於這種狀況下使用Lock 和synchronized 會有大相徑庭的結果。Lock能夠讓等待鎖的線程響應中斷,Lock獲取鎖,以後須要釋放鎖。以下代碼,多個線程不可訪問同一個類中的2個加了Lock鎖的方法。
[if !supportLists]63. [endif]package com;
[if !supportLists]64. [endif]import java.util.concurrent.locks.Lock;
[if !supportLists]65. [endif]import java.util.concurrent.locks.ReentrantLock;
[if !supportLists]66. [endif]public class qq {
[if !supportLists]67. [endif]
[if !supportLists]68. [endif] private int count = 0;
[if !supportLists]69. [endif]private Lock lock = new ReentrantLock();//設置lock鎖
[if !supportLists]70. [endif]//方法1
[if !supportLists]71. [endif] public Runnable run1 = new Runnable(){
[if !supportLists]72. [endif] public void run() {
[if !supportLists]73. [endif]lock.lock(); //加鎖
[if !supportLists]74. [endif] while(count < 1000) {
[if !supportLists]75. [endif] try {
[if !supportLists]76. [endif]//打印是否執行該方法
[if !supportLists]77. [endif] System.out.println(Thread.currentThread().getName() + " run1: "+count++);
[if !supportLists]78. [endif] } catch (Exception e) {
[if !supportLists]79. [endif] e.printStackTrace();
[if !supportLists]80. [endif] }
[if !supportLists]81. [endif] }
[if !supportLists]82. [endif] }
[if !supportLists]83. [endif] lock.unlock();
[if !supportLists]84. [endif] }};
[if !supportLists]85. [endif]//方法2
[if !supportLists]86. [endif] public Runnable run2 = new Runnable(){
[if !supportLists]87. [endif] public void run() {
[if !supportLists]88. [endif] lock.lock();
[if !supportLists]89. [endif] while(count < 1000) {
[if !supportLists]90. [endif] try {
[if !supportLists]91. [endif] System.out.println(Thread.currentThread().getName() +
[if !supportLists]92. [endif] " run2: "+count++);
[if !supportLists]93. [endif] } catch (Exception e) {
[if !supportLists]94. [endif] e.printStackTrace();
[if !supportLists]95. [endif] }
[if !supportLists]96. [endif] }
[if !supportLists]97. [endif] lock.unlock();
[if !supportLists]98. [endif] }};
[if !supportLists]99. [endif]
[if !supportLists]100. [endif]
[if !supportLists]101. [endif]
[if !supportLists]102. [endif] public static void main(String[] args) throws InterruptedException {
[if !supportLists]103. [endif] qq t = new qq(); //建立一個對象
[if !supportLists]104. [endif]new Thread(t.run1).start();//獲取該對象的方法1
[if !supportLists]105. [endif]
[if !supportLists]106. [endif]new Thread(t.run2).start();//獲取該對象的方法2
[if !supportLists]107. [endif] }
[if !supportLists]108. [endif]}
結果是:
Thread-0 run1: 0
Thread-0 run1: 1
Thread-0 run1: 2
Thread-0 run1: 3
Thread-0 run1: 4
Thread-0 run1: 5
Thread-0 run1: 6
........
而synchronized卻不行,使用synchronized時,當咱們訪問同一個類對象的時候,是同一把鎖,因此能夠訪問該對象的其餘synchronized方法。代碼以下:
[if !supportLists]1.[endif]package com;
[if !supportLists]2.[endif]import java.util.concurrent.locks.Lock;
[if !supportLists]3.[endif]import java.util.concurrent.locks.ReentrantLock;
[if !supportLists]4.[endif]public class qq {
[if !supportLists]5.[endif] private int count = 0;
[if !supportLists]6.[endif] private Lock lock = new ReentrantLock();
[if !supportLists]7.[endif] public Runnable run1 = new Runnable(){
[if !supportLists]8.[endif] public void run() {
[if !supportLists]9.[endif]synchronized(this) { //設置關鍵字synchronized,以當前類爲鎖
[if !supportLists]10.[endif] while(count < 1000) {
[if !supportLists]11.[endif] try {
[if !supportLists]12.[endif]//打印是否執行該方法
[if !supportLists]13.[endif] System.out.println(Thread.currentThread().getName() + " run1: "+count++);
[if !supportLists]14.[endif] } catch (Exception e) {
[if !supportLists]15.[endif] e.printStackTrace();
[if !supportLists]16.[endif] }
[if !supportLists]17.[endif] }
[if !supportLists]18.[endif] }
[if !supportLists]19.[endif] }};
[if !supportLists]20.[endif] public Runnable run2 = new Runnable(){
[if !supportLists]21.[endif] public void run() {
[if !supportLists]22.[endif] synchronized(this) {
[if !supportLists]23.[endif] while(count < 1000) {
[if !supportLists]24.[endif] try {
[if !supportLists]25.[endif] System.out.println(Thread.currentThread().getName()
[if !supportLists]26.[endif] + " run2: "+count++);
[if !supportLists]27.[endif] } catch (Exception e) {
[if !supportLists]28.[endif] e.printStackTrace();
[if !supportLists]29.[endif] }
[if !supportLists]30.[endif] }
[if !supportLists]31.[endif] }
[if !supportLists]32.[endif] }};
[if !supportLists]33.[endif] public static void main(String[] args) throws InterruptedException {
[if !supportLists]34.[endif] qq t = new qq(); //建立一個對象
[if !supportLists]35.[endif]new Thread(t.run1).start(); //獲取該對象的方法1
[if !supportLists]36.[endif]new Thread(t.run2).start(); //獲取該對象的方法2
[if !supportLists]37.[endif] }
[if !supportLists]38.[endif]}
結果爲:
Thread-1 run2: 0
Thread-1 run2: 1
Thread-1 run2: 2
Thread-0 run1: 0
Thread-0 run1: 4 Thread-0 run1: 5 Thread-0 run1: 6
......
[if !supportLists]12. [endif]什麼狀況下致使線程死鎖,遇到線程死鎖該怎麼解決?(2017-2-24)
11.1 死鎖的定義:所謂死鎖是指多個線程因競爭資源而形成的一種僵局(互相等待),若無外力做用,這些進程都將沒法向前推動。
11.2 死鎖產生的必要條件:
互斥條件:線程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某資源僅爲一個線程所佔有。此時如有其餘線程請求該資源,則請求線程只能等待。
不剝奪條件:線程所得到的資源在未使用完畢以前,不能被其餘線程強行奪走,即只能由得到該資源的線程本身來釋放(只能是主動釋放)。
請求和保持條件:線程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其餘線程佔有,此時請求進程被阻塞,但對本身已得到的資源保持不放。
循環等待條件:存在一種線程資源的循環等待鏈,鏈中每個線程已得到的資源同時被鏈中下一個線程所請求。即存在一個處於等待狀態的線程集合{Pl, P2, ..., pn},其中Pi等待的資源被P(i+1)佔有(i=0, 1, ..., n-1),Pn等待的資源被P0佔有,如圖2-15所示。
11.3 產生死鎖的一個例子
[if !supportLists]1.[endif]package itheima.com;
[if !supportLists]2.[endif]/**
[if !supportLists]3.[endif]*一個簡單的死鎖類
[if !supportLists]4.[endif]*當DeadLock類的對象flag==1時(td1),先鎖定o1,睡眠500毫秒
[if !supportLists]5.[endif]*而td1在睡眠的時候另外一個flag==0的對象(td2)線程啓動,先鎖定o2,睡眠500毫秒
[if !supportLists]6.[endif]* td1睡眠結束後須要鎖定o2才能繼續執行,而此時o2已被td2鎖定;
[if !supportLists]7.[endif]* td2睡眠結束後須要鎖定o1才能繼續執行,而此時o1已被td1鎖定;
[if !supportLists]8.[endif]* td一、td2相互等待,都須要獲得對方鎖定的資源才能繼續執行,從而死鎖。
[if !supportLists]9.[endif]*/
[if !supportLists]10.[endif]public class DeadLock implements Runnable {
[if !supportLists]11.[endif] public int flag = 1;
[if !supportLists]12.[endif]//靜態對象是類的全部對象共享的
[if !supportLists]13.[endif] private static Object o1 = new Object(), o2 = new Object();
[if !supportLists]14.[endif] public void run() {
[if !supportLists]15.[endif] System.out.println("flag=" + flag);
[if !supportLists]16.[endif] if (flag == 1) {
[if !supportLists]17.[endif] synchronized (o1) {
[if !supportLists]18.[endif] try {
[if !supportLists]19.[endif] Thread.sleep(500);
[if !supportLists]20.[endif] } catch (Exception e) {
[if !supportLists]21.[endif] e.printStackTrace();
[if !supportLists]22.[endif] }
[if !supportLists]23.[endif] synchronized (o2) {
[if !supportLists]24.[endif] System.out.println("1");
[if !supportLists]25.[endif] }
[if !supportLists]26.[endif] }
[if !supportLists]27.[endif] }
[if !supportLists]28.[endif] if (flag == 0) {
[if !supportLists]29.[endif] synchronized (o2) {
[if !supportLists]30.[endif] try {
[if !supportLists]31.[endif] Thread.sleep(500);
[if !supportLists]32.[endif] } catch (Exception e) {
[if !supportLists]33.[endif] e.printStackTrace();
[if !supportLists]34.[endif] }
[if !supportLists]35.[endif] synchronized (o1) {
[if !supportLists]36.[endif] System.out.println("0");
[if !supportLists]37.[endif] }
[if !supportLists]38.[endif] }
[if !supportLists]39.[endif] }
[if !supportLists]40.[endif] }
[if !supportLists]41.[endif] public static void main(String[] args) {
[if !supportLists]42.[endif] DeadLock td1 = new DeadLock();
[if !supportLists]43.[endif] DeadLock td2 = new DeadLock();
[if !supportLists]44.[endif] td1.flag = 1;
[if !supportLists]45.[endif] td2.flag = 0;
[if !supportLists]46.[endif]//td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。
[if !supportLists]47.[endif]//td2的run()可能在td1的run()以前運行
[if !supportLists]48.[endif] new Thread(td1).start();
[if !supportLists]49.[endif] new Thread(td2).start();
[if !supportLists]50.[endif] }
[if !supportLists]51.[endif]}
11.4 如何避免死鎖
在有些狀況下死鎖是能夠避免的。兩種用於避免死鎖的技術:
[if !supportLists]1)[endif]加鎖順序(線程按照必定的順序加鎖)
[if !supportLists]1.[endif]package itheima.com;
[if !supportLists]2.[endif]public class DeadLock {
[if !supportLists]3.[endif] public int flag = 1;
[if !supportLists]4.[endif]//靜態對象是類的全部對象共享的
[if !supportLists]5.[endif] private static Object o1 = new Object(), o2 = new Object();
[if !supportLists]6.[endif] public void money(int flag) {
[if !supportLists]7.[endif] this.flag=flag;
[if !supportLists]8.[endif] if( flag ==1){
[if !supportLists]9.[endif] synchronized (o1) {
[if !supportLists]10.[endif] try {
[if !supportLists]11.[endif] Thread.sleep(500);
[if !supportLists]12.[endif] } catch (Exception e) {
[if !supportLists]13.[endif] e.printStackTrace();
[if !supportLists]14.[endif] }
[if !supportLists]15.[endif] synchronized (o2) {
[if !supportLists]16.[endif]System.out.println("當前的線程是"+
[if !supportLists]17.[endif] Thread.currentThread().getName()+" "+"flag的值"+"1");
[if !supportLists]18.[endif] }
[if !supportLists]19.[endif] }
[if !supportLists]20.[endif] }
[if !supportLists]21.[endif] if(flag ==0){
[if !supportLists]22.[endif] synchronized (o2) {
[if !supportLists]23.[endif] try {
[if !supportLists]24.[endif] Thread.sleep(500);
[if !supportLists]25.[endif] } catch (Exception e) {
[if !supportLists]26.[endif] e.printStackTrace();
[if !supportLists]27.[endif] }
[if !supportLists]28.[endif] synchronized (o1) {
[if !supportLists]29.[endif]System.out.println("當前的線程是"+
[if !supportLists]30.[endif] Thread.currentThread().getName()+" "+"flag的值"+"0");
[if !supportLists]31.[endif] }
[if !supportLists]32.[endif] }
[if !supportLists]33.[endif] }
[if !supportLists]34.[endif] }
[if !supportLists]35.[endif]
[if !supportLists]36.[endif] public static void main(String[] args) {
[if !supportLists]37.[endif] final DeadLock td1 = new DeadLock();
[if !supportLists]38.[endif] final DeadLock td2 = new DeadLock();
[if !supportLists]39.[endif] td1.flag = 1;
[if !supportLists]40.[endif] td2.flag = 0;
[if !supportLists]41.[endif]//td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。
[if !supportLists]42.[endif]//td2的run()可能在td1的run()以前運行
[if !supportLists]43.[endif] final Thread t1=new Thread(new Runnable(){
[if !supportLists]44.[endif] public void run() {
[if !supportLists]45.[endif] td1.flag = 1;
[if !supportLists]46.[endif] td1.money(1);
[if !supportLists]47.[endif] }
[if !supportLists]48.[endif] });
[if !supportLists]49.[endif] t1.start();
[if !supportLists]50.[endif] Thread t2= new Thread(new Runnable(){
[if !supportLists]51.[endif] public void run() {
[if !supportLists]52.[endif] // TODO Auto-generated method stub
[if !supportLists]53.[endif] try {
[if !supportLists]54.[endif] //讓t2等待t1執行完
[if !supportLists]55.[endif] t1.join();//核心代碼,讓t1執行完後t2纔會執行
[if !supportLists]56.[endif] } catch (InterruptedException e) {
[if !supportLists]57.[endif] // TODO Auto-generated catch block
[if !supportLists]58.[endif] e.printStackTrace();
[if !supportLists]59.[endif] }
[if !supportLists]60.[endif] td2.flag = 0;
[if !supportLists]61.[endif] td1.money(0);
[if !supportLists]62.[endif] }
[if !supportLists]63.[endif] });
[if !supportLists]64.[endif] t2.start();
[if !supportLists]65.[endif] }
[if !supportLists]66.[endif]}
結果:
當前的線程是Thread-0 flag的值1
當前的線程是Thread-1 flag的值0
[if !supportLists]2)[endif]加鎖時限(線程嘗試獲取鎖的時候加上必定的時限,超過期限則放棄對該鎖的請求,並釋放本身佔有的鎖)
[if !supportLists]1.[endif] package itheima.com;
[if !supportLists]2.[endif]import java.util.concurrent.TimeUnit;
[if !supportLists]3.[endif]import java.util.concurrent.locks.Lock;
[if !supportLists]4.[endif]import java.util.concurrent.locks.ReentrantLock;
[if !supportLists]5.[endif]public class DeadLock {
[if !supportLists]6.[endif] public int flag = 1;
[if !supportLists]7.[endif]//靜態對象是類的全部對象共享的
[if !supportLists]8.[endif] private static Object o1 = new Object(), o2 = new Object();
[if !supportLists]9.[endif] public void money(int flag) throws InterruptedException {
[if !supportLists]10.[endif] this.flag=flag;
[if !supportLists]11.[endif] if( flag ==1){
[if !supportLists]12.[endif] synchronized (o1) {
[if !supportLists]13.[endif] Thread.sleep(500);
[if !supportLists]14.[endif] synchronized (o2) {
[if !supportLists]15.[endif]System.out.println("當前的線程是"+
[if !supportLists]16.[endif] Thread.currentThread().getName()+" "+"flag的值"+"1");
[if !supportLists]17.[endif] }
[if !supportLists]18.[endif] }
[if !supportLists]19.[endif] }
[if !supportLists]20.[endif] if(flag ==0){
[if !supportLists]21.[endif] synchronized (o2) {
[if !supportLists]22.[endif] Thread.sleep(500);
[if !supportLists]23.[endif] synchronized (o1) {
[if !supportLists]24.[endif]System.out.println("當前的線程是"+
[if !supportLists]25.[endif] Thread.currentThread().getName()+" "+"flag的值"+"0");
[if !supportLists]26.[endif] }
[if !supportLists]27.[endif] }
[if !supportLists]28.[endif] }
[if !supportLists]29.[endif] }
[if !supportLists]30.[endif]
[if !supportLists]31.[endif] public static void main(String[] args) {
[if !supportLists]32.[endif] final Lock lock = new ReentrantLock();
[if !supportLists]33.[endif] final DeadLock td1 = new DeadLock();
[if !supportLists]34.[endif] final DeadLock td2 = new DeadLock();
[if !supportLists]35.[endif] td1.flag = 1;
[if !supportLists]36.[endif] td2.flag = 0;
[if !supportLists]37.[endif]//td1,td2都處於可執行狀態,但JVM線程調度先執行哪一個線程是不肯定的。
[if !supportLists]38.[endif]//td2的run()可能在td1的run()以前運行
[if !supportLists]39.[endif]
[if !supportLists]40.[endif] final Thread t1=new Thread(new Runnable(){
[if !supportLists]41.[endif] public void run() {
[if !supportLists]42.[endif] // TODO Auto-generated method stub
[if !supportLists]43.[endif] String tName = Thread.currentThread().getName();
[if !supportLists]44.[endif]
[if !supportLists]45.[endif] td1.flag = 1;
[if !supportLists]46.[endif] try {
[if !supportLists]47.[endif]//獲取不到鎖,就等5秒,若是5秒後仍是獲取不到就返回false
[if !supportLists]48.[endif] if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
[if !supportLists]49.[endif] System.out.println(tName + "獲取到鎖!");
[if !supportLists]50.[endif] } else {
[if !supportLists]51.[endif] System.out.println(tName + "獲取不到鎖!");
[if !supportLists]52.[endif] return;
[if !supportLists]53.[endif] }
[if !supportLists]54.[endif] } catch (Exception e) {
[if !supportLists]55.[endif] e.printStackTrace();
[if !supportLists]56.[endif] }
[if !supportLists]57.[endif]
[if !supportLists]58.[endif] try {
[if !supportLists]59.[endif] td1.money(1);
[if !supportLists]60.[endif] } catch (Exception e) {
[if !supportLists]61.[endif] System.out.println(tName + "出錯了!!!");
[if !supportLists]62.[endif] } finally {
[if !supportLists]63.[endif]System.out.println("當前的線程是"+Thread.currentThread().getName()+"釋放鎖!!");
[if !supportLists]64.[endif] lock.unlock();
[if !supportLists]65.[endif] }
[if !supportLists]66.[endif] }
[if !supportLists]67.[endif] });
[if !supportLists]68.[endif] t1.start();
[if !supportLists]69.[endif] Thread t2= new Thread(new Runnable(){
[if !supportLists]70.[endif] public void run() {
[if !supportLists]71.[endif] String tName = Thread.currentThread().getName();
[if !supportLists]72.[endif] // TODO Auto-generated method stub
[if !supportLists]73.[endif] td1.flag = 1;
[if !supportLists]74.[endif] try {
[if !supportLists]75.[endif]//獲取不到鎖,就等5秒,若是5秒後仍是獲取不到就返回false
[if !supportLists]76.[endif] if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {
[if !supportLists]77.[endif] System.out.println(tName + "獲取到鎖!");
[if !supportLists]78.[endif] } else {
[if !supportLists]79.[endif] System.out.println(tName + "獲取不到鎖!");
[if !supportLists]80.[endif] return;
[if !supportLists]81.[endif] }
[if !supportLists]82.[endif] } catch (Exception e) {
[if !supportLists]83.[endif] e.printStackTrace();
[if !supportLists]84.[endif] }
[if !supportLists]85.[endif] try {
[if !supportLists]86.[endif] td2.money(0);
[if !supportLists]87.[endif] } catch (Exception e) {
[if !supportLists]88.[endif] System.out.println(tName + "出錯了!!!");
[if !supportLists]89.[endif] } finally {
[if !supportLists]90.[endif]System.out.println("當前的線程是"+Thread.currentThread().getName()+"釋放鎖!!");
[if !supportLists]91.[endif] lock.unlock();
[if !supportLists]92.[endif] }
[if !supportLists]93.[endif] }
[if !supportLists]94.[endif] });
[if !supportLists]95.[endif] t2.start();
[if !supportLists]96.[endif] }
[if !supportLists]97.[endif]}
打印結果:
Thread-0獲取到鎖!
當前的線程是Thread-0 flag的值1
當前的線程是Thread-0釋放鎖!!
Thread-1獲取到鎖!
當前的線程是Thread-1 flag的值0
當前的線程是Thread-1釋放鎖!!
[if !supportLists]13. [endif]Java中多線程間的通訊怎麼實現?(2017-2-24)
線程通訊的方式:
[if !supportLists]1.[endif]共享變量
線程間通訊能夠經過發送信號,發送信號的一個簡單方式是在共享對象的變量裏設置信號值。線程A在一個同步塊裏設置boolean型成員變量hasDataToProcess爲true,線程B也在同步塊裏讀取hasDataToProcess這個成員變量。這個簡單的例子使用了一個持有信號的對象,並提供了set和get方法:
[if !supportLists]1.[endif]package itheima.com;
[if !supportLists]2.[endif]public class MySignal{
[if !supportLists]3.[endif]//共享的變量
[if !supportLists]4.[endif] private boolean hasDataToProcess=false;
[if !supportLists]5.[endif]//取值
[if !supportLists]6.[endif] public boolean getHasDataToProcess() {
[if !supportLists]7.[endif] return hasDataToProcess;
[if !supportLists]8.[endif] }
[if !supportLists]9.[endif]//存值
[if !supportLists]10.[endif] public void setHasDataToProcess(boolean hasDataToProcess) {
[if !supportLists]11.[endif] this.hasDataToProcess = hasDataToProcess;
[if !supportLists]12.[endif] }
[if !supportLists]13.[endif] public static void main(String[] args){
[if !supportLists]14.[endif]//同一個對象
[if !supportLists]15.[endif] final MySignal my=new MySignal();
[if !supportLists]16.[endif]//線程1設置hasDataToProcess值爲true
[if !supportLists]17.[endif] final Thread t1=new Thread(new Runnable(){
[if !supportLists]18.[endif] public void run() {
[if !supportLists]19.[endif] my.setHasDataToProcess(true);
[if !supportLists]20.[endif] }
[if !supportLists]21.[endif] });
[if !supportLists]22.[endif] t1.start();
[if !supportLists]23.[endif]//線程2取這個值hasDataToProcess
[if !supportLists]24.[endif] Thread t2=new Thread(new Runnable(){
[if !supportLists]25.[endif] public void run() {
[if !supportLists]26.[endif] try {
[if !supportLists]27.[endif]//等待線程1完成而後取值
[if !supportLists]28.[endif] t1.join();
[if !supportLists]29.[endif] } catch (InterruptedException e) {
[if !supportLists]30.[endif] e.printStackTrace();
[if !supportLists]31.[endif] }
[if !supportLists]32.[endif] my.getHasDataToProcess();
[if !supportLists]33.[endif] System.out.println("t1改變之後的值:" + my.isHasDataToProcess());
[if !supportLists]34.[endif] }
[if !supportLists]35.[endif] });
[if !supportLists]36.[endif] t2.start();
[if !supportLists]37.[endif]}
[if !supportLists]38.[endif]}
結果:
t1改變之後的值:true
[if !supportLists]2.[endif]wait/notify機制
以資源爲例,生產者生產一個資源,通知消費者就消費掉一個資源,生產者繼續生產資源,消費者消費資源,以此循環。代碼以下:
[if !supportLists]1.[endif]package itheima.com;
[if !supportLists]2.[endif]//資源類
[if !supportLists]3.[endif] class Resource{
[if !supportLists]4.[endif] private String name;
[if !supportLists]5.[endif] private int count=1;
[if !supportLists]6.[endif] private boolean flag=false;
[if !supportLists]7.[endif] public synchronized void set(String name){
[if !supportLists]8.[endif] //生產資源
[if !supportLists]9.[endif] while(flag) {
[if !supportLists]10.[endif] try{
[if !supportLists]11.[endif] //線程等待。消費者消費資源
[if !supportLists]12.[endif] wait();
[if !supportLists]13.[endif] }catch(Exception e){}
[if !supportLists]14.[endif] }
[if !supportLists]15.[endif] this.name=name+"---"+count++;
[if !supportLists]16.[endif]System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
[if !supportLists]17.[endif] flag=true;
[if !supportLists]18.[endif]//喚醒等待中的消費者
[if !supportLists]19.[endif] this.notifyAll();
[if !supportLists]20.[endif] }
[if !supportLists]21.[endif] public synchronized void out(){
[if !supportLists]22.[endif] //消費資源
[if !supportLists]23.[endif] while(!flag) {
[if !supportLists]24.[endif] //線程等待,生產者生產資源
[if !supportLists]25.[endif] try{wait();}catch(Exception e){}
[if !supportLists]26.[endif] }
[if !supportLists]27.[endif]System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
[if !supportLists]28.[endif] flag=false;
[if !supportLists]29.[endif]//喚醒生產者,生產資源
[if !supportLists]30.[endif] this.notifyAll();
[if !supportLists]31.[endif] }
[if !supportLists]32.[endif]}
[if !supportLists]33.[endif]//生產者
[if !supportLists]34.[endif] class Producer implements Runnable{
[if !supportLists]35.[endif] private Resource res;
[if !supportLists]36.[endif] Producer(Resource res){
[if !supportLists]37.[endif] this.res=res;
[if !supportLists]38.[endif] }
[if !supportLists]39.[endif] //生產者生產資源
[if !supportLists]40.[endif] public void run(){
[if !supportLists]41.[endif] while(true){
[if !supportLists]42.[endif] res.set("商品");
[if !supportLists]43.[endif] }
[if !supportLists]44.[endif] }
[if !supportLists]45.[endif] }
[if !supportLists]46.[endif]//消費者消費資源
[if !supportLists]47.[endif] class Consumer implements Runnable{
[if !supportLists]48.[endif] private Resource res;
[if !supportLists]49.[endif] Consumer(Resource res){
[if !supportLists]50.[endif] this.res=res;
[if !supportLists]51.[endif] }
[if !supportLists]52.[endif] public void run(){
[if !supportLists]53.[endif] while(true){
[if !supportLists]54.[endif] res.out();
[if !supportLists]55.[endif] }
[if !supportLists]56.[endif] }
[if !supportLists]57.[endif] }
[if !supportLists]58.[endif]public class ProducerConsumerDemo{
[if !supportLists]59.[endif] public static void main(String[] args){
[if !supportLists]60.[endif] Resource r=new Resource();
[if !supportLists]61.[endif] Producer pro=new Producer(r);
[if !supportLists]62.[endif] Consumer con=new Consumer(r);
[if !supportLists]63.[endif] Thread t1=new Thread(pro);
[if !supportLists]64.[endif] Thread t2=new Thread(con);
[if !supportLists]65.[endif] t1.start();
[if !supportLists]66.[endif] t2.start();
[if !supportLists]67.[endif] }
[if !supportLists]68.[endif]}
[if !supportLists]14. [endif]線程和進程的區別(2017-11-23-wzz)
進程:具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是操做系統進行資源分配和調度的一個獨立單位。
線程:是進程的一個實體,是cpu調度和分派的基本單位,是比進程更小的能夠獨立運行的基本單位。
特色:線程的劃分尺度小於進程,這使多線程程序擁有高併發性,進程在運行時各自內存單元相互獨立,線程之間內存共享,這使多線程編程能夠擁有更好的性能和用戶體驗
注意:多線程編程對於其它程序是不友好的,佔據大量cpu資源。
[if !supportLists]15. [endif]請說出同步線程及線程調度相關的方法?(2017-11-23-wzz)
wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;
notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;
notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;
注意:java 5 經過Lock接口提供了顯示的鎖機制,Lock接口中定義了加鎖(lock()方法)和解鎖(unLock()方法),加強了多線程編程的靈活性及對線程的協調
[if !supportLists]16. [endif]啓動一個線程是調用run()方法仍是start()方法?(2017-11-23-wzz)
啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。
run()方法是線程啓動後要進行回調(callback)的方法。
[if !supportLists]10、[endif]Java內部類
[if !supportLists]1. [endif]靜態嵌套類(Static Nested Class) 和內部類(Inner Class)的不一樣?(2017-11-16-wl)
靜態嵌套類:Static Nested Class 是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。
內部類:須要在外部類實例化後才能實例化,其語法看起來挺詭異的。
[if !supportLists]2. [endif]下面的代碼哪些地方會產生編譯錯誤?(2017-11-16-wl)
[if !supportLists]1.[endif]class Outer {
[if !supportLists]2.[endif]
[if !supportLists]3.[endif]class Inner {}
[if !supportLists]4.[endif]
[if !supportLists]5.[endif]public static void foo() { new Inner(); }
[if !supportLists]6.[endif]
[if !supportLists]7.[endif]public void bar() { new Inner(); }
[if !supportLists]8.[endif]
[if !supportLists]9.[endif]public static void main(String[] args) {
[if !supportLists]10.[endif]new Inner();
[if !supportLists]11.[endif]}
[if !supportLists]12.[endif]}
注意:Java 中非靜態內部類對象的建立要依賴其外部類對象,上面的面試題中 foo 和 main 方法都是靜態方法,靜態方法中沒有 this,也就是說沒有所謂的外部類對象,所以沒法建立內部類對象,若是要在靜態方法中建立內部類對象,能夠這樣作
[if !supportLists]1.[endif]new Outer().new Inner();
[if !supportLists]第三章 [endif]JavaSE高級
[if !supportLists]1、[endif]Java中的反射
[if !supportLists]1. [endif]說說你對Java中反射的理解
Java中的反射首先是可以獲取到Java中要反射類的字節碼,獲取字節碼有三種方法,1.Class.forName(className) 2.類名.class 3.this.getClass()。而後將字節碼中的方法,變量,構造函數等映射成相應的Method、Filed、Constructor等類,這些類提供了豐富的方法能夠被咱們所使用。
[if !supportLists]2、[endif]Java中的動態代理
[if !supportLists]1. [endif]寫一個ArrayList的動態代理類(筆試題)
[if !supportLists]1. [endif]final List list = new ArrayList();
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] List proxyInstance =
[if !supportLists]4. [endif](List)Proxy.newProxyInstance(list.getClass().getClassLoader(),
[if !supportLists]5. [endif]list.getClass().getInterfaces(),
[if !supportLists]6. [endif]new InvocationHandler() {
[if !supportLists]7. [endif]
[if !supportLists]8. [endif] @Override
[if !supportLists]9. [endif] public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
[if !supportLists]10. [endif] return method.invoke(list, args);
[if !supportLists]11. [endif] }
[if !supportLists]12. [endif] });
[if !supportLists]13. [endif] proxyInstance.add("你好");
[if !supportLists]14. [endif] System.out.println(list);
[if !supportLists]2. [endif]動靜態代理的區別,什麼場景使用?(2015-11-25)
靜態代理一般只代理一個類,動態代理是代理一個接口下的多個實現類。
靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在運行時才知道。
動態代理是實現JDK裏的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的業務類必需要實現接口,經過Proxy裏的newProxyInstance獲得代理對象。
還有一種動態代理CGLIB,代理的是類,不須要業務類繼承接口,經過派生的子類來實現代理。經過在運行時,動態修改字節碼達到修改類的目的。
AOP編程就是基於動態代理實現的,好比著名的Spring框架、Hibernate框架等等都是動態代理的使用例子。
[if !supportLists]3、[endif]Java中的設計模式&回收機制
[if !supportLists]1. [endif]你所知道的設計模式有哪些
Java中通常認爲有23種設計模式,咱們不須要全部的都會,可是其中經常使用的幾種設計模式應該去掌握。下面列出了全部的設計模式。須要掌握的設計模式我單獨列出來了,固然能掌握的越多越好。
整體來講設計模式分爲三大類:
建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。
[if !supportLists]2. [endif]單例設計模式
最好理解的一種設計模式,分爲懶漢式和餓漢式。
餓漢式:
[if !supportLists]1. [endif]public class Singleton {
[if !supportLists]2. [endif] //直接建立對象
[if !supportLists]3. [endif] public static Singleton instance = new Singleton();
[if !supportLists]4. [endif]
[if !supportLists]5. [endif] //私有化構造函數
[if !supportLists]6. [endif] private Singleton() {
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] //返回對象實例
[if !supportLists]10. [endif] public static Singleton getInstance() {
[if !supportLists]11. [endif] return instance;
[if !supportLists]12. [endif] }
[if !supportLists]13. [endif]}
懶漢式:
[if !supportLists]1. [endif]public class Singleton {
[if !supportLists]2. [endif] //聲明變量
[if !supportLists]3. [endif] private static volatile Singleton singleton = null;
[if !supportLists]4. [endif]
[if !supportLists]5. [endif] //私有構造函數
[if !supportLists]6. [endif] private Singleton() {
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] //提供對外方法
[if !supportLists]10. [endif] public static Singleton getInstance() {
[if !supportLists]11. [endif] if (singleton == null) {
[if !supportLists]12. [endif] synchronized (Singleton.class) {
[if !supportLists]13. [endif] if (singleton == null) {
[if !supportLists]14. [endif] singleton = new Singleton();
[if !supportLists]15. [endif] }
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif] return singleton;
[if !supportLists]19. [endif] }
[if !supportLists]20. [endif]}
[if !supportLists]3. [endif]工廠設計模式
工廠模式分爲工廠方法模式和抽象工廠模式。
工廠方法模式
工廠方法模式分爲三種:普通工廠模式,就是創建一個工廠類,對實現了同一接口的一些類進行實例的建立。
多個工廠方法模式,是對普通工廠方法模式的改進,在普通工廠方法模式中,若是傳遞的字符串出錯,則不能正確建立對象,而多個工廠方法模式是提供多個工廠方法,分別建立對象。
靜態工廠方法模式,將上面的多個工廠方法模式裏的方法置爲靜態的,不須要建立實例,直接調用便可。
普通工廠模式
[if !supportLists]1. [endif]public interface Sender {
[if !supportLists]2. [endif] public void Send();
[if !supportLists]3. [endif]}
[if !supportLists]4. [endif]public class MailSender implements Sender {
[if !supportLists]5. [endif]
[if !supportLists]6. [endif] @Override
[if !supportLists]7. [endif] public void Send() {
[if !supportLists]8. [endif] System.out.println("this is mail sender!");
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]}
[if !supportLists]11. [endif]public class SmsSender implements Sender {
[if !supportLists]12. [endif]
[if !supportLists]13. [endif] @Override
[if !supportLists]14. [endif] public void Send() {
[if !supportLists]15. [endif] System.out.println("this is sms sender!");
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif]}
[if !supportLists]18. [endif]public class SendFactory {
[if !supportLists]19. [endif] public Sender produce(String type) {
[if !supportLists]20. [endif] if ("mail".equals(type)) {
[if !supportLists]21. [endif] return new MailSender();
[if !supportLists]22. [endif] } else if ("sms".equals(type)) {
[if !supportLists]23. [endif] return new SmsSender();
[if !supportLists]24. [endif] } else {
[if !supportLists]25. [endif] System.out.println("請輸入正確的類型!");
[if !supportLists]26. [endif] return null;
[if !supportLists]27. [endif] }
[if !supportLists]28. [endif] }
[if !supportLists]29. [endif]}
多個工廠方法模式
該模式是對普通工廠方法模式的改進,在普通工廠方法模式中,若是傳遞的字符串出錯,則不能正確建立對象,而多個工廠方法模式是提供多個工廠方法,分別建立對象。
[if !supportLists]1. [endif]public class SendFactory {
[if !supportLists]2. [endif] public Sender produceMail(){
[if !supportLists]3. [endif] return new MailSender();
[if !supportLists]4. [endif] }
[if !supportLists]5. [endif]
[if !supportLists]6. [endif] public Sender produceSms(){
[if !supportLists]7. [endif] return new SmsSender();
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif]}
[if !supportLists]10. [endif]
[if !supportLists]11. [endif]public class FactoryTest {
[if !supportLists]12. [endif] public static void main(String[] args) {
[if !supportLists]13. [endif] SendFactory factory = new SendFactory();
[if !supportLists]14. [endif] Sender sender = factory.produceMail();
[if !supportLists]15. [endif] sender.send();
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif]}
靜態工廠方法模式,將上面的多個工廠方法模式裏的方法置爲靜態的,不須要建立實例,直接調用便可。
[if !supportLists]1. [endif]public class SendFactory {
[if !supportLists]2. [endif] public static Sender produceMail(){
[if !supportLists]3. [endif] return new MailSender();
[if !supportLists]4. [endif] }
[if !supportLists]5. [endif]
[if !supportLists]6. [endif] public static Sender produceSms(){
[if !supportLists]7. [endif] return new SmsSender();
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif]}
[if !supportLists]10. [endif]
[if !supportLists]11. [endif]
[if !supportLists]12. [endif]public class FactoryTest {
[if !supportLists]13. [endif] public static void main(String[] args) {
[if !supportLists]14. [endif] Sender sender = SendFactory.produceMail();
[if !supportLists]15. [endif] sender.send();
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif]}
抽象工廠模式
工廠方法模式有一個問題就是,類的建立依賴工廠類,也就是說,若是想要拓展程序,必須對工廠類進行修改,這違背了閉包原則,因此,從設計角度考慮,有必定的問題,如何解決?就用到抽象工廠模式,建立多個工廠類,這樣一旦須要增長新的功能,直接增長新的工廠類就能夠了,不須要修改以前的代碼。
[if !supportLists]1. [endif]public interface Provider {
[if !supportLists]2. [endif] public Sender produce();
[if !supportLists]3. [endif]}
[if !supportLists]4. [endif]-------------------------------------------------------------------------------------
[if !supportLists]5. [endif]public interface Sender {
[if !supportLists]6. [endif] public void send();
[if !supportLists]7. [endif]}
[if !supportLists]8. [endif]-------------------------------------------------------------------------------------
[if !supportLists]9. [endif]public class MailSender implements Sender {
[if !supportLists]10. [endif]
[if !supportLists]11. [endif] @Override
[if !supportLists]12. [endif] public void send() {
[if !supportLists]13. [endif] System.out.println("this is mail sender!");
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
[if !supportLists]16. [endif]-------------------------------------------------------------------------------------
[if !supportLists]17. [endif]public class SmsSender implements Sender {
[if !supportLists]18. [endif]
[if !supportLists]19. [endif] @Override
[if !supportLists]20. [endif] public void send() {
[if !supportLists]21. [endif] System.out.println("this is sms sender!");
[if !supportLists]22. [endif] }
[if !supportLists]23. [endif]}
[if !supportLists]24. [endif]-------------------------------------------------------------------------------------
[if !supportLists]25. [endif]public class SendSmsFactory implements Provider {
[if !supportLists]26. [endif]
[if !supportLists]27. [endif] @Override
[if !supportLists]28. [endif] public Sender produce() {
[if !supportLists]29. [endif] return new SmsSender();
[if !supportLists]30. [endif] }
[if !supportLists]31. [endif]}
[if !supportLists]1. [endif]public class SendMailFactory implements Provider {
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] @Override
[if !supportLists]4. [endif] public Sender produce() {
[if !supportLists]5. [endif] return new MailSender();
[if !supportLists]6. [endif] }
[if !supportLists]7. [endif]}
[if !supportLists]8. [endif]-------------------------------------------------------------------------------------
[if !supportLists]9. [endif]public class Test {
[if !supportLists]10. [endif] public static void main(String[] args) {
[if !supportLists]11. [endif] Provider provider = new SendMailFactory();
[if !supportLists]12. [endif] Sender sender = provider.produce();
[if !supportLists]13. [endif] sender.send();
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
[if !supportLists]4. [endif]建造者模式(Builder)
工廠類模式提供的是建立單個類的模式,而建造者模式則是將各類產品集中起來進行管理,用來建立複合對象,所謂複合對象就是指某個類具備不一樣的屬性,其實建造者模式就是前面抽象工廠模式和最後的Test結合起來獲得的。
[if !supportLists]1. [endif]public class Builder {
[if !supportLists]2. [endif] private List list = new ArrayList();
[if !supportLists]3. [endif]
[if !supportLists]4. [endif] public void produceMailSender(int count) {
[if !supportLists]5. [endif] for (int i = 0; i < count; i++) {
[if !supportLists]6. [endif] list.add(new MailSender());
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif]
[if !supportLists]10. [endif] public void produceSmsSender(int count) {
[if !supportLists]11. [endif] for (int i = 0; i < count; i++) {
[if !supportLists]12. [endif] list.add(new SmsSender());
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
[if !supportLists]1. [endif]public class Builder {
[if !supportLists]2. [endif] private List list = new ArrayList();
[if !supportLists]3. [endif]
[if !supportLists]4. [endif] public void produceMailSender(int count) {
[if !supportLists]5. [endif] for (int i = 0; i < count; i++) {
[if !supportLists]6. [endif] list.add(new MailSender());
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif] }
[if !supportLists]9. [endif]
[if !supportLists]10. [endif] public void produceSmsSender(int count) {
[if !supportLists]11. [endif] for (int i = 0; i < count; i++) {
[if !supportLists]12. [endif] list.add(new SmsSender());
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]}
[if !supportLists]1. [endif]public class TestBuilder {
[if !supportLists]2. [endif] public static void main(String[] args) {
[if !supportLists]3. [endif] Builder builder = new Builder();
[if !supportLists]4. [endif] builder.produceMailSender(10);
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif]}
[if !supportLists]5. [endif]適配器設計模式
適配器模式將某個類的接口轉換成客戶端指望的另外一個接口表示,目的是消除因爲接口不匹配所形成的類的兼容性問題。主要分爲三類:類的適配器模式、對象的適配器模式、接口的適配器模式。
類的適配器模式
[if !supportLists]1. [endif]public class Source {
[if !supportLists]2. [endif] public void method1() {
[if !supportLists]3. [endif] System.out.println("this is original method!");
[if !supportLists]4. [endif] }
[if !supportLists]5. [endif]}
[if !supportLists]6. [endif]-------------------------------------------------------------
[if !supportLists]7. [endif]public interface Targetable {
[if !supportLists]8. [endif] /*與原類中的方法相同 */
[if !supportLists]9. [endif] public void method1();
[if !supportLists]10. [endif] /*新類的方法 */
[if !supportLists]11. [endif] public void method2();
[if !supportLists]12. [endif]}
[if !supportLists]13. [endif]public class Adapter extends Source implements Targetable {
[if !supportLists]14. [endif] @Override
[if !supportLists]15. [endif] public void method2() {
[if !supportLists]16. [endif] System.out.println("this is the targetable method!");
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif]}
[if !supportLists]19. [endif]public class AdapterTest {
[if !supportLists]20. [endif] public static void main(String[] args) {
[if !supportLists]21. [endif] Targetable target = new Adapter();
[if !supportLists]22. [endif] target.method1();
[if !supportLists]23. [endif] target.method2();
[if !supportLists]24. [endif] }
[if !supportLists]25. [endif]}
對象的適配器模式
基本思路和類的適配器模式相同,只是將Adapter類做修改,此次不繼承Source類,而是持有Source類的實例,以達到解決兼容性的問題。
[if !supportLists]1. [endif]public class Wrapper implements Targetable {
[if !supportLists]2. [endif] private Source source;
[if !supportLists]3. [endif]
[if !supportLists]4. [endif] public Wrapper(Source source) {
[if !supportLists]5. [endif] super();
[if !supportLists]6. [endif] this.source = source;
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] @Override
[if !supportLists]10. [endif] public void method2() {
[if !supportLists]11. [endif] System.out.println("this is the targetable method!");
[if !supportLists]12. [endif] }
[if !supportLists]13. [endif]
[if !supportLists]14. [endif] @Override
[if !supportLists]15. [endif] public void method1() {
[if !supportLists]16. [endif] source.method1();
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif]}
[if !supportLists]19. [endif]--------------------------------------------------------------
[if !supportLists]20. [endif]public class AdapterTest {
[if !supportLists]21. [endif]
[if !supportLists]22. [endif] public static void main(String[] args) {
[if !supportLists]23. [endif] Source source = new Source();
[if !supportLists]24. [endif] Targetable target = new Wrapper(source);
[if !supportLists]25. [endif] target.method1();
[if !supportLists]26. [endif] target.method2();
[if !supportLists]27. [endif] }
[if !supportLists]28. [endif] }
接口的適配器模式
接口的適配器是這樣的:有時咱們寫的一個接口中有多個抽象方法,當咱們寫該接口的實現類時,必須實現該接口的全部方法,這明顯有時比較浪費,由於並非全部的方法都是咱們須要的,有時只須要某一些,此處爲了解決這個問題,咱們引入了接口的適配器模式,藉助於一個抽象類,該抽象類實現了該接口,實現了全部的方法,而咱們不和原始的接口打交道,只和該抽象類取得聯繫,因此咱們寫一個類,繼承該抽象類,重寫咱們須要的方法就行。
[if !supportLists]6. [endif]裝飾模式(Decorator)
顧名思義,裝飾模式就是給一個對象增長一些新的功能,並且是動態的,要求裝飾對象和被裝飾對象實現同一個接口,裝飾對象持有被裝飾對象的實例。
[if !supportLists]1. [endif]public interface Sourceable {
[if !supportLists]2. [endif] public void method();
[if !supportLists]3. [endif]}
[if !supportLists]4. [endif]----------------------------------------------------
[if !supportLists]5. [endif]public class Source implements Sourceable {
[if !supportLists]6. [endif] @Override
[if !supportLists]7. [endif] public void method() {
[if !supportLists]8. [endif] System.out.println("the original method!");
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]}
[if !supportLists]11. [endif]----------------------------------------------------
[if !supportLists]12. [endif]public class Decorator implements Sourceable {
[if !supportLists]13. [endif] private Sourceable source;
[if !supportLists]14. [endif] public Decorator(Sourceable source) {
[if !supportLists]15. [endif] super();
[if !supportLists]16. [endif] this.source = source;
[if !supportLists]17. [endif] }
[if !supportLists]18. [endif]
[if !supportLists]19. [endif] @Override
[if !supportLists]20. [endif] public void method() {
[if !supportLists]21. [endif] System.out.println("before decorator!");
[if !supportLists]22. [endif] source.method();
[if !supportLists]23. [endif] System.out.println("after decorator!");
[if !supportLists]24. [endif] }
[if !supportLists]25. [endif]}
[if !supportLists]26. [endif]----------------------------------------------------
[if !supportLists]27. [endif]public class DecoratorTest {
[if !supportLists]28. [endif] public static void main(String[] args) {
[if !supportLists]29. [endif] Sourceable source = new Source();
[if !supportLists]30. [endif] Sourceable obj = new Decorator(source);
[if !supportLists]31. [endif] obj.method();
[if !supportLists]32. [endif] }
[if !supportLists]33. [endif]}
[if !supportLists]7. [endif]策略模式(strategy)
策略模式定義了一系列算法,並將每一個算法封裝起來,使他們能夠相互替換,且算法的變化不會影響到使用算法的客戶。須要設計一個接口,爲一系列實現類提供統一的方法,多個實現類實現該接口,設計一個抽象類(無關緊要,屬於輔助類),提供輔助函數。策略模式的決定權在用戶,系統自己提供不一樣算法的實現,新增或者刪除算法,對各類算法作封裝。所以,策略模式多用在算法決策系統中,外部用戶只須要決定用哪一個算法便可。
[if !supportLists]1. [endif]public interface ICalculator {
[if !supportLists]2. [endif] public int calculate(String exp);
[if !supportLists]3. [endif]}
[if !supportLists]4. [endif]---------------------------------------------------------
[if !supportLists]5. [endif]public class Minus extends AbstractCalculator implements ICalculator {
[if !supportLists]6. [endif]
[if !supportLists]7. [endif] @Override
[if !supportLists]8. [endif] public int calculate(String exp) {
[if !supportLists]9. [endif] int arrayInt[] = split(exp, "-");
[if !supportLists]10. [endif] return arrayInt[0] - arrayInt[1];
[if !supportLists]11. [endif] }
[if !supportLists]12. [endif]}
[if !supportLists]13. [endif]---------------------------------------------------------
[if !supportLists]14. [endif]public class Plus extends AbstractCalculator implements ICalculator {
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] @Override
[if !supportLists]17. [endif] public int calculate(String exp) {
[if !supportLists]18. [endif] int arrayInt[] = split(exp, "\\+");
[if !supportLists]19. [endif] return arrayInt[0] + arrayInt[1];
[if !supportLists]20. [endif] }
[if !supportLists]21. [endif]}
[if !supportLists]22. [endif]--------------------------------------------------------
[if !supportLists]23. [endif]public class AbstractCalculator {
[if !supportLists]24. [endif] public int[] split(String exp, String opt) {
[if !supportLists]25. [endif] String array[] = exp.split(opt);
[if !supportLists]26. [endif] int arrayInt[] = new int[2];
[if !supportLists]27. [endif] arrayInt[0] = Integer.parseInt(array[0]);
[if !supportLists]28. [endif] arrayInt[1] = Integer.parseInt(array[1]);
[if !supportLists]29. [endif] return arrayInt;
[if !supportLists]30. [endif] }
[if !supportLists]31. [endif]}
[if !supportLists]1. [endif]public class StrategyTest {
[if !supportLists]2. [endif] public static void main(String[] args) {
[if !supportLists]3. [endif] String exp = "2+8";
[if !supportLists]4. [endif] ICalculator cal = new Plus();
[if !supportLists]5. [endif] int result = cal.calculate(exp);
[if !supportLists]6. [endif] System.out.println(result);
[if !supportLists]7. [endif] }
[if !supportLists]8. [endif]}
[if !supportLists]8. [endif]觀察者模式(Observer)
觀察者模式很好理解,相似於郵件訂閱和RSS訂閱,當咱們瀏覽一些博客或wiki時,常常會看到RSS圖標,就這的意思是,當你訂閱了該文章,若是後續有更新,會及時通知你。其實,簡單來說就一句話:當一個對象變化時,其它依賴該對象的對象都會收到通知,而且隨着變化!對象之間是一種一對多的關係。
[if !supportLists]1. [endif]public interface Observer {
[if !supportLists]2. [endif] public void update();
[if !supportLists]3. [endif]}
[if !supportLists]4. [endif]
[if !supportLists]5. [endif]public class Observer1 implements Observer {
[if !supportLists]6. [endif] @Override
[if !supportLists]7. [endif] public void update() {
[if !supportLists]8. [endif] System.out.println("observer1 has received!");
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]}
[if !supportLists]11. [endif]
[if !supportLists]12. [endif]public class Observer2 implements Observer {
[if !supportLists]13. [endif] @Override
[if !supportLists]14. [endif] public void update() {
[if !supportLists]15. [endif] System.out.println("observer2 has received!");
[if !supportLists]16. [endif] }
[if !supportLists]17. [endif]}
[if !supportLists]18. [endif]
[if !supportLists]19. [endif]public interface Subject {
[if !supportLists]20. [endif] /*增長觀察者*/
[if !supportLists]21. [endif] public void add(Observer observer);
[if !supportLists]22. [endif]
[if !supportLists]23. [endif]/*刪除觀察者*/
[if !supportLists]24. [endif] public void del(Observer observer);
25./*通知全部的觀察者*/
[if !supportLists]1. [endif] public void notifyObservers();
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]/*自身的操做*/
[if !supportLists]4. [endif] public void operation();
[if !supportLists]5. [endif]}
[if !supportLists]6. [endif]
[if !supportLists]7. [endif]public abstract class AbstractSubject implements Subject {
[if !supportLists]8. [endif]
[if !supportLists]9. [endif] private Vector vector = new Vector();
[if !supportLists]10. [endif]
[if !supportLists]11. [endif] @Override
[if !supportLists]12. [endif] public void add(Observer observer) {
[if !supportLists]13. [endif] vector.add(observer);
[if !supportLists]14. [endif] }
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] @Override
[if !supportLists]17. [endif] public void del(Observer observer) {
[if !supportLists]18. [endif] vector.remove(observer);
[if !supportLists]19. [endif] }
[if !supportLists]20. [endif]
[if !supportLists]21. [endif] @Override
[if !supportLists]22. [endif] public void notifyObservers() {
[if !supportLists]23. [endif] Enumeration enumo = vector.elements();
[if !supportLists]24. [endif] while (enumo.hasMoreElements()) {
[if !supportLists]25. [endif] enumo.nextElement().update();
[if !supportLists]26. [endif] }
[if !supportLists]27. [endif] }
[if !supportLists]28. [endif]}
[if !supportLists]29. [endif]
[if !supportLists]30. [endif]public class MySubject extends AbstractSubject {
[if !supportLists]31. [endif]
[if !supportLists]32. [endif] @Override
[if !supportLists]33. [endif] public void operation() {
[if !supportLists]34. [endif] System.out.println("update self!");
[if !supportLists]35. [endif] notifyObservers();
[if !supportLists]36. [endif] }
[if !supportLists]37. [endif]}
[if !supportLists]38. [endif]
[if !supportLists]39. [endif]public class ObserverTest {
[if !supportLists]40. [endif] public static void main(String[] args) {
[if !supportLists]41. [endif] Subject sub = new MySubject();
[if !supportLists]42. [endif] sub.add(new Observer1());
[if !supportLists]43. [endif] sub.add(new Observer2());
[if !supportLists]44. [endif] sub.operation();
[if !supportLists]45. [endif] }
[if !supportLists]46. [endif]}
[if !supportLists]9. [endif]JVM垃圾回收機制和常見算法
理論上來說Sun公司只定義了垃圾回收機制規則而不侷限於其實現算法,所以不一樣廠商生產的虛擬機採用的算法也不盡相同。
GC(Garbage Collector)在回收對象前首先必須發現那些無用的對象,如何去發現定位這些無用的對象?經常使用的搜索算法以下:
[if !supportLists]1)[endif]引用計數器算法(廢棄)
引用計數器算法是給每一個對象設置一個計數器,當有地方引用這個對象的時候,計數器+1,當引用失效的時候,計數器-1,當計數器爲0的時候,JVM就認爲對象再也不被使用,是「垃圾」了。
引用計數器實現簡單,效率高;可是不能解決循環引用問問題(A對象引用B對象,B對象又引用A對象,可是A,B對象已不被任何其餘對象引用),同時每次計數器的增長和減小都帶來了不少額外的開銷,因此在JDK1.1以後,這個算法已經再也不使用了。
[if !supportLists]2)[endif]根搜索算法(使用)
根搜索算法是經過一些「GC Roots」對象做爲起點,從這些節點開始往下搜索,搜索經過的路徑成爲引用鏈(Reference Chain),當一個對象沒有被GC Roots的引用鏈鏈接的時候,說明這個對象是不可用的。
GC Roots對象包括:
a) 虛擬機棧(棧幀中的本地變量表)中的引用的對象。
b) 方法區域中的類靜態屬性引用的對象。
c) 方法區域中常量引用的對象。
d) 本地方法棧中JNI(Native方法)的引用的對象。
經過上面的算法搜索到無用對象以後,就是回收過程,回收算法以下:
[if !supportLists]1)[endif]標記—清除算法(Mark-Sweep)(DVM使用的算法)
標記—清除算法包括兩個階段:「標記」和「清除」。在標記階段,肯定全部要回收的對象,並作標記。清除階段緊隨標記階段,將標記階段肯定不可用的對象清除。標記—清除算法是基礎的收集算法,標記和清除階段的效率不高,並且清除後回產生大量的不連續空間,這樣當程序須要分配大內存對象時,可能沒法找到足夠的連續空間。
[if !supportLists]2)[endif]複製算法(Copying)
複製算法是把內存分紅大小相等的兩塊,每次使用其中一塊,當垃圾回收的時候,把存活的對象複製到另外一塊上,而後把這塊內存整個清理掉。複製算法實現簡單,運行效率高,可是因爲每次只能使用其中的一半,形成內存的利用率不高。如今的JVM用複製方法收集新生代,因爲新生代中大部分對象(98%)都是朝生夕死的,因此兩塊內存的比例不是1:1(大概是8:1)。
[if !supportLists]3)[endif]標記—整理算法(Mark-Compact)
標記—整理算法和標記—清除算法同樣,可是標記—整理算法不是把存活對象複製到另外一塊內存,而是把存活對象往內存的一端移動,而後直接回收邊界之外的內存。標記—整理算法提升了內存的利用率,而且它適合在收集對象存活時間較長的老年代。
[if !supportLists]4)[endif]分代收集(Generational Collection)
分代收集是根據對象的存活時間把內存分爲新生代和老年代,根據各個代對象的存活特色,每一個代採用不一樣的垃圾回收算法。新生代採用複製算法,老年代採用標記—整理算法。垃圾算法的實現涉及大量的程序細節,並且不一樣的虛擬機平臺實現的方法也各不相同。
[if !supportLists]10. [endif]談談JVM的內存結構和內存分配
[if !supportLists]a)[endif] Java內存模型
Java虛擬機將其管轄的內存大體分三個邏輯部分:方法區(Method Area)、Java棧和Java堆。
一、方法區是靜態分配的,編譯器將變量綁定在某個存儲位置上,並且這些綁定不會在運行時改變。
常數池,源代碼中的命名常量、String常量和static變量保存在方法區。
二、Java Stack是一個邏輯概念,特色是後進先出。一個棧的空間多是連續的,也多是不連續的。
最典型的Stack應用是方法的調用,Java虛擬機每調用一次方法就建立一個方法幀(frame),退出該方法則對應的 方法幀被彈出(pop)。棧中存儲的數據也是運行時肯定的。
三、Java堆分配(heap allocation)意味着以隨意的順序,在運行時進行存儲空間分配和收回的內存管理模型。
堆中存儲的數據經常是大小、數量和生命期在編譯時沒法肯定的。Java對象的內存老是在heap中分配。
咱們天天都在寫代碼,天天都在使用JVM的內存。
[if !supportLists]b)[endif] java內存分配
一、基礎數據類型直接在棧空間分配;
二、方法的形式參數,直接在棧空間分配,當方法調用完成後從棧空間回收;
三、引用數據類型,須要用new來建立,既在棧空間分配一個地址空間,又在堆空間分配對象的類變量;
四、方法的引用參數,在棧空間分配一個地址空間,並指向堆空間的對象區,當方法調用完後從棧空間回收;
五、局部變量 new 出來時,在棧空間和堆空間中分配空間,當局部變量生命週期結束後,棧空間馬上被回收,堆空間區域等待GC回收;
六、方法調用時傳入的實際參數,先在棧空間分配,在方法調用完成後從棧空間釋放;
七、字符串常量在 DATA 區域分配 ,this 在堆空間分配;
八、數組既在棧空間分配數組名稱, 又在堆空間分配數組實際的大小!
[if !supportLists]11. [endif]Java中引用類型都有哪些?(重要)
Java中對象的引用分爲四種級別,這四種級別由高到低依次爲:強引用、軟引用、弱引用和虛引用。
強引用(StrongReference)
這個就很少說,咱們寫代碼每天在用的就是強引用。若是一個對象被被人擁有強引用,那麼垃圾回收器毫不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足問題。
Java的對象是位於heap中的,heap中對象有強可及對象、軟可及對象、弱可及對象、虛可及對象和不可到達對象。應用的強弱順序是強、軟、弱、和虛。對於對象是屬於哪一種可及的對象,由他的最強的引用決定。以下代碼:
[if !supportLists]1. [endif] String abc=new String("abc"); //1
[if !supportLists]2. [endif] SoftReference softRef=new SoftReference(abc); //2
[if !supportLists]3. [endif] WeakReference weakRef = new WeakReference(abc); //3
[if !supportLists]4. [endif] abc=null; //4
[if !supportLists]5. [endif] softRef.clear();//5
第一行在heap堆中建立內容爲「abc」的對象,並創建abc到該對象的強引用,該對象是強可及的。
第二行和第三行分別創建對heap中對象的軟引用和弱引用,此時heap中的abc對象已經有3個引用,顯然此時abc對象還是強可及的。
第四行以後heap中對象再也不是強可及的,變成軟可及的。
第五行執行以後變成弱可及的。
軟引用(SoftReference)
若是一個對象只具備軟引用,那麼若是內存空間足夠,垃圾回收器就不會回收它,若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存。
軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
軟引用是主要用於內存敏感的高速緩存。在jvm報告內存不足以前會清除全部的軟引用,這樣以來gc就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。何時會被收集取決於gc的算法和gc運行時可用內存的大小。當gc決定要收集軟引用時執行如下過程,以上面的softRef爲例:
1 首先將softRef的referent(abc)設置爲null,再也不引用heap中的new String("abc")對象。
2 將heap中的new String("abc")對象設置爲可結束的(finalizable)。
3 當heap中的new String("abc")對象的finalize()方法被運行並且該對象佔用的內存被釋放, softRef被添加到它的ReferenceQueue(若是有的話)中。
注意:對ReferenceQueue軟引用和弱引用能夠有可無,可是虛引用必須有。
被Soft Reference 指到的對象,即便沒有任何 Direct Reference,也不會被清除。一直要到 JVM 內存不足且沒有Direct Reference 時纔會清除,SoftReference 是用來設計 object-cache 之用的。如此一來 SoftReference 不但能夠把對象 cache 起來,也不會形成內存不足的錯誤 (OutOfMemoryError)。
弱引用(WeakReference)
若是一個對象只具備弱引用,那該類就是無關緊要的對象,由於只要該對象被gc掃描到了隨時都會把它幹掉。弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
虛引用(PhantomReference)
"虛引用"顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。虛引用主要用來跟蹤對象被垃圾回收的活動。
虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之關聯的引用隊列中。程序能夠經過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序若是發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動。
創建虛引用以後經過get方法返回結果始終爲null,經過源代碼你會發現,虛引用通向會把引用的對象寫進referent,只是get方法返回結果爲null。先看一下和gc交互的過程再說一下他的做用。
1 不把referent設置爲null, 直接把heap中的new String("abc")對象設置爲可結束的(finalizable)。
2 與軟引用和弱引用不一樣, 先把PhantomRefrence對象添加到它的ReferenceQueue中.而後在釋放虛可及的對象。
[if !supportLists]12. [endif]heap和stack有什麼區別(2017-2-23)
從如下幾個方面闡述堆(heap)和棧(stack)的區別。
1. 申請方式
stack:由系統自動分配。例如,聲明在函數中一個局部變量 int b; 系統自動在棧中爲b開闢空間
heap:須要程序員本身申請,並指明大小,在c中malloc函數,對於Java須要手動new Object()的形式開闢
2. 申請後系統的響應
stack:只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,不然將報異常提示棧溢出。
heap:首先應該知道操做系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,
會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,而後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。另外,因爲找到的堆結點的大小不必定正好等於申請的大小,系統會自動的將多餘的那部分從新放入空閒鏈表中。
3. 申請大小的限制
stack:棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在 WINDOWS下,棧的大小是2M(也有的說是1M,總之是一個編譯時就肯定的常數),若是申請的空間超過棧的剩餘空間時,將提示overflow。所以,能從棧得到的空間較小。
heap:堆是向高地址擴展的數據結構,是不連續的內存區域。這是因爲系統是用鏈表來存儲的空閒內存地址的,天然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。因而可知,堆得到的空間比較靈活,也比較大。
4. 申請效率的比較:
stack:由系統自動分配,速度較快。但程序員是沒法控制的。
heap:由new分配的內存,通常速度比較慢,並且容易產生內存碎片,不過用起來最方便。
5. heap和stack中的存儲內容
stack: 在函數調用時,第一個進棧的是主函數中後的下一條指令(函數調用語句的下一條可執行語句)的地址,而後是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,而後是函數中的局部變量。注意靜態變量是不入棧的。
當本次函數調用結束後,局部變量先出棧,而後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
heap:通常是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。
6. 數據結構層面的區別
還有就是數據結構方面的堆和棧,這些都是不一樣的概念。這裏的堆實際上指的就是(知足堆性質的)優先隊列的一種數據結構,第1個元素有最高的優先權;棧實際上就是知足先進後出的性質的數學或數據結構。
雖然堆棧,堆棧的說法是連起來叫,可是他們仍是有很大區別的,連着叫只是因爲歷史的緣由。
7. 拓展知識(Java中堆棧的應用)
1). 棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方。與C++不一樣,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
2). 棧的優點是,存取速度比堆要快,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。另外,棧數據能夠共享,詳見第3點。堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,Java的垃圾回收器會自動收走這些再也不使用的數據。但缺點是,因爲要在運行時動態分配內存,存取速度較慢。
3). Java中的數據類型有兩種。
一種是基本類型(primitive types), 共有8種,即int, short, long, byte, float, double, boolean, char(注意,並無string的基本類型)。這種類型的定義是經過諸如int a = 3; long b = 255L;的形式來定義的,稱爲自動變量(自動變量:只在定義它們的時候才建立,在定義它們的函數返回時系統回收變量所佔存儲空間。對這些變量存儲空間的分配和回收是由系統自動完成的。)。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這裏並無類的存在。如int a = 3; 這裏的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,因爲大小可知,生存期可知(這些字面值固定定義在某個程序塊裏面,程序塊退出後,字段值就消失了),出於追求速度的緣由,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據能夠共享。假設咱們同時定義
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中建立一個變量爲a的引用,而後查找有沒有字面值爲3的地址,沒找到,就開闢一個存放3這個字面值的地址,而後將a指向3的地址。接着處理int b = 3;在建立完b的引用變量後,因爲在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的狀況。
特別注意的是,這種字面值的引用與類對象的引用不一樣。假定兩個類對象的引用同時指向一個對象,若是一個對象引用變量修改了這個對象的內部狀態,那麼另外一個對象引用變量也即刻反映出這個變化。相反,經過字面值的引用來修改其值,不會致使另外一個指向此字面值的引用的值也跟着改變的狀況。如上例,咱們定義完a與 b的值後,再令a=4;那麼,b不會等於4,仍是等於3。在編譯器內部,遇到a=4;時,它就會從新搜索棧中是否有4的字面值,若是沒有,從新開闢地址存放4的值;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。
另外一種是包裝類數據,如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些類數據所有存在於堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據須要動態建立,所以比較靈活,但缺點是要佔用更多的時間。
4).每一個JVM的線程都有本身的私有的棧空間,隨線程建立而建立,java的stack存放的是frames,java的stack和c的不一樣,只是存放本地變量,返回值和調用方法,不容許直接push和pop frames ,由於frames 多是有heap分配的,因此java的stack分配的內存不須要是連續的。java的heap是全部線程共享的,堆存放全部 runtime data ,裏面是全部的對象實例和數組,heap是JVM啓動時建立。
5). String是一個特殊的包裝類數據。便可以用String str = new String("abc");的形式來建立,也能夠用String str = "abc";的形式來建立(做爲對比,在JDK 5.0以前,你從未見過Integer i = 3;的表達式,由於類與字面值是不能通用的,除了String。而在JDK 5.0中,這種表達式是能夠的!由於編譯器在後臺進行Integer i = new Integer(3)的轉換)。前者是規範的類的建立過程,即在Java中,一切都是對象,而對象是類的實例,所有經過new()的形式來建立。那爲何在String str = "abc";中,並無經過new()來建立實例,是否是違反了上述原則?其實沒有。
5.1). 關於String str = "abc"的內部工做。Java內部將此語句轉化爲如下幾個步驟:
(1)先定義一個名爲str的對String類的對象引用變量:String str;
(2)在棧中查找有沒有存放值爲"abc"的地址,若是沒有,則開闢一個存放字面值爲"abc"的地址,接着建立一個新的String類的對象o,並將o 的字符串值指向這個地址,並且在棧中這個地址旁邊記下這個引用的對象o。若是已經有了值爲"abc"的地址,則查找對象o,並返回o的地址。
(3)將str指向對象o的地址。
值得注意的是,通常String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值倒是保存了一個指向存在棧中數據的引用!
爲了更好地說明這個問題,咱們能夠經過如下的幾個代碼進行驗證。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
注意,咱們這裏並不用str1.equals(str2);的方式,由於這將比較兩個字符串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而咱們在這裏要看的是,str1與str2是否都指向了同一個對象。
結果說明,JVM建立了兩個引用str1和str2,但只建立了一個對象,並且兩個引用都指向了這個對象。
咱們再來更進一步,將以上代碼改爲:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
這就是說,賦值的變化致使了類對象引用的變化,str1指向了另一個新對象!而str2仍舊指向原來的對象。上例中,當咱們將str1的值改成"bcd"時,JVM發如今棧中沒有存放該值的地址,便開闢了這個地址,並建立了一個新的對象,其字符串的值指向這個地址。
事實上,String類被設計成爲不可改變(immutable)的類。若是你要改變其值,能夠,但JVM在運行時根據新值悄悄建立了一個新對象,而後將這個對象的地址返回給原來類的引用。這個建立過程雖然說是徹底自動進行的,但它畢竟佔用了更多的時間。在對時間要求比較敏感的環境中,會帶有必定的不良影響。
再修改原來代碼:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;
System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true
str3 這個對象的引用直接指向str1所指向的對象(注意,str3並無建立新對象)。當str1改完其值後,再建立一個String的引用str4,並指向因str1修改值而建立的新的對象。能夠發現,這回str4也沒有建立新的對象,從而再次實現棧中數據的共享。
咱們再接着看如下的代碼。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false
建立了兩個引用。建立了兩個對象。兩個引用分別指向不一樣的兩個對象。
以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中建立,並且其字符串是單獨存值的,即便與棧中的數據相同,也不會與棧中的數據共享。
6). 數據類型包裝類的值不可修改。不只僅是String類的值不可修改,全部的數據類型包裝類都不能更改其內部的值。
7). 結論與建議:
(1)咱們在使用諸如String str = "abc";的格式定義類時,老是想固然地認爲,咱們建立了String類的對象str。擔憂陷阱!對象可能並無被建立!惟一能夠確定的是,指向 String類的引用被建立了。至於這個引用究竟是否指向了一個新的對象,必須根據上下文來考慮,除非你經過new()方法來顯要地建立一個新的對象。所以,更爲準確的說法是,咱們建立了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值爲"abc"的String類。清醒地認識到這一點對排除程序中難以發現的bug是頗有幫助的。
(2)使用String str = "abc";的方式,能夠在必定程度上提升程序的運行速度,由於JVM會自動根據棧中數據的實際狀況來決定是否有必要建立新對象。而對於String str = new String("abc");的代碼,則一律在堆中建立新對象,而無論其字符串值是否相等,是否有必要建立新對象,從而加劇了程序的負擔。這個思想應該是享元模式的思想,但JDK的內部在這裏實現是否應用了這個模式,不得而知。
(3)當比較包裝類裏面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==。
(4)因爲String類的immutable性質,當String變量須要常常變換其值時,應該考慮使用StringBuffer類,以提升程序效率。
若是java不能成功分配heap的空間,將拋出OutOfMemoryError。
[if !supportLists]13. [endif]解釋內存中的棧 (stack) 、堆 (heap) 和方法區 (method area) 的用法(2017-11-12-wl)
一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用JVM中的棧空間;而經過new關鍵字和構造器建立的對象則放在堆空間,堆是垃圾收集器管理的主要區域,因爲如今的垃圾收集器都採用分代收集算法,因此堆空間還能夠細分爲新生代和老生代,再具體一點能夠分爲 Eden、Survivor(又可分爲 From Survivor 和 To Survivor)、Tenured;方法區和堆都是各個線程共享的內存區域,用於存儲已經被JVM加載的類信息、常量、靜態變量、JIT 編譯器編譯後的代碼等數據;程序中的字面量(literal)如直接書寫的 100、"hello"和常量都是放在常量池中,常量池是方法區的一部分。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,棧和堆的大小均可以經過 JVM 的啓動參數來進行調整,棧空間用光了會引起 StackOverflowError,而堆和常量池空間不足則會引起 OutOfMemoryError。
String str = new String("hello");
上面的語句中變量str 放在棧上,用 new 建立出來的字符串對象放在堆上,而"hello"這個字面量是放在方法區的。
[if !supportLists]4、[endif]Java的類加載器(2015-12-2)
[if !supportLists]1. [endif]Java的類加載器的種類都有哪些?
一、根類加載器(Bootstrap) --C++寫的 ,看不到源碼
二、擴展類加載器(Extension) --加載位置 :jre\lib\ext中
三、系統(應用)類加載器(System\App) --加載位置 :classpath中
四、自定義加載器(必須繼承ClassLoader)
[if !supportLists]2. [endif]類何時被初始化?
1)建立類的實例,也就是new一個對象
2)訪問某個類或接口的靜態變量,或者對該靜態變量賦值
3)調用類的靜態方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一個類的子類(會首先初始化子類的父類)
6)JVM啓動時標明的啓動類,即文件名和類名相同的那個類
只有這6中狀況纔會致使類的類的初始化。
類的初始化步驟:
1)若是這個類尚未被加載和連接,那先進行加載和連接
2)假如這個類存在直接父類,而且這個類尚未被初始化(注意:在一個類加載器中,類只能初始化一次),那就初始化直接的父類(不適用於接口)
3)加入類中存在初始化語句(如static變量和static塊),那就依次執行這些初始化語句。
[if !supportLists]3. [endif]Java類加載體系之ClassLoader雙親委託機制 (2017-2-24)
java是一種類型安全的語言,它有四類稱爲安全沙箱機制的安全機制來保證語言的安全性,這四類安全沙箱分別是:
[if !supportLists]1) [endif]類加載體系
[if !supportLists]2) [endif].class文件檢驗器
[if !supportLists]3) [endif]內置於Java虛擬機(及語言)的安全特性
[if !supportLists]4) [endif]安全管理器及Java API
主要講解類的加載體系:
java程序中的 .java文件編譯完會生成 .class文件,而 .class文件就是經過被稱爲類加載器的ClassLoader加載的,而ClassLoder在加載過程當中會使用「雙親委派機制」來加載 .class文件,先上圖:
BootStrapClassLoader:啓動類加載器,該ClassLoader是jvm在啓動時建立的,用於加載 $JAVA_HOME$/jre/lib下面的類庫(或者經過參數-Xbootclasspath指定)。因爲啓動類加載器涉及到虛擬機本地實現細節,開發者沒法直接獲取到啓動類加載器的引用,因此不能直接經過引用進行操做。
ExtClassLoader:擴展類加載器,該ClassLoader是在sun.misc.Launcher裏做爲一個內部類ExtClassLoader定義的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader會加載 $JAVA_HOME/jre/lib/ext下的類庫(或者經過參數-Djava.ext.dirs指定)。
AppClassLoader:應用程序類加載器,該ClassLoader一樣是在sun.misc.Launcher裏做爲一個內部類AppClassLoader定義的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader會加載java環境變量CLASSPATH所指定的路徑下的類庫,而CLASSPATH所指定的路徑能夠經過System.getProperty("java.class.path")獲取;固然,該變量也能夠覆蓋,能夠使用參數-cp,例如:java -cp 路徑 (能夠指定要執行的class目錄)。
CustomClassLoader:自定義類加載器,該ClassLoader是指咱們自定義的ClassLoader,好比tomcat的StandardClassLoader屬於這一類;固然,大部分狀況下使用AppClassLoader就足夠了。
前面談到了ClassLoader的幾類加載器,而ClassLoader使用雙親委派機制來加載class文件的。ClassLoader的雙親委派機制是這樣的(這裏先忽略掉自定義類加載器CustomClassLoader):
1)當AppClassLoader加載一個class時,它首先不會本身去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
2)當ExtClassLoader加載一個class時,它首先也不會本身去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
3)若是BootStrapClassLoader加載失敗(例如在$JAVA_HOME$/jre/lib裏未查找到該class),會使用ExtClassLoader來嘗試加載;
4)若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,若是AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。
下面貼下ClassLoader的loadClass(String name, boolean resolve)的源碼:
[if !supportLists]1.[endif]protected synchronized Class loadClass(String name, boolean resolve)
[if !supportLists]2.[endif] throws ClassNotFoundException {
[if !supportLists]3.[endif] // 首先找緩存是否有class
[if !supportLists]4.[endif] Class c = findLoadedClass(name);
[if !supportLists]5.[endif] if (c == null) {
[if !supportLists]6.[endif]//沒有判斷有沒有父類
[if !supportLists]7.[endif] try {
[if !supportLists]8.[endif] if (parent != null) {
[if !supportLists]9.[endif]//有的話,用父類遞歸獲取class
[if !supportLists]10.[endif] c = parent.loadClass(name, false);
[if !supportLists]11.[endif] } else {
[if !supportLists]12.[endif]//沒有父類。經過這個方法來加載
[if !supportLists]13.[endif] c = findBootstrapClassOrNull(name);
[if !supportLists]14.[endif] }
[if !supportLists]15.[endif] } catch (ClassNotFoundException e) {
[if !supportLists]16.[endif] // ClassNotFoundException thrown if class not found
[if !supportLists]17.[endif] // from the non-null parent class loader
[if !supportLists]18.[endif] }
[if !supportLists]19.[endif] if (c == null) {
[if !supportLists]20.[endif] // 若是仍是沒有找到,調用findClass(name)去找這個類
[if !supportLists]21.[endif] c = findClass(name);
[if !supportLists]22.[endif] }
[if !supportLists]23.[endif] }
[if !supportLists]24.[endif] if (resolve) {
[if !supportLists]25.[endif] resolveClass(c);
[if !supportLists]26.[endif] }
[if !supportLists]27.[endif] return c;
[if !supportLists]28.[endif] }
代碼很明朗:首先找緩存(findLoadedClass),沒有的話就判斷有沒有parent,有的話就用parent來遞歸的loadClass,然而ExtClassLoader並無設置parent,則會經過findBootstrapClassOrNull來加載class,而findBootstrapClassOrNull則會經過JNI方法」private native Class findBootstrapClass(String name)「來使用BootStrapClassLoader來加載class。
而後若是parent未找到class,則會調用findClass來加載class,findClass是一個protected的空方法,能夠覆蓋它以便自定義class加載過程。
另外,雖然ClassLoader加載類是使用loadClass方法,可是鼓勵用 ClassLoader 的子類重寫 findClass(String),而不是重寫loadClass,這樣就不會覆蓋了類加載默認的雙親委派機制。
雙親委派託機制爲何安全
舉個例子,ClassLoader加載的class文件來源不少,好比編譯器編譯生成的class、或者網絡下載的字節碼。而一些來源的class文件是不可靠的,好比我能夠自定義一個java.lang.Integer類來覆蓋jdk中默認的Integer類,例以下面這樣:
[if !supportLists]1. [endif]package java.lang;
[if !supportLists]2. [endif]public class Integer {
[if !supportLists]3. [endif] public Integer(int value) {
[if !supportLists]4. [endif] System.exit(0);
[if !supportLists]5. [endif] }
[if !supportLists]6. [endif]}
初始化這個Integer的構造器是會退出JVM,破壞應用程序的正常進行,若是使用雙親委派機制的話該Integer類永遠不會被調用,覺得委託BootStrapClassLoader加載後會加載JDK中的Integer類而不會加載自定義的這個,能夠看下下面這測試個用例:
[if !supportLists]1.[endif]public static void main(String... args) {
[if !supportLists]2.[endif] Integer i = new Integer(1);
[if !supportLists]3.[endif] System.err.println(i);
[if !supportLists]4.[endif] }
執行時JVM並未在new Integer(1)時退出,說明未使用自定義的Integer,因而就保證了安全性。
[if !supportLists]4. [endif]描述一下JVM加載class (2017-11-15-wl)
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java 中的類加載器是一個重要的Java 運行時系統組件,它負責在運行時查找和裝入類文件中的類。
因爲Java的跨平臺性,通過編譯的 Java 源程序並非一個可執行程序,而是一個或多個類文件。當Java程序須要使用某個類時,JVM會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。類的加載是指把類的.class 文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class 文件,而後產生與所加載類對應的 Class 對象。加載完成後,Class 對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後 JVM 對類進行初始化,包括:
若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;
若是類中存在初始化語句,就依次執行這些初始化語句。類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader 的子類)。
從Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM 更好的保證了 Java 平臺的安全性,在該機制中,JVM 自帶的 Bootstrap是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java 程序提供對 Bootstrap 的引用。
下面是關於幾個類加載器的說明:
• Bootstrap:通常用本地代碼實現,負責加載 JVM 基礎核心類庫(rt.jar);
• Extension:從 java.ext.dirs 系統屬性所指定的目錄中加載類庫,它的父加載器是 Bootstrap;
• System:又叫應用類加載器,其父類是 Extension。它是應用最普遍的類加載器。它從環境變量classpath
或者系統屬性java.class.path 所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。
[if !supportLists]5. [endif]得到一個類對象有哪些方式?(2017-11-23-wzz)
類型.class,例如:String.class
對象.getClass(),例如:」hello」.getClass()
Class.forName(),例如:Class.forName(「java.lang.String」)
[if !supportLists]5、[endif]JVM基礎知識(2017-11-16-wl)
[if !supportLists]1. [endif]既然有GC機制,爲何還會有內存泄露的狀況(2017-11-16-wl)
理論上Java 由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是 Java 被普遍使用於服務器端編程的一個重要緣由)。然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被 GC 回收,所以也會致使內存泄露的發生。
例如hibernate 的 Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。
下面例子中的代碼也會致使內存泄露。
[if !supportLists]1. [endif]import java.util.Arrays;
[if !supportLists]2. [endif]import java.util.EmptyStackException;
[if !supportLists]3. [endif]public class MyStack {
[if !supportLists]4. [endif] private T[] elements;
[if !supportLists]5. [endif] private int size = 0;
[if !supportLists]6. [endif] private static final int INIT_CAPACITY = 16;
[if !supportLists]7. [endif] public MyStack() {
[if !supportLists]8. [endif] elements = (T[]) new Object[INIT_CAPACITY];
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif] public void push(T elem) {
[if !supportLists]11. [endif] ensureCapacity();
[if !supportLists]12. [endif] elements[size++] = elem;
[if !supportLists]13. [endif] }
[if !supportLists]14. [endif] public T pop() {
[if !supportLists]15. [endif] if(size == 0)throw new EmptyStackException();
[if !supportLists]16. [endif] return elements[--size];
[if !supportLists]17. [endif]}
[if !supportLists]18. [endif] private void ensureCapacity() {
[if !supportLists]19. [endif] if(elements.length == size) {
[if !supportLists]20. [endif] elements = Arrays.copyOf(elements, 2 * size + 1);
[if !supportLists]21. [endif] }
[if !supportLists]22. [endif]}
[if !supportLists]23. [endif]}
上面的代碼實現了一個棧(先進後出(FILO))結構,乍看之下彷佛沒有什麼明顯的問題,它甚至能夠經過你編寫的各類單元測試。然而其中的 pop 方法卻存在內存泄露的問題,當咱們用 pop 方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起 Disk Paging (物理內存與硬盤的虛擬內存交換數據),甚至形成 OutOfMemoryError。
[if !supportLists]6、[endif]GC基礎知識(2017-11-16-wl)
[if !supportLists]1. [endif]Java中爲何會有GC機制呢?(2017-11-16-wl)
Java中爲何會有GC機制呢?
[if !supportLists]· [endif]安全性考慮;-- for security.
[if !supportLists]· [endif]減小內存泄露;-- erase memory leak in some degree.
[if !supportLists]· [endif]減小程序員工做量。-- Programmers don't worry about memory releasing.
[if !supportLists]2. [endif]對於Java的GC哪些內存須要回收(2017-11-16-wl)
內存運行時JVM會有一個運行時數據區來管理內存。它主要包括5大部分:程序計數器(Program Counter Register)、虛擬機棧(VM Stack)、本地方法棧(Native Method Stack)、方法區(Method Area)、堆(Heap).
而其中程序計數器、虛擬機棧、本地方法棧是每一個線程私有的內存空間,隨線程而生,隨線程而亡。例如棧中每個棧幀中分配多少內存基本上在類結構肯定是哪一個時就已知了,所以這3個區域的內存分配和回收都是肯定的,無需考慮內存回收的問題。
但方法區和堆就不一樣了,一個接口的多個實現類須要的內存可能不同,咱們只有在程序運行期間纔會知道會建立哪些對象,這部份內存的分配和回收都是動態的,GC主要關注的是這部份內存。
總而言之,GC主要進行回收的內存是JVM中的方法區和堆;
[if !supportLists]3. [endif]Java的GC何時回收垃圾(2017-11-16-wl)
在面試中常常會碰到這樣一個問題(事實上筆者也碰到過):如何判斷一個對象已經死去?
很容易想到的一個答案是:對一個對象添加引用計數器。每當有地方引用它時,計數器值加1;當引用失效時,計數器值減1.而當計數器的值爲0時這個對象就不會再被使用,判斷爲已死。是否是簡單又直觀。然而,很遺憾。這種作法是錯誤的!爲何是錯的呢?事實上,用引用計數法確實在大部分狀況下是一個不錯的解決方案,而在實際的應用中也有很多案例,但它卻沒法解決對象之間的循環引用問題。好比對象A中有一個字段指向了對象B,而對象B中也有一個字段指向了對象A,而事實上他們倆都再也不使用,但計數器的值永遠都不可能爲0,也就不會被回收,而後就發生了內存泄露。
因此,正確的作法應該是怎樣呢? 在Java,C#等語言中,比較主流的斷定一個對象已死的方法是:可達性分析(Reachability Analysis).全部生成的對象都是一個稱爲"GC Roots"的根的子樹。從GC Roots開始向下搜索,搜索所通過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈能夠到達時,就稱這個對象是不可達的(不可引用的),也就是能夠被GC回收了。
不管是引用計數器仍是可達性分析,斷定對象是否存活都與引用有關!那麼,如何定義對象的引用呢?
咱們但願給出這樣一類描述:當內存空間還夠時,可以保存在內存中;若是進行了垃圾回收以後內存空間仍舊很是緊張,則能夠拋棄這些對象。因此根據不一樣的需求,給出以下四種引用,根據引用類型的不一樣,GC回收時也會有不一樣的操做:
1)強引用(Strong Reference):Object obj = new Object();只要強引用還存在,GC永遠不會回收掉被引用的對象。
2)軟引用(Soft Reference):描述一些還有用但非必需的對象。在系統將會發生內存溢出以前,會把這些對象列入回收範圍進行二次回收(即系統將會發生內存溢出了,纔會對他們進行回收。)
弱引用(Weak Reference):程度比軟引用還要弱一些。這些對象只能生存到下次GC以前。當GC工做時,不管內存是否足夠都會將其回收(即只要進行GC,就會對他們進行回收。)
虛引用(Phantom Reference):一個對象是否存在虛引用,徹底不會對其生存時間構成影響。
關於方法區中須要回收的是一些廢棄的常量和無用的類。
1.廢棄的常量的回收。這裏看引用計數就能夠了。沒有對象引用該常量就能夠放心的回收了。
2.無用的類的回收。什麼是無用的類呢?
A.該類全部的實例都已經被回收。也就是Java堆中不存在該類的任何實例;
B.加載該類的ClassLoader已經被回收;
C.該類對應的java.lang.Class對象沒有任何地方被引用,沒法在任何地方經過反射訪問該類的方法。
總而言之:
對於堆中的對象,主要用可達性分析判斷一個對象是否還存在引用,若是該對象沒有任何引用就應該被回收。而根據咱們實際對引用的不一樣需求,又分紅了4中引用,每種引用的回收機制也是不一樣的。
對於方法區中的常量和類,當一個常量沒有任何對象引用它,它就能夠被回收了。而對於類,若是能夠斷定它爲無用類,就能夠被回收了。
[if !supportLists]7、[endif]Java8的新特性以及使用(2017-12-02-wl)
[if !supportLists]1. [endif]經過10個示例來初步認識Java8中的lambda表達式(2017-12-02-wl)
我我的對Java 8發佈很是激動,尤爲是lambda表達式和流API。愈來愈多的瞭解它們,我能寫出更乾淨的代碼。雖然一開始並非這樣。第一次看到用lambda表達式寫出來的Java代碼時,我對這種神祕的語法感到很是失望,認爲它們把Java搞得不可讀,但我錯了。花了一天時間作了一些lambda表達式和流API示例的練習後,我開心的看到了更清晰的Java代碼。這有點像學習泛型,第一次見的時候我很討厭它。我甚至繼續使用老版Java 1.4來處理集合,直到有一天,朋友跟我介紹了使用泛型的好處(才意識到它的好處)。因此基本立場就是,不要畏懼lambda表達式以及方法引用的神祕語法,作幾回練習,從集合類中提取、過濾數據以後,你就會喜歡上它。下面讓咱們開啓學習Java 8 lambda表達式的學習之旅吧~
本小節中先不說lambda表達的含義和繁瑣的概念。咱們先從最簡單的示例來介紹java8中的lambda表達式
// Java 8以前:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();
//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
輸出:
too much code, for too little to do
Lambda expression rocks !!
這個例子向咱們展現了Java 8 lambda表達式的語法。你能夠使用lambda寫出以下代碼:
(params) -> expression
(params) -> statement
(params) -> { statements }
例如,若是你的方法不對參數進行修改、重寫,只是在控制檯打印點東西的話,那麼能夠這樣寫:
() -> System.out.println("Hello Lambda Expressions");
若是你的方法接收兩個參數,那麼能夠寫成以下這樣:
(int even, int odd) -> even + odd
順便提一句,一般都會把lambda表達式內部變量的名字起得短一些。這樣能使代碼更簡短,放在同一行。因此,在上述代碼中,變量名選用a、b或者x、y會比even、odd要好。
若是你用過Swing API編程,你就會記得怎樣寫事件監聽代碼。這又是一箇舊版本簡單匿名類的經典用例,但如今能夠不這樣了。你能夠用lambda表達式寫出更好的事件監聽代碼,以下所示:
// Java 8以前:
JButton show = new JButton("Show");
show.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Event handling without lambda expression is boring");
}
});
// Java 8方式:
show.addActionListener((e) -> {
System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});
Java開發者常用匿名類的另外一個地方是爲 Collections.sort() 定製 Comparator。在Java 8中,你能夠用更可讀的lambda表達式換掉醜陋的匿名類。我把這個留作練習,應該不難,能夠按照我在使用lambda表達式實現 Runnable 和 ActionListener 的過程當中的套路來作。
例三、使用Java 8 lambda表達式進行事件處理 使用lambda表達式對列表進行迭代
若是你使過幾年Java,你就知道針對集合類,最多見的操做就是進行迭代,並將業務邏輯應用於各個元素,例如處理訂單、交易和事件的列表。因爲Java是命令式語言,Java 8以前的全部循環代碼都是順序的,便可以對其元素進行並行化處理。若是你想作並行過濾,就須要本身寫代碼,這並非那麼容易。經過引入lambda表達式和默認方法,將作什麼和怎麼作的問題分開了,這意味着Java集合如今知道怎樣作迭代,並能夠在API層面對集合元素進行並行處理。下面的例子裏,我將介紹如何在使用lambda或不使用lambda表達式的狀況下迭代列表。你能夠看到列表如今有了一個 forEach() 方法,它能夠迭代全部對象,並將你的lambda代碼應用在其中。
// Java 8以前:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
System.out.println(feature);
}
// Java 8以後:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));
//使用Java 8的方法引用更方便,方法引用由::雙冒號操做符標示,
//看起來像C++的做用域解析運算符
features.forEach(System.out::println);
輸出:
Lambdas
Default Method
Stream API
Date and Time API
列表循環的最後一個例子展現瞭如何在Java 8中使用方法引用(method reference)。你能夠看到C++裏面的雙冒號、範圍解析操做符如今在Java 8中用來表示方法引用。
除了在語言層面支持函數式編程風格,Java 8也添加了一個包,叫作 java.util.function。它包含了不少類,用來支持Java的函數式編程。其中一個即是Predicate,使用 java.util.function.Predicate 函數式接口以及lambda表達式,能夠向API方法添加邏輯,用更少的代碼支持更多的動態行爲。下面是Java 8 Predicate 的例子,展現了過濾集合數據的多種經常使用方法。Predicate接口很是適用於作過濾。
public static void main(args[]){
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
System.out.println("Languages which starts with J :");
filter(languages, (str)->str.startsWith("J"));
System.out.println("Languages which ends with a ");
filter(languages, (str)->str.endsWith("a"));
System.out.println("Print all languages :");
filter(languages, (str)->true);
System.out.println("Print no language : ");
filter(languages, (str)->false);
System.out.println("Print language whose length greater than 4:");
filter(languages, (str)->str.length() > 4);
}
public static void filter(List names, Predicate condition) {
for(String name: names) {
if(condition.test(name)) {
System.out.println(name + " ");
}
}
}
// filter更好的辦法--filter方法改進
public static void filter(List names, Predicate condition) {
names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
System.out.println(name + " ");
});
}
能夠看到,Stream API的過濾方法也接受一個Predicate,這意味着能夠將咱們定製的 filter() 方法替換成寫在裏面的內聯代碼,這就是lambda表達式的魔力。另外,Predicate接口也容許進行多重條件的測試,下個例子將要講到。
上個例子說到,java.util.function.Predicate 容許將兩個或更多的 Predicate 合成一個。它提供相似於邏輯操做符AND和OR的方法,名字叫作and()、or()和xor(),用於將傳入 filter() 方法的條件合併起來。例如,要獲得全部以J開始,長度爲四個字母的語言,能夠定義兩個獨立的 Predicate 示例分別表示每個條件,而後用 Predicate.and() 方法將它們合併起來,以下所示:
//甚至能夠用and()、or()和xor()邏輯函數來合併Predicate,
//例如要找到全部以J開始,長度爲四個字母的名字,你能夠合併兩個Predicate並傳入
Predicate startsWithJ = (n) -> n.startsWith("J");
Predicate fourLetterLong = (n) -> n.length() == 4;
names.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));
相似地,也能夠使用or() 和 xor() 方法。本例着重介紹了以下要點:可按須要將 Predicate 做爲單獨條件而後將其合併起來使用。簡而言之,你能夠以傳統Java命令方式使用 Predicate 接口,也能夠充分利用lambda表達式達到事半功倍的效果。
例六、Java 8中使用lambda表達式的Map和Reduce示例
本例介紹最廣爲人知的函數式編程概念map。它容許你將對象進行轉換。例如在本例中,咱們將 costBeforeTax 列表的每一個元素轉換成爲稅後的值。咱們將 x -> x*x lambda表達式傳到 map() 方法,後者將其應用到流中的每個元素。而後用 forEach() 將列表元素打印出來。使用流API的收集器類,能夠獲得全部含稅的開銷。有 toList() 這樣的方法將 map 或任何其餘操做的結果合併起來。因爲收集器在流上作終端操做,所以以後便不能重用流了。你甚至能夠用流API的 reduce() 方法將全部數字合成一個,下一個例子將會講到。
//不使用lambda表達式爲每一個訂單加上12%的稅
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
System.out.println(price);
}
//使用lambda表達式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);
在上面例子中,能夠看到map將集合類(例如列表)元素進行轉換的。還有一個 reduce() 函數能夠將全部值合併成一個。Map和Reduce操做是函數式編程的核心操做,由於其功能,reduce 又被稱爲摺疊操做。另外,reduce 並非一個新的操做,你有可能已經在使用它。SQL中相似 sum()、avg() 或者 count() 的彙集函數,實際上就是 reduce 操做,由於它們接收多個值並返回一個值。流API定義的 reduceh() 函數能夠接受lambda表達式,並對全部值進行合併。IntStream這樣的類有相似 average()、count()、sum() 的內建方法來作 reduce 操做,也有mapToLong()、mapToDouble() 方法來作轉換。這並不會限制你,你能夠用內建方法,也能夠本身定義。在這個Java 8的Map Reduce示例裏,咱們首先對全部價格應用 12% 的VAT,而後用 reduce() 方法計算總和。
//爲每一個訂單加上12%的稅
//老方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double total = 0;
for (Integer cost : costBeforeTax) {
double price = cost + .12*cost;
total = total + price;
}
System.out.println("Total : " + total);
//新方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);
過濾是Java開發者在大規模集合上的一個經常使用操做,而如今使用lambda表達式和流API過濾大規模數據集合是驚人的簡單。流提供了一個 filter() 方法,接受一個 Predicate 對象,便可以傳入一個lambda表達式做爲過濾邏輯。下面的例子是用lambda表達式過濾Java集合,將幫助理解。
//建立一個字符串列表,每一個字符串長度大於2
List costBeforeTax = Arrays.asList("abc","bcd","defg","jk");
List filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());
System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);
輸出:
Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]
另外,關於filter() 方法有個常見誤解。在現實生活中,作過濾的時候,一般會丟棄部分,但使用filter()方法則是得到一個新的列表,且其每一個元素符合過濾原則。
咱們一般須要對列表的每一個元素使用某個函數,例如逐一乘以某個數、除以某個數或者作其它操做。這些操做都很適合用map() 方法,能夠將轉換邏輯以lambda表達式的形式放在 map() 方法裏,就能夠對集合的各個元素進行轉換了,以下所示。
//將字符串換成大寫並用逗號連接起來
List G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);
輸出:
USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA
本例展現瞭如何利用流的distinct() 方法來對集合進行去重
//用全部不一樣的數字建立一個正方形列表
List numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
System.out.printf("Original List : %s, Square Without duplicates : %s %n", numbers, distinct);
輸出:
Original List : [9, 10, 3, 4, 7, 3, 4], Square Without duplicates : [81, 100, 9, 16, 49]
IntStream、LongStream 和 DoubleStream 等流的類中,有個很是有用的方法叫作 summaryStatistics() 。能夠返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各類摘要數據。在本例中,咱們用這個方法來計算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法來得到列表的全部元素的總和及平均值。
//獲取數字的個數、最小值、最大值、總和以及平均值
List primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());
輸出:
Highest prime number in List : 29
Lowest prime number in List : 2
Sum of all prime numbers : 129
Average of all prime numbers : 12.9
Java 8的10個lambda表達式,這對於新手來講是個合適的任務量,你可能須要親自運行示例程序以便掌握。試着修改要求建立本身的例子,達到快速學習的目的。
補充:從例子中咱們能夠能夠看到,之前寫的匿名內部類都用了lambda表達式代替了。那麼,咱們簡單談談「lambda表達式&匿名內部類」
二者不用:
[if !supportLists]1. [endif]關鍵字this
[if !supportLists](1) [endif]匿名內部類中的this表明匿名類
[if !supportLists](2) [endif]Lambda表達式中的this表明lambda表達式的類
[if !supportLists]2. [endif]編譯方式不一樣
[if !supportLists](1) [endif]匿名內部類中會編譯成一個.class文件,文件命名方式爲:主類+$+(1,2,3.......)
[if !supportLists](2) [endif]Java編譯器將lambda表達式編譯成類的私有方法。使用了Java 7的 invokedynamic 字節碼指令來動態綁定這個方法
[if !supportLists]2. [endif]Java8中的lambda表達式要點(2017-12-02-wl)
經過面10個小示例中學習,咱們下面說下lambda表達式的6個要點
預約義使用了@Functional 註釋的函數式接口,自帶一個抽象函數的方法,或者SAM(Single Abstract Method 單個抽象方法)類型。這些稱爲lambda表達式的目標類型,能夠用做返回類型,或lambda目標代碼的參數。例如,若一個方法接收Runnable、Comparable或者 Callable 接口,都有單個抽象方法,能夠傳入lambda表達式。相似的,若是一個方法接受聲明於 java.util.function 包內的接口,例如 Predicate、Function、Consumer 或 Supplier,那麼能夠向其傳lambda表達式。
lambda表達式內能夠使用方法引用,僅當該方法不修改lambda表達式提供的參數。本例中的lambda表達式能夠換爲方法引用,由於這僅是一個參數相同的簡單方法調用。
list.forEach(n -> System.out.println(n));
list.forEach(System.out::println); //使用方法引用
然而,若對參數有任何修改,則不能使用方法引用,而需鍵入完整地lambda表達式,以下所示:
list.forEach((String s) -> System.out.println("*" + s + "*"));
事實上,能夠省略這裏的lambda參數的類型聲明,編譯器能夠從列表的類屬性推測出來。
lambda內部能夠使用靜態、非靜態和局部變量,這稱爲lambda內的變量捕獲。
Lambda表達式在Java中又稱爲閉包或匿名函數,因此若是有同事把它叫閉包的時候,不用驚訝。
Lambda方法在編譯器內部被翻譯成私有方法,並派發 invokedynamic 字節碼指令來進行調用。能夠使用JDK中的 javap 工具來反編譯class文件。使用 javap -p 或 javap -c -v 命令來看一看lambda表達式生成的字節碼。大體應該長這樣:
private static java.lang.Object lambda$0(java.lang.String);
lambda表達式有個限制,那就是隻能引用 final 或 final 局部變量,這就是說不能在lambda內部修改定義在域外的變量。
List primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });
Error:
Compile time error : "local variables referenced from a lambda expression must be final or effectively final"
另外,只是訪問它而不做修改是能夠的,以下所示:
List primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });
輸出:
4
6
10
14
所以,它看起來更像不可變閉包,相似於Python。
[if !supportLists]3. [endif]Java8中的Optional類的解析(2017-12-02-wl)
身爲一名Java程序員,你們可能都有這樣的經歷:調用一個方法獲得了返回值卻不能直接將返回值做爲參數去調用別的方法。咱們首先要判斷這個返回值是否爲null,只有在非空的前提下才能將其做爲其餘方法的參數。這正是一些相似Guava的外部API試圖解決的問題。一些JVM編程語言好比Scala、Ceylon等已經將對在覈心API中解決了這個問題。在個人前一篇文章中,介紹了Scala是如何解決了這個問題。
新版本的Java,好比Java 8引入了一個新的Optional類。Optional類的Javadoc描述以下:
這是一個能夠爲null的容器對象。若是值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
下面會逐個探討Optional類包含的方法,並經過一兩個示例展現如何使用。
做用:爲非null的值建立一個Optional。
說明:of方法經過工廠方法建立Optional類。須要注意的是,建立對象時傳入的參數不能爲null。若是傳入參數爲null,則拋出NullPointerException 。
//調用工廠方法建立Optional實例
Optional name = Optional.of("Sanaulla");
//傳入參數爲null,拋出NullPointerException.
Optional someNull = Optional.of(null);
做用:爲指定的值建立一個Optional,若是指定的值爲null,則返回一個空的Optional。
說明:ofNullable與of方法類似,惟一的區別是能夠接受參數爲null的狀況。
//下面建立了一個不包含任何值的Optional實例
//例如,值爲'null'
Optional empty = Optional.ofNullable(null);
方法3:Optional.isPresent()
做用:判斷預期值是否存在
說明:若是值存在返回true,不然返回false。
//isPresent方法用來檢查Optional實例中是否包含值
Optional name = Optional.of("Sanaulla");
if (name.isPresent()) {
//在Optional實例內調用get()返回已存在的值
System.out.println(name.get());//輸出Sanaulla
}
方法4:Optional.get()
做用:若是Optional有值則將其返回,不然拋出NoSuchElementException。
說明:上面的示例中,get方法用來獲得Optional實例中的值。下面咱們看一個拋出NoSuchElementException的例子
//執行下面的代碼會輸出:No value present
try {
Optional empty = Optional.ofNullable(null);
//在空的Optional實例上調用get(),拋出NoSuchElementException
System.out.println(empty.get());
} catch (NoSuchElementException ex) {
System.out.println(ex.getMessage());
}
方法5:Optional.ifPresent()
做用:若是Optional實例有值則爲其調用consumer,不然不作處理
說明:要理解ifPresent方法,首先須要瞭解Consumer類。簡答地說,Consumer類包含一個抽象方法。該抽象方法對傳入的值進行處理,但沒有返回值。Java8支持不用接口直接經過lambda表達式傳入參數,若是Optional實例有值,調用ifPresent()能夠接受接口段或lambda表達式
//ifPresent方法接受lambda表達式做爲參數。
//lambda表達式對Optional的值調用consumer進行處理。
Optional name = Optional.of("Sanaulla");
name.ifPresent((value) -> {
System.out.println("The length of the value is: " + value.length());
});
方法7:Optional.orElse()
做用:若是有值則將其返回,不然返回指定的其它值。
說明:若是Optional實例有值則將其返回,不然返回orElse方法傳入的參數。示例以下:
Optional name = Optional.of("Sanaulla");
Optional someNull = Optional.of(null);
//若是值不爲null,orElse方法返回Optional實例的值。
//若是爲null,返回傳入的消息。
//輸出:There is no value present!
System.out.println(empty.orElse("There is no value present!"));
//輸出:Sanaulla
System.out.println(name.orElse("There is some value!"));
方法8:Optional.orElseGet()
做用:若是有值則將其返回,不然返回指定的其它值。
說明:orElseGet與orElse方法相似,區別在於獲得的默認值。orElse方法將傳入的字符串做爲默認值,orElseGet方法能夠接受Supplier接口的實現用來生成默認值
Optional name = Optional.of("Sanaulla");
Optional someNull = Optional.of(null);
//orElseGet與orElse方法相似,區別在於orElse傳入的是默認值,
//orElseGet能夠接受一個lambda表達式生成默認值。
//輸出:Default Value
System.out.println(empty.orElseGet(() -> "Default Value"));
//輸出:Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));
方法9:Optional.orElseThrow()
做用:若是有值則將其返回,不然拋出supplier接口建立的異常。
說明:在orElseGet方法中,咱們傳入一個Supplier接口。然而,在orElseThrow中咱們能夠傳入一個lambda表達式或方法,若是值不存在來拋出異常
try {
Optional empty= Optional.of(null);
//orElseThrow與orElse方法相似。與返回默認值不一樣,
//orElseThrow會拋出lambda表達式或方法生成的異常
empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
//輸出: No value present in the Optional instance
System.out.println(ex.getMessage());
}
ValueAbsentException定義以下:
class ValueAbsentException extends Throwable {
public ValueAbsentException() {
super();
}
public ValueAbsentException(String msg) {
super(msg);
}
@Override
public String getMessage() {
return "No value present in the Optional instance";
}
}
方法10:Optional.map()
做用:若是有值,則對其執行調用mapping函數獲得返回值。若是返回值不爲null,則建立包含mapping返回值的Optional做爲map方法返回值,不然返回空Optional。
說明:map方法用來對Optional實例的值執行一系列操做。經過一組實現了Function接口的lambda表達式傳入操做。
Optional name = Optional.of("Sanaulla");
//map方法執行傳入的lambda表達式參數對Optional實例的值進行修改。
//爲lambda表達式的返回值建立新的Optional實例做爲map方法的返回值。
Optional upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));
方法11:Optional.flatMap()
做用:若是有值,爲其執行mapping函數返回Optional類型返回值,不然返回空Optional。flatMap與map(Funtion)方法相似,區別在於flatMap中的mapper返回值必須是Optional。調用結束時,flatMap不會對結果用Optional封裝。
說明:flatMap方法與map方法相似,區別在於mapping函數的返回值不一樣。map方法的mapping函數返回值能夠是任何類型T,而flatMap方法的mapping函數必須是Optional。
Optional name = Optional.of("Sanaulla");
//flatMap與map(Function)很是相似,區別在於傳入方法的lambda表達式的返回類型。
//map方法中的lambda表達式返回值能夠是任意類型,在map函數返回以前會包裝爲Optional。
//但flatMap方法中的lambda表達式返回值必須是Optionl實例。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//輸出SANAULLA
方法12:Optional.filter()
做用:若是有值而且知足斷言條件返回包含該值的Optional,不然返回空Optional。
說明:filter個方法經過傳入限定條件對Optional實例的值進行過濾。這裏能夠傳入一個lambda表達式。對於filter函數咱們應該傳入實現了Predicate接口的lambda表達式。
Optional name = Optional.of("Sanaulla");
//filter方法檢查給定的Option值是否知足某些條件。
//若是知足則返回同一個Option實例,不然返回空Optional。
Optional longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//輸出Sanaulla
//另外一個例子是Optional值不知足filter指定的條件。
Optional anotherName = Optional.of("Sana");
Optional shortName = anotherName.filter((value) -> value.length() > 6);
//輸出:name長度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));
以上,咱們介紹了Optional類的各個方法。下面經過一個完整的示例對用法集中展現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class OptionalDemo {
public static void main(String[] args) {
//建立Optional實例,也能夠經過方法返回值獲得。
Optional name = Optional.of("Sanaulla");
//建立沒有值的Optional實例,例如值爲'null'
Optional empty = Optional.ofNullable(null);
//isPresent方法用來檢查Optional實例是否有值。
if (name.isPresent()) {
//調用get()返回Optional值。
System.out.println(name.get());
}
try {
//在Optional實例上調用get()拋出NoSuchElementException。
System.out.println(empty.get());
} catch (NoSuchElementException ex) {
System.out.println(ex.getMessage());
}
//ifPresent方法接受lambda表達式參數。
//若是Optional值不爲空,lambda表達式會處理並在其上執行操做。
name.ifPresent((value) -> {
System.out.println("The length of the value is: " + value.length());
});
//若是有值orElse方法會返回Optional實例,不然返回傳入的錯誤信息。
System.out.println(empty.orElse("There is no value present!"));
System.out.println(name.orElse("There is some value!"));
//orElseGet與orElse相似,區別在於傳入的默認值。
//orElseGet接受lambda表達式生成默認值。
System.out.println(empty.orElseGet(() -> "Default Value"));
System.out.println(name.orElseGet(() -> "Default Value"));
try {
//orElseThrow與orElse方法相似,區別在於返回值。
//orElseThrow拋出由傳入的lambda表達式/方法生成異常。
empty.orElseThrow(ValueAbsentException::new);
} catch (Throwable ex) {
System.out.println(ex.getMessage());
}
//map方法經過傳入的lambda表達式修改Optonal實例默認值。
//lambda表達式返回值會包裝爲Optional實例。
Optional upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));
//flatMap與map(Funtion)很是類似,區別在於lambda表達式的返回值。
//map方法的lambda表達式返回值能夠是任何類型,可是返回值會包裝成Optional實例。
//可是flatMap方法的lambda返回值老是Optional類型。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));
//filter方法檢查Optiona值是否知足給定條件。
//若是知足返回Optional實例值,不然返回空Optional。
Optional longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));
//另外一個示例,Optional值不知足給定條件。
Optional anotherName = Optional.of("Sana");
Optional shortName = anotherName.filter((value) -> value.length() > 6);
System.out.println(shortName.orElse("The name is less than 6 characters"));
}
}
[if !supportLists]8、[endif]在開發中遇到過內存溢出麼?緣由有哪些?解決方法有哪些?(2017-11-23-gxb)
引發內存溢出的緣由有不少種,常見的有如下幾種:
1.內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
2.集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
3.代碼中存在死循環或循環產生過多重複的對象實體;
4.使用的第三方軟件中的BUG;
5.啓動參數內存值設定的太小;
內存溢出的解決方案:
第一步,修改JVM啓動參數,直接增長內存。(-Xms,-Xmx參數必定不要忘記加。)
第二步,檢查錯誤日誌,查看「OutOfMemory」錯誤前是否有其它異常或錯誤。
第三步,對代碼進行走查和分析,找出可能發生內存溢出的位置。
重點排查如下幾點:
1.檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。
2.檢查代碼中是否有死循環或遞歸調用。
3.檢查是否有大循環重複產生新對象實體。
4.檢查對數據庫查詢中,是否有一次得到所有數據的查詢。通常來講,若是一次取十萬條記錄到內存,就可能引發內存溢出。這個問題比較隱蔽,在上線前,數據庫中 數據較少,不容易出問題,上線後,數據庫中數據多了,一次查詢就有可能引發內存溢出。所以對於數據庫查詢儘可能採用分頁的方式查詢。
5.檢查List、MAP等集合對象是否有使用完後,未清除的問題。List、MAP等集合對象會始終存有對對象的引用,使得這些對象不能被GC回收。
第四步,使用內存查看工具動態查看內存使用狀況。
[if !supportLists]第四章 [endif]JavaWEB 基礎
1、JDBC技術
[if !supportLists]1. [endif]說下原生jdbc操做數據庫流程?(2017-11-25-wzz)
第一步:Class.forName()加載數據庫鏈接驅動;
第二步:DriverManager.getConnection()獲取數據鏈接對象;
第三步:根據SQL獲取sql會話對象,有2種方式 Statement、PreparedStatement ;
第四步:執行SQL處理結果集,執行SQL前若是有參數值就設置參數值setXXX();
第五步:關閉結果集、關閉會話、關閉鏈接。
詳細代碼請看(封裝):http://blog.csdn.net/qq_29542611/article/details/52426006
[if !supportLists]2. [endif]什麼要使用PreparedStatement?(2017-11-25-wzz)
一、 PreparedStatement接口繼承Statement, PreparedStatement 實例包含已編譯的 SQL 語句,因此其執行速度要快於 Statement 對象。
二、做爲 Statement 的子類,PreparedStatement 繼承了 Statement 的全部功能。三種方法 execute、 executeQuery 和 executeUpdate 已被更改以使之再也不須要參數
三、在JDBC應用中,在任什麼時候候都不要使用Statement,緣由以下:
1、代碼的可讀性和可維護性.Statement須要不斷地拼接,而PreparedStatement不會。
2、PreparedStatement盡最大可能提升性能.DB有緩存機制,相同的預編譯語句再次被調用不會再次須要編譯。
3、最重要的一點是極大地提升了安全性.Statement容易被SQL注入,而PreparedStatementc傳入的內容不會和sql語句發生任何匹配關係。
[if !supportLists]3. [endif]關係數據庫中鏈接池的機制是什麼?(2017-12-6-lyq)
前提:爲數據庫鏈接創建一個緩衝池。
1:從鏈接池獲取或建立可用鏈接
2:使用完畢以後,把鏈接返回給鏈接池
3:在系統關閉前,斷開全部鏈接並釋放鏈接佔用的系統資源
4:可以處理無效鏈接,限制鏈接池中的鏈接總數不低於或者不超過某個限定值。
其中有幾個概念須要你們理解:
最小鏈接數是鏈接池一直保持的數據鏈接。若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費掉。
最大鏈接數是鏈接池能申請的最大鏈接數。若是數據鏈接請求超過此數,後面的數據鏈接請求將被加入到等待隊列中,這會影響以後的數據庫操做。
若是最小鏈接數與最大鏈接數相差太大,那麼,最早的鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接。不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,它將被放到鏈接池中等待重複使用或是空閒超時後被釋放。
上面的解釋,能夠這樣理解:數據庫池鏈接數量一直保持一個很多於最小鏈接數的數量,當數量不夠時,數據庫會建立一些鏈接,直到一個最大鏈接數,以後鏈接數據庫就會等待。
3、Http協議
[if !supportLists]1. [endif]http的長鏈接和短鏈接(2017-11-14-lyq)
HTTP協議有HTTP/1.0版本和HTTP/1.1版本。HTTP1.1默認保持長鏈接(HTTP persistent connection,也翻譯爲持久鏈接),數據傳輸完成了保持TCP鏈接不斷開(不發RST包、不四次握手),等待在同域名下繼續用這個通道傳輸數據;相反的就是短鏈接。
在HTTP/1.0中,默認使用的是短鏈接。也就是說,瀏覽器和服務器每進行一次HTTP操做,就創建一次鏈接,任務結束就中斷鏈接。從HTTP/1.1起,默認使用的是長鏈接,用以保持鏈接特性。
[if !supportLists]2. [endif]HTTP/1.1與HTTP/1.0的區別(2017-11-21-wzy)
參考原文:http://blog.csdn.net/forgotaboutgirl/article/details/6936982
[if !supportLists]1 [endif]可擴展性
[if !supportLists]a) [endif]HTTP/1.1 在消息中增長版本號,用於兼容性判斷。
[if !supportLists]b) [endif]HTTP/1.1增長了OPTIONS方法,它容許客戶端獲取一個服務器支持的方法列表。
[if !supportLists]c) [endif]爲了與將來的協議規範兼容,HTTP/1.1在請求消息中包含了Upgrade頭域,經過該頭域,客戶端能夠讓服務器知道它可以支持的其它備用通訊協議,服務器能夠據此進行協議切換,使用備用協議與客戶端進行通訊。
[if !supportLists]2 [endif]緩存
在HTTP/1.0中,使用Expire頭域來判斷資源的fresh或stale,並使用條件請求(conditional request)來判斷資源是否仍有效。HTTP/1.1在1.0的基礎上加入了一些cache的新特性,當緩存對象的Age超過Expire時變爲stale對象,cache不須要直接拋棄stale對象,而是與源服務器進行從新激活(revalidation)。
[if !supportLists]3 [endif]帶寬優化
HTTP/1.0中,存在一些浪費帶寬的現象,例如客戶端只是須要某個對象的一部分,而服務器卻將整個對象送過來了。例如,客戶端只須要顯示一個文檔的部份內容,又好比下載大文件時須要支持斷點續傳功能,而不是在發生斷連後不得不從新下載完整的包。
HTTP/1.1中在請求消息中引入了range頭域,它容許只請求資源的某個部分。在響應消息中Content-Range頭域聲明瞭返回的這部分對象的偏移值和長度。若是服務器相應地返回了對象所請求範圍的內容,則響應碼爲206(Partial Content),它能夠防止Cache將響應誤覺得是完整的一個對象。
另一種狀況是請求消息中若是包含比較大的實體內容,但不肯定服務器是否可以接收該請求(如是否有權限),此時若貿然發出帶實體的請求,若是被拒絕也會浪費帶寬。
HTTP/1.1加入了一個新的狀態碼100(Continue)。客戶端事先發送一個只帶頭域的請求,若是服務器由於權限拒絕了請求,就回送響應碼401(Unauthorized);若是服務器接收此請求就回送響應碼100,客戶端就能夠繼續發送帶實體的完整請求了。注意,HTTP/1.0的客戶端不支持100響應碼。但能夠讓客戶端在請求消息中加入Expect頭域,並將它的值設置爲100-continue。
節省帶寬資源的一個很是有效的作法就是壓縮要傳送的數據。Content-Encoding是對消息進行端到端(end-to-end)的編碼,它多是資源在服務器上保存的固有格式(如jpeg圖片格式);在請求消息中加入Accept-Encoding頭域,它能夠告訴服務器客戶端可以解碼的編碼方式。
[if !supportLists]4 [endif]長鏈接
HTTP/1.0規定瀏覽器與服務器只保持短暫的鏈接,瀏覽器的每次請求都須要與服務器創建一個TCP鏈接,服務器完成請求處理後當即斷開TCP鏈接,服務器不跟蹤每一個客戶也不記錄過去的請求。此外,因爲大多數網頁的流量都比較小,一次TCP鏈接不多能經過slow-start區,不利於提升帶寬利用率。
HTTP 1.1支持長鏈接(PersistentConnection)和請求的流水線(Pipelining)處理,在一個TCP鏈接上能夠傳送多個HTTP請求和響應,減小了創建和關閉鏈接的消耗和延遲。例如:一個包含有許多圖像的網頁文件的多個請求和應答能夠在一個鏈接中傳輸,但每一個單獨的網頁文件的請求和應答仍然須要使用各自的鏈接。
HTTP 1.1還容許客戶端不用等待上一次請求結果返回,就能夠發出下一次請求,但服務器端必須按照接收到客戶端請求的前後順序依次回送響應結果,以保證客戶端可以區分出每次請求的響應內容,這樣也顯著地減小了整個下載過程所須要的時間。
[if !supportLists]5 [endif]消息傳遞
HTTP消息中能夠包含任意長度的實體,一般它們使用Content-Length來給出消息結束標誌。可是,對於不少動態產生的響應,只能經過緩衝完整的消息來判斷消息的大小,但這樣作會加大延遲。若是不使用長鏈接,還能夠經過鏈接關閉的信號來斷定一個消息的結束。
HTTP/1.1中引入了Chunkedtransfer-coding來解決上面這個問題,發送方將消息分割成若干個任意大小的數據塊,每一個數據塊在發送時都會附上塊的長度,最後用一個零長度的塊做爲消息結束的標誌。這種方法容許發送方只緩衝消息的一個片斷,避免緩衝整個消息帶來的過載。
在HTTP/1.0中,有一個Content-MD5的頭域,要計算這個頭域須要發送方緩衝完整個消息後才能進行。而HTTP/1.1中,採用chunked分塊傳遞的消息在最後一個塊(零長度)結束以後會再傳遞一個拖尾(trailer),它包含一個或多個頭域,這些頭域是發送方在傳遞完全部塊以後再計算出值的。發送方會在消息中包含一個Trailer頭域告訴接收方這個拖尾的存在。
[if !supportLists]6 [endif]Host頭域
在HTTP1.0中認爲每臺服務器都綁定一個惟一的IP地址,所以,請求消息中的URL並無傳遞主機名(hostname)。但隨着虛擬主機技術的發展,在一臺物理服務器上能夠存在多個虛擬主機(Multi-homed Web Servers),而且它們共享一個IP地址。
HTTP1.1的請求消息和響應消息都應支持Host頭域,且請求消息中若是沒有Host頭域會報告一個錯誤(400 Bad Request)。此外,服務器應該接受以絕對路徑標記的資源請求。
[if !supportLists]7 [endif]錯誤提示
HTTP/1.0中只定義了16個狀態響應碼,對錯誤或警告的提示不夠具體。HTTP/1.1引入了一個Warning頭域,增長對錯誤或警告信息的描述。
此外,在HTTP/1.1中新增了24個狀態響應碼,如409(Conflict)表示請求的資源與資源的當前狀態發生衝突;410(Gone)表示服務器上的某個資源被永久性的刪除。
[if !supportLists]3. [endif]http常見的狀態碼有哪些?(2017-11-23-wzz)
200 OK //客戶端請求成功
301 Moved Permanently(永久移除),請求的URL已移走。Response中應該包含一個Location URL, 說明資源如今所處的位置
302 found 重定向
400 Bad Request //客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthorized //請求未經受權,這個狀態代碼必須和WWW-Authenticate報頭域一塊兒使用
403 Forbidden //服務器收到請求,可是拒絕提供服務
404 Not Found //請求資源不存在,eg:輸入了錯誤的URL
500 Internal Server Error //服務器發生不可預期的錯誤
503 Server Unavailable //服務器當前不能處理客戶端的請求,一段時間後可能恢復正常
[if !supportLists]4. [endif]GET和POST的區別?(2017-11-23-wzz)
從表面現像上面看GET和POST的區別:
1. GET請求的數據會附在URL以後(就是把數據放置在HTTP協議頭中),以?分割URL和傳輸數據,參數之間以&相連,如:login.action?name=zhagnsan&password=123456。POST把提交的數據則放置在是HTTP包的包體中。
2. GET方式提交的數據最多隻能是1024字節,理論上POST沒有限制,可傳較大量的數據。其實這樣說是錯誤的,不許確的:
「GET方式提交的數據最多隻能是1024字節",由於GET是經過URL提交數據,那麼GET可提交的數據量就跟URL的長度有直接關係了。而實際上,URL不存在參數上限的問題,HTTP協議規範沒有對URL長度進行限制。這個限制是特定的瀏覽器及服務器對它的限制。IE對URL長度的限制是2083字節(2K+35)。對於其餘瀏覽器,如Netscape、FireFox等,理論上沒有長度限制,其限制取決於操做系統的支持。
3.POST的安全性要比GET的安全性高。注意:這裏所說的安全性和上面GET提到的「安全」不是同個概念。上面「安全」的含義僅僅是不做數據修改,而這裏安全的含義是真正的Security的含義,好比:經過GET提交數據,用戶名和密碼將明文出如今URL上,由於(1)登陸頁面有可能被瀏覽器緩存,(2)其餘人查看瀏覽器的歷史紀錄,那麼別人就能夠拿到你的帳號和密碼了,除此以外,使用GET提交數據還可能會形成Cross-site request forgery攻擊。
Get是向服務器發索取數據的一種請求,而Post是向服務器提交數據的一種請求,在FORM(表單)中,Method默認爲"GET",實質上,GET和POST只是發送機制不一樣,並非一個取一個發!
參考原文:https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html
[if !supportLists]5. [endif]http中重定向和請求轉發的區別?(2017-11-23-wzz)
本質區別:轉發是服務器行爲,重定向是客戶端行爲。
重定向特色:兩次請求,瀏覽器地址發生變化,能夠訪問本身web以外的資源,傳輸的數據會丟失。
請求轉發特色:一次強求,瀏覽器地址不變,訪問的是本身自己的web資源,傳輸的數據不會丟失。
4、Cookie和Session
[if !supportLists]1. [endif]Cookie和Session的區別(2017-11-15-lyq)
Cookie是web服務器發送給瀏覽器的一塊信息,瀏覽器會在本地一個文件中給每一個web服務器存儲cookie。之後瀏覽器再給特定的web服務器發送請求時,同時會發送全部爲該服務器存儲的cookie。
Session是存儲在web服務器端的一塊信息。session對象存儲特定用戶會話所需的屬性及配置信息。當用戶在應用程序的 Web 頁之間跳轉時,存儲在 Session 對象中的變量將不會丟失,而是在整個用戶會話中一直存在下去。
Cookie和session的不一樣點:
一、不管客戶端作怎樣的設置,session都可以正常工做。當客戶端禁用cookie時將沒法使用cookie。
二、在存儲的數據量方面:session可以存儲任意的java對象,cookie只能存儲String類型的對象。
[if !supportLists]2. [endif]session共享怎麼作的(分佈式如何實現session共享)?
參考原文:http://blog.csdn.net/sxiaobei/article/details/57086489
問題描述:一個用戶在登陸成功之後會把用戶信息存儲在session當中,這時session所在服務器爲server1,那麼用戶在session失效以前若是再次使用app,那麼可能會被路由到server2,這時問題來了,server沒有該用戶的session,因此須要用戶從新登陸,這時的用戶體驗會很是很差,因此咱們想如何實現多臺server之間共享session,讓用戶狀態得以保存。
一、服務器實現的session複製或session共享,這類型的共享session是和服務器緊密相關的,好比webSphere或JBOSS在搭建集羣時候能夠配置實現session複製或session共享,可是這種方式有一個致命的缺點,就是很差擴展和移植,好比咱們更換服務器,那麼就要修改服務器配置。
二、利用成熟的技術作session複製,好比12306使用的gemfire,好比常見的內存數據庫如redis或memorycache,這類方案雖然比較普適,可是嚴重依賴於第三方,這樣當第三方服務器出現問題的時候,那麼將是應用的災難。
三、將session維護在客戶端,很容易想到就是利用cookie,可是客戶端存在風險,數據不安全,並且能夠存放的數據量比較小,因此將session維護在客戶端還要對session中的信息加密。
咱們實現的方案能夠說是第二種方案和第三種方案的合體,能夠利用gemfire實現session複製共享,還能夠將session維護在redis中實現session共享,同時能夠將session維護在客戶端的cookie中,可是前提是數據要加密。這三種方式能夠迅速切換,而不影響應用正常執行。咱們在實踐中,首選gemfire或者redis做爲session共享的載體,一旦session不穩定出現問題的時候,能夠緊急切換cookie維護session做爲備用,不影響應用提供服務。
這裏主要講解redis和cookie方案,gemfire比較複雜你們能夠自行查看gemfire工做原理。利用redis作session共享,首先須要與業務邏輯代碼解耦,否則session共享將沒有意義,其次支持動態切換到客戶端cookie模式。redis的方案是,重寫服務器中的HttpSession和HttpServletRequest,首先實現HttpSession接口,重寫session的全部方法,將session以hash值的方式存在redis中,一個session的key就是sessionID,setAtrribute重寫以後就是更新redis中的數據,getAttribute重寫以後就是獲取redis中的數據,等等須要將HttpSession的接口一一實現。
實現了HttpSesson,那麼咱們先將該session類叫作MySession(固然實踐中不是這麼命名的),當MySession出現以後問題纔開始,怎麼能在不影響業務邏輯代碼的狀況下,還能讓本來的request.getSession()獲取到的是MySession,而不是服務器原生的session。這裏,我決定重寫服務器的HttpServletRequet,這裏先稱爲MyRequest,可是這可不是單純的重寫,我須要在原生的request基礎上重寫,因而我決定在filter中,實現request的偷樑換柱,個人思路是這樣的,MyRequest的構建器,必須以request做爲參數,因而我在filter中將服務器原生的request(也有多是框架封裝過的request),當作參數new出來一個MyRequest,而且MyRequest也實現了HttpServletRequest接口,其實就是對原生request的一個加強,這裏主要重寫了幾個request的方法,可是最重要的是重寫了request.getSession(),寫到這裏你們應該都明白爲何重寫這個方法了吧,固然是爲了獲取MySession,因而這樣就在filter中,偷偷的將原生的request換成MyRequest了,而後再將替換過的request傳入chan.doFilter(),這樣filter時候的代碼都使用的是MyRequest了,同時對業務代碼是透明的,業務代碼獲取session的方法仍然是request.getSession(),但其實獲取到的已是MySession了,這樣對session的操做已經變成了對redis的操做。這樣實現的好處有兩個,第一開發人員不須要對session共享作任何關注,session共享對用戶是透明的;第二,filter是可配置的,經過filter的方式能夠將session共享作成一項可插拔的功能,沒有任何侵入性。
這個時候已經實現了一套可插拔的session共享的框架了,可是咱們想到若是redis服務出了問題,這時咱們該怎麼辦呢,因而咱們延續redis的想法,想到能夠將session維護在客戶端內(加密的cookie),固然實現方法仍是同樣的,咱們重寫HttpSession接口,實現其全部方法,好比setAttribute就是寫入cookie,getAttribute就是讀取cookie,咱們能夠將重寫的session稱做MySession2,這時怎麼讓開發人員透明的獲取到MySession2呢,實現方法仍是在filter內偷樑換柱,在MyRequest加一個判斷,讀取sessionType配置,若是sessionType是redis的,那麼getSession的時候獲取到的是MySession,若是sessionType是coolie的,那麼getSession的時候獲取到的是MySession2,以此類推,用一樣的方法就能夠獲取到MySession 3,4,5,6等等。
這樣兩種方式都有了,那麼咱們怎實現兩種session共享方式的快速切換呢,剛剛我提到一個sessionType,這是用來決定獲取到session的類型的,只要變換sessionType就能實現兩種session共享方式的切換,可是sessionType必須對全部的服務器都是一致的,若是不一致那將會出現比較嚴重的問題,咱們目前是將sessionType維護在環境變量裏,若是要切換sessionType就要重啓每一臺服務器,完成session共享的轉換,可是當服務器太多的時候將是一種災難。並且重啓服務意味着服務的中斷,因此這樣的方式只適合服務器規模比較小,並且用戶量比較少的狀況,當服務器太多的時候,務必須要一種協調技術,可以讓服務器可以及時獲取切換的通知。基於這樣的緣由,咱們選用zookeeper做爲配置平臺,每一臺服務器都會訂閱zookeeper上的配置,當咱們切換sessionType以後,全部服務器都會訂閱到修改以後的配置,那麼切換就會當即生效,固然可能會有短暫的時間延遲,但這是能夠接受的。
[if !supportLists]3. [endif]在單點登陸中,若是cookie被禁用了怎麼辦?(2017-11-23-gxb)
單點登陸的原理是後端生成一個session ID,而後設置到 cookie,後面的全部請求瀏覽器都會帶上 cookie,而後服務端從 cookie 裏獲取 session ID,再查詢到用戶信息。因此,保持登陸的關鍵不是 cookie,而是經過 cookie 保存和傳輸的 session ID,其本質是能獲取用戶信息的數據。除了 cookie,還一般使用 HTTP 請求頭來傳輸。可是這個請求頭瀏覽器不會像 cookie 同樣自動攜帶,須要手工處理。
[if !supportLists]1. [endif]什麼是jsp,什麼是Servlet?jsp和Servlet有什麼區別?(2017-11-23-wzz)
jsp本質上就是一個Servlet,它是Servlet的一種特殊形式(由SUN公司推出),每一個jsp頁面都是一個servlet實例。
Servlet是由Java提供用於開發web服務器應用程序的一個組件,運行在服務端,由servlet容器管理,用來生成動態內容。一個servlet實例是實現了特殊接口Servlet的Java類,全部自定義的servlet均必須實現Servlet接口。
區別:
jsp是html頁面中內嵌的Java代碼,側重頁面顯示;
Servlet是html代碼和Java代碼分離,側重邏輯控制,mvc設計思想中jsp位於視圖層,servlet位於控制層
Jsp運行機制:以下圖
JVM只能識別Java類,並不能識別jsp代碼!web容器收到以.jsp爲擴展名的url請求時,會將訪問請求交給tomcat中jsp引擎處理,每一個jsp頁面第一次被訪問時,jsp引擎將jsp代碼解釋爲一個servlet源程序,接着編譯servlet源程序生成.class文件,再有web容器servlet引擎去裝載執行servlet程序,實現頁面交互。
[if !supportLists]2. [endif]jsp有哪些域對象和內置對象及他們的做用?(2017-11-25-wzz)
四大域對象:
(1)pageContext page域-指當前頁面,在當前jsp頁面有效,跳到其它頁面失效
(2)request request域-指一次請求範圍內有效,從http請求到服務器處理結束,返回響應的整個過程。在這個過程當中使用forward(請求轉發)方式跳轉多個jsp,在這些頁面裏你均可以使用這個變量
(3)session session域-指當前會話有效範圍,瀏覽器從打開到關閉過程當中,轉發、重定向都可以使用
(4)application context域-指只能在同一個web中使用,服務器未關閉或者重啓,數據就有效
九大內置對象:
生命週期做用域使用狀況
Request一次請求只在Jsp頁面內有效用於接受經過HTTP協議傳送到服務器的數據(包括頭信息、系統信息、請求方式以及請求參數等)。
Reponse一次響應只在Jsp頁面內有效表示服務器端對客戶端的迴應。主要用於設置頭信息、跳轉、Cookie等
Session從存入數據開始,默認閒置30分鐘後失效會話內有效用於存儲特定的用戶會話所需的信息
Outhttp://www.cnblogs.com/leirenyuan/p/6016063.html 用於在Web瀏覽器內輸出信息,而且管理應用服務器上的輸出緩衝區
PageContext詳細瞭解:
http://www.cnblogs.com/leirenyuan/p/6016063.html
用於存取其餘隱含對象,如request、reponse、session、application 等對象。(實際上,pageContext對象提供了對JSP頁面全部的對象及命名空間的訪問。
Pagehttp://www.cnblogs.com/leirenyuan/p/6016063.html page對象表明JSP自己(對應this),只有在JSP頁面內纔是合法的
Exceptionhttp://www.cnblogs.com/leirenyuan/p/6016063.html 顯示異常信息,必須在page 指令中設定< %@ page isErrorPage="true" %>才能使用,在通常的JSP頁面中使用該對象將沒法編譯JSP文件
Application服務器啓動發送第一個請求時就產生了Application對象,直到服務器關閉。 用於存儲和訪問來自任何頁面的變量
全部的用戶分享一個 Application 對象
Confighttp://www.cnblogs.com/leirenyuan/p/6016063.html 取得服務器的配置信息
[if !supportLists]1. [endif]什麼是xml,使用xml的優缺點,xml的解析器有哪幾種,分別有什麼區別?(2017-11-25-wzz)
xml是一種可擴展性標記語言,支持自定義標籤(使用前必須預約義)使用DTD和XML Schema標準化XML結構。
具體瞭解xml詳見:http://www.importnew.com/10839.html
優勢:用於配置文件,格式統一,符合標準;用於在互不兼容的系統間交互數據,共享數據方便;
缺點:xml文件格式複雜,數據傳輸佔流量,服務端和客戶端解析xml文件佔用大量資源且不易維護
Xml經常使用解析器有2種,分別是:DOM和SAX;
主要區別在於它們解析xml文檔的方式不一樣。使用DOM解析,xml文檔以DOM
樹形結構加載入內存,而SAX採用的是事件模型,
詳細區別:https://wenku.baidu.com/view/fc3fb5610b1c59eef8c7b410.html
[if !supportLists]第五章 [endif]JavaWEB高級
[if !supportLists]1、[endif]Filter和Listener
未完待續......
2、AJAX
[if !supportLists]1. [endif]談談你對ajax的認識?(2017-11-23-wzz)
Ajax是一種建立交互式網頁應用的的網頁開發技術;Asynchronous JavaScript and XML」的縮寫。
Ajax的優點:
經過異步模式,提高了用戶體驗。
優化了瀏覽器和服務器之間的傳輸,減小沒必要要的數據往返,減小了帶寬佔用。
Ajax引擎在客戶端運行,承擔了一部分原本由服務器承擔的工做,從而減小了大用戶量下的服務器負載。
Ajax的最大特色:
能夠實現局部刷新,在不更新整個頁面的前提下維護數據,提高用戶體驗度。
注意:
ajax在實際項目開發中使用率很是高(牢固掌握),針對ajax的詳細描述:http://www.jb51.net/article/93258.htm
[if !supportLists]2. [endif]jsonp原理(2017-11-21-gxb)
JavaScript是一種在Web開發中常用的前端動態腳本技術。在JavaScript中,有一個很重要的安全性限制,被稱爲「Same-Origin Policy」(同源策略)。這一策略對於JavaScript代碼可以訪問的頁面內容作了很重要的限制,即JavaScript只能訪問與包含它的文檔在同一域下的內容。
JavaScript這個安全策略在進行多iframe或多窗口編程、以及Ajax編程時顯得尤其重要。根據這個策略,在baidu.com下的頁面中包含的JavaScript代碼,不能訪問在google.com域名下的頁面內容;甚至不一樣的子域名之間的頁面也不能經過JavaScript代碼互相訪問。對於Ajax的影響在於,經過XMLHttpRequest實現的Ajax請求,不能向不一樣的域提交請求,例如,在abc.example.com下的頁面,不能向def.example.com提交Ajax請求,等等。
然而,當進行一些比較深刻的前端編程的時候,不可避免地須要進行跨域操做,這時候「同源策略」就顯得過於苛刻。JSONP跨域GET請求是一個經常使用的解決方案,下面咱們來看一下JSONP跨域是如何實現的,而且探討下JSONP跨域的原理。
jsonp的最基本的原理是:動態添加一個標籤,使用script標籤的src屬性沒有跨域的限制的特色實現跨域。首先在客戶端註冊一個callback, 而後把callback的名字傳給服務器。此時,服務器先生成 json 數據。 而後以 javascript 語法的方式,生成一個function , function 名字就是傳遞上來的參數 jsonp。最後將 json 數據直接以入參的方式,放置到 function 中,這樣就生成了一段 js 語法的文檔,返回給客戶端。
客戶端瀏覽器,解析script標籤,並執行返回的 javascript 文檔,此時數據做爲參數,傳入到了客戶端預先定義好的 callback 函數裏。
參考資料:http://www.nowamagic.net/librarys/veda/detail/224
3、Linux
[if !supportLists]1. [endif]說一下經常使用的Linux命令
列出文件列表:ls 【參數 -a -l】
建立目錄和移除目錄:mkdir rmdir
用於顯示文件後幾行內容:tail
打包:tar -xvf
打包並壓縮:tar -zcvf
查找字符串:grep
顯示當前所在目錄:pwd
建立空文件:touch
編輯器:vim vi
列出文件列表:ls 【參數 -a -l】
建立目錄和移除目錄:mkdir rmdir
用於顯示文件後幾行內容:tail
打包:tar -xvf
打包並壓縮:tar -zcvf
查找字符串:grep
顯示當前所在目錄:pwd
建立空文件:touch
編輯器:vim vi
[if !supportLists]2. [endif]Linux中如何查看日誌?(2017-11-21-gxb)
動態打印日誌信息:tail –f 日誌文件
參考資料:https://www.cnblogs.com/zdz8207/p/linux-log-tail-cat-tac.html
[if !supportLists]3. [endif]Linux怎麼關閉進程(2017-11-21-gxb)
一般用ps 查看進程PID ,用kill命令終止進程。
ps 命令用於查看當前正在運行的進程。
grep 是搜索
例如:ps -ef | grep java
表示查看全部進程裏CMD是java的進程信息。
ps -aux | grep java
-aux 顯示全部狀態
kill 命令用於終止進程。
例如:kill -9 [PID]
-9表示強迫進程當即中止。
[if !supportLists]4、[endif]常見的前端框架有哪些
[if !supportLists]1. [endif]EasyUI(2017-11-23-lyq)
EasyUI是一種基於jQuery的用戶界面插件集合。easyui爲建立現代化,互動,JavaScript應用程序,提供必要的功能。使用easyui你不須要寫不少代碼,你只須要經過編寫一些簡單HTML標記,就能夠定義用戶界面。優點:開源免費,頁面也還說的過去。
easyUI入門:
頁面引入必要的js和css樣式文件,文件引入順序爲:
[if !supportLists]1. [endif]
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]
[if !supportLists]4. [endif]
[if !supportLists]5. [endif]
[if !supportLists]6. [endif]
[if !supportLists]7. [endif]
[if !supportLists]8. [endif]
[if !supportLists]9. [endif]
[if !supportLists]10. [endif]
而後在頁面寫easyUI代碼就行,easyUI提供了不少樣式:
示例以下:
實現代碼以下:
[if !supportLists]1. [endif]
[if !supportLists]2. [endif]
[if !supportLists]3. [endif]
[if !supportLists]4. [endif]
[if !supportLists]5. [endif] Basic Dialog - jQuery EasyUI Demo
[if !supportLists]6. [endif]
[if !supportLists]7. [endif]
[if !supportLists]8. [endif]
[if !supportLists]9. [endif]
[if !supportLists]10. [endif]
[if !supportLists]11. [endif]
[if !supportLists]12. [endif]
[if !supportLists]13. [endif]
[if !supportLists]14. [endif]
Click below button to open or close dialog.
[if !supportLists]15. [endif]
[if !supportLists]16. [endif] Open
[if !supportLists]17. [endif] Close
[if !supportLists]18. [endif]
[if !supportLists]19. [endif]
[if !supportLists]20. [endif] The dialog content.
[if !supportLists]21. [endif]
[if !supportLists]22. [endif]
[if !supportLists]23. [endif]
[if !supportLists]24. [endif]
[if !supportLists]2. [endif]MiniUI(2017-11-23-lyq)
基於jquery 的框架,開發的界面功能都很豐富。jQuery MiniUI - 快速開發 WebUI。它能縮短開發時間,減小代碼量,使開發者更專一於業務和服務端,輕鬆實現界面開發,帶來絕佳的用戶體驗。使用 MiniUI,開發者能夠快速建立 Ajax 無刷新、 B/S 快速錄入數據、 CRUD、 Master-Detail、菜單工具欄、彈出面板、佈局導航、數據驗證、分頁表格、樹、樹形表格等典型 WEB 應用系統界面。缺點:收費,沒有源碼,基於這個開發若是想對功能作擴展就須要找他們的團隊進行升級!
提供如下幾大類控件:
表格控件
樹形控件
佈局控件:標題面板、彈出面板、摺疊分割器、佈局器、表單佈局器等
導航控件:分頁導航器、導航菜單、選項卡、菜單、工具欄等。
表單控件:多選輸入框、彈出選擇框、文本輸入框、數字輸入框、日期選擇框、下拉選擇框、下拉樹形選擇框、下拉表格選擇框、文件上傳控件、多選框、列表框、多選框組、單選框組、按鈕等
富文本編輯器
圖表控件:柱狀圖、餅圖、線形圖、雙軸圖等。
技術亮點:
快速開發:使用Html配置界面,減小80%界面代碼量。
易學易用:簡單的API設計,能夠獨立、組合使用控件。
性能優化:內置數據懶加載、低內存開銷、快速界面佈局等機制。
豐富控件:包含表格、樹、數據驗證、佈局導航等超過50個控件。
超強表格:提供鎖定列、多表頭、分頁排序、行過濾、數據彙總、單元格編輯、詳細行、Excel導出等功能。
第三方兼容:與ExtJS、jQuery、YUI、Dojo等任意第三方控件無縫集成。
瀏覽器兼容:支持IE6+、FireFox、Chrome等。
跨平臺支持:支持Java、.NET、PHP等。
示例以下:
實現代碼以下:
[if !supportLists]1. [endif]
[if !supportLists]2. [endif] showTreeIcon="true" textField="text" idField="id"
[if !supportLists]3. [endif] allowDrag="true" allowDrop="true"
[if !supportLists]4. [endif] >
[if !supportLists]5. [endif]
[if !supportLists]1. [endif]jQueryUI(2017-11-23-lyq)
jQuery UI 是一套 jQuery 的頁面 UI 插件,包含不少種經常使用的頁面空間,例如 Tabs(如本站首頁右上角部分) 、拉簾效果(本站首頁左上角)、對話框、拖放效果、日期選擇、顏色選擇、數據排序、窗體大小調整等等很是多的內容。
技術亮點:
簡單易用:繼承jQuery 簡易使用特性,提供高度抽象接口,短時間改善網站易用性。
開源免費:採用MIT & GPL 雙協議受權,輕鬆知足自由產品至企業產品各類受權需求。
普遍兼容:兼容各主流桌面瀏覽器。包括IE 6+、Firefox 2+、Safari 3+、Opera 9+、Chrome 1+。
輕便快捷:組件間相對獨立,可按需加載,避免浪費帶寬拖慢網頁打開速度。
標準先進:支持WAI-ARIA,經過標準 XHTML 代碼提供漸進加強,保證低端環境可訪問性。
美觀多變:提供近20 種預設主題,並可自定義多達 60 項可配置樣式規則,提供 24 種背景紋理選擇。
度娘上搜jQueryUI的api,其用法與easyUI、MiniUI都大同小異,此處將再也不舉例。
[if !supportLists]2. [endif]Vue.js(2017-11-23-lyq)
參考原文:https://cn.vuejs.org/v2/guide/
Vue.js (讀音 /vjuː/,相似於 view) 是一套構建用戶界面的漸進式框架。與其餘重量級框架不一樣的是,Vue 採用自底向上增量開發的設計。Vue 的核心庫只關注視圖層,它不只易於上手,還便於與第三方庫或既有項目整合。另外一方面,當與單文件組件和 Vue 生態系統支持的庫結合使用時,Vue 也徹底可以爲複雜的單頁應用程序提供驅動。
Vue.js起步:
引入相應文件:
[if !supportLists]1. [endif]
聲明式渲染:
Vue.js 的核心是一個容許採用簡潔的模板語法來聲明式的將數據渲染進 DOM 的系統:
[if !supportLists]1. [endif]
[if !supportLists]2. [endif]
[if !supportLists]3. [endif] {{ message }}
[if !supportLists]4. [endif]
[if !supportLists]5. [endif]var app = new Vue({
[if !supportLists]6. [endif] el: '#app',
[if !supportLists]7. [endif] data: {
[if !supportLists]8. [endif] message: 'Hello Vue!'
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]})
經過瀏覽器查看效果圖爲:
建立vue實例:
每一個Vue 應用都是經過 Vue 函數建立一個新的 Vue 實例開始的,當建立一個 Vue 實例時,你能夠傳入一個選項對象。能夠使用這些選項來建立你想要的行爲。
[if !supportLists]1. [endif]
[if !supportLists]2. [endif]var vm = new Vue({
[if !supportLists]3. [endif]//選項
[if !supportLists]4. [endif]})
實例生命週期:
每一個Vue 實例在被建立以前都要通過一系列的初始化過程。例如須要設置數據監聽、編譯模板、掛載實例到 DOM、在數據變化時更新 DOM 等。同時在這個過程當中也會運行一些叫作生命週期鉤子的函數,給予用戶機會在一些特定的場景下添加他們本身的代碼。好比created 鉤子能夠用來在一個實例被建立以後執行代碼:
[if !supportLists]1. [endif]
[if !supportLists]2. [endif]new Vue({
[if !supportLists]3. [endif] data: {
[if !supportLists]4. [endif] a: 1
[if !supportLists]5. [endif] },
[if !supportLists]6. [endif] created: function () {
[if !supportLists]7. [endif]// `this`指向 vm 實例
[if !supportLists]8. [endif] console.log('a is: ' + this.a)
[if !supportLists]9. [endif] }
[if !supportLists]10. [endif]})
[if !supportLists]11. [endif]// => "a is: 1"
[if !supportLists]3. [endif]AngularJS (2017-11-23-lyq)
參考原文:http://www.angularjs.net.cn/api/
AngularJS 是google 開發者設計的一個前端開發框架,它是由是由JavaScript 編寫的一個JS框架。一般它是用來在靜態網頁構建動態應用不足而設計的。
AngularJS 特色以下:
一、 數據綁定: AngularJS是數據雙向綁定。
二、 MVVM(Model-View-ViewModel)模式: Model 簡單數據對象,View 視圖(如HTML,JSP等),ViewModel是用來提供數據和方法,和View 進行交互。這種設計模式使得代碼解耦合。
三、依賴注入:AngularJS支持注入方式把須要的對象,方法等注入到指定的對象中。
四、 指令: AngularJS內部自帶各類經常使用指令,同時也支持開發者自定義指令。
五、HTML模板和擴展HTML: AngularJS能夠定義與HTML兼容的自定義模板。
AngularJS 的Api:
AngularJS提供了不少功能豐富的組件,處理核心的ng組件外,還擴展了不少經常使用的功能組件,如ngRoute(路由),ngAnimate(動畫),ngTouch(移動端操做)等,只須要引入相應的頭文件,並依賴注入你的工做模塊,則可以使用。
ng (core module):AngularJS的默認模塊,包含AngularJS的全部核心組件。
指令(directive)這是指令的核心集合,你能夠在你的模板代碼中使用它們來構建一個AngularJS應用。
一些例子包括:ngClick,ngInclude,ngRepeat,等等
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: $compile,$http,$location,等等
過濾器(filter)在組件模塊中的核心過濾器是用來轉換模板數據。
一些例子包括: filter,date,currency,lowercase,uppercase等等
全局函數(function)核心的全局函數做爲angularjs對象。這些核心功能在您的應用程序的原生的JavaScript操做有用。
一些例子包括: angular.copy(),angular.equals(),angular.element()等等
ngRoute:AgularJS的路由模塊,你能使用ngRoute結合"#"定義你的地址訪問。引入angular-route.js文件,而後在你當前的工做模塊依賴注入ngRoute模塊。
服務(service)下面這些服務用做AngularJS的路由管理。
$routeParams- 解析返回路由中帶有的參數
$route- 用於構建各個路由的url、view、controller這三者的關係
$routeProvider- 提供路由配置
指令(Directive)指令ngView提供不一樣路由模板插入的視圖層
ngAnimate:AngularJS的動畫模塊,使用ngAnimate各類核心指令能爲你的應用程序提供動畫效果。動畫可以使用css或者JavaScript回調函數。引入angular-animate.js文件,而後在你當前的工做模塊依賴注入ngAnimate模塊。
服務(service)使用$animate來在你的指令代碼中觸發動畫操做。
CSS-based animations按照nganimate的CSS命名結構參考CSS轉換/關鍵幀動畫在AngularJS。
一旦定義,動畫能夠經過引用CSS在HTML模板代碼觸發。
JS-based animations使用module.animation()來註冊一個 JavaScript動畫。
一旦註冊,動畫能夠經過引用CSS在HTML模板代碼觸發。
ngAria:幫助製做AngularJS 自定義組件的新模塊。引入angular-aria.js 文件,而後在你當前的工做模塊依賴注入ngAria模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
ngResource:AngularJS的動畫模塊,使用ngAnimate各類核心指令能爲你的應用程序提供動畫效果。動畫可以使用css或者JavaScript回調函數。引入angular-resource.js 文件,而後在你當前的工做模塊依賴注入ngResource模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
ngCookies:ngCookies模塊提供了一個方便的包用於讀取和寫入瀏覽器的cookies。
引入angular-cookies.js 文件,而後在你當前的工做模塊依賴注入ngCookies模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
ngTouch:AngularJS的動畫模塊,使用ngAnimate各類核心指令能爲你的應用程序提供動畫效果。動畫可以使用css或者JavaScript回調函數。引入angular-touch.js文件,而後在你當前的工做模塊依賴注入ngTouch模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
ngSanitize:使用ngSanitize可安全地解析和在你的應用程序中操做HTML數據。
引入angular-sanitize.js 文件,而後在你當前的工做模塊依賴注入ngSanitize模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
ngMock:AngularJS的動畫模塊,使用ngAnimate各類核心指令能爲你的應用程序提供動畫效果。動畫可以使用css或者JavaScript回調函數。引入angular-animate.js 文件,而後在你當前的工做模塊依賴注入ngMock模塊。
服務(service)這是服務的核心集合,依賴注入後可在你的應用中使用。
一些例子包括: ngClick, ngInclude, ngRepeat等等
[if !supportLists]第六章 [endif]數據庫
[if !supportLists]1、[endif]Mysql
[if !supportLists]1. [endif]SQL的select語句完整的執行順序(2017-11-15-lyq)
SQL Select語句完整的執行順序:
一、from子句組裝來自不一樣數據源的數據;
二、where子句基於指定的條件對記錄行進行篩選;
三、group by子句將數據劃分爲多個分組;
四、使用匯集函數進行計算;
五、使用having子句篩選分組;
六、計算全部的表達式;
七、select 的字段;
八、使用order by對結果集進行排序。
SQL語言不一樣於其餘編程語言的最明顯特徵是處理代碼的順序。在大多數據庫語言中,代碼按編碼順序被處理。但在SQL語句中,第一個被處理的子句式FROM,而不是第一齣現的SELECT。SQL查詢處理的步驟序號:
(1) FROM
(2) JOIN
(3) ON
(4) WHERE
(5) GROUP BY
(6) WITH {CUBE | ROLLUP}
(7) HAVING
(8) SELECT
(9) DISTINCT
(9) ORDER BY
(10)
以上每一個步驟都會產生一個虛擬表,該虛擬表被用做下一個步驟的輸入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最後一步生成的表纔會會給調用者。若是沒有在查詢中指定某一個子句,將跳過相應的步驟。
邏輯查詢處理階段簡介:
[if !supportLists]一、 [endif]FROM:對FROM子句中的前兩個表執行笛卡爾積(交叉聯接),生成虛擬表VT1。
[if !supportLists]二、 [endif]ON:對VT1應用ON篩選器,只有那些使爲真才被插入到TV2。
[if !supportLists]三、 [endif]OUTER (JOIN):若是指定了OUTER JOIN(相對於CROSS JOIN或INNER JOIN),保留表中未找到匹配的行將做爲外部行添加到VT2,生成TV3。若是FROM子句包含兩個以上的表,則對上一個聯接生成的結果表和下一個表重複執行步驟1到步驟3,直處處理完全部的表位置。
[if !supportLists]四、 [endif]WHERE:對TV3應用WHERE篩選器,只有使爲true的行才插入TV4。
[if !supportLists]五、 [endif]GROUP BY:按GROUP BY子句中的列列表對TV4中的行進行分組,生成TV5。
[if !supportLists]六、 [endif]CUTE|ROLLUP:把超組插入VT5,生成VT6。
[if !supportLists]七、 [endif]HAVING:對VT6應用HAVING篩選器,只有使爲true的組插入到VT7。
[if !supportLists]八、 [endif]SELECT:處理SELECT列表,產生VT8。
[if !supportLists]九、 [endif]DISTINCT:將重複的行從VT8中刪除,產品VT9。
[if !supportLists]十、 [endif]ORDER BY:將VT9中的行按ORDER BY子句中的列列表順序,生成一個遊標(VC10)。
[if !supportLists]十一、 [endif]TOP:從VC10的開始處選擇指定數量或比例的行,生成表TV11,並返回給調用者。
where子句中的條件書寫順序
[if !supportLists]2. [endif]SQL之聚合函數(2017-11-15-lyq)
聚合函數是對一組值進行計算並返回單一的值的函數,它常常與select語句中的group by子句一同使用。
[if !supportLists]a. [endif]avg():返回的是指定組中的平均值,空值被忽略。
[if !supportLists]b. [endif]count():返回的是指定組中的項目個數。
[if !supportLists]c. [endif]max():返回指定數據中的最大值。
[if !supportLists]d. [endif]min():返回指定數據中的最小值。
[if !supportLists]e. [endif]sum():返回指定數據的和,只能用於數字列,空值忽略。
[if !supportLists]f. [endif]group by():對數據進行分組,對執行完group by以後的組進行聚合函數的運算,計算每一組的值。最後用having去掉不符合條件的組,having子句中的每個元素必須出如今select列表中(只針對於mysql)。
[if !supportLists]3. [endif]SQL之鏈接查詢(左鏈接和右鏈接的區別)(2017-11-15-lyq)
外鏈接:
左鏈接(左外鏈接):以左表做爲基準進行查詢,左表數據會所有顯示出來,右表若是和左表匹配的數據則顯示相應字段的數據,若是不匹配則顯示爲null。
右鏈接(右外鏈接):以右表做爲基準進行查詢,右表數據會所有顯示出來,左表若是和右表匹配的數據則顯示相應字段的數據,若是不匹配則顯示爲null。
全鏈接:先以左表進行左外鏈接,再以右表進行右外鏈接。
內鏈接:
顯示錶之間有鏈接匹配的全部行。
[if !supportLists]4. [endif]SQL之sql注入(2017-11-15-lyq)
經過在Web表單中輸入(惡意)SQL語句獲得一個存在安全漏洞的網站上的數據庫,而不是按照設計者意圖去執行SQL語句。舉例:當執行的sql爲 select * from user where username =「admin」 or 「a」=「a」時,sql語句恆成立,參數admin毫無心義。
防止sql注入的方式:
[if !supportLists]1. [endif]預編譯語句:如,select * from user where username = ?,sql語句語義不會發生改變,sql語句中變量用?表示,即便傳遞參數時爲「admin or‘a’= ‘a’」,也會把這總體當作一個字符創去查詢。
[if !supportLists]2. [endif]Mybatis框架中的mapper方式中的 # 也能很大程度的防止sql注入($沒法防止sql注入)。
[if !supportLists]5. [endif]Mysql性能優化(2017-11-15-lyq)
[if !supportLists]一、[endif]當只要一行數據時使用limit 1
查詢時若是已知會獲得一條數據,這種狀況下加上limit 1 會增長性能。由於mysql數據庫引擎會在找到一條結果中止搜索,而不是繼續查詢下一條是否符合標準直到全部記錄查詢完畢。
[if !supportLists]二、[endif]選擇正確的數據庫引擎
Mysql中有兩個引擎MyISAM和InnoDB,每一個引擎有利有弊。
MyISAM適用於一些大量查詢的應用,但對於有大量寫功能的應用不是很好。甚至你只須要update一個字段整個表都會被鎖起來。而別的進程就算是讀操做也不行要等到當前update操做完成以後才能繼續進行。另外,MyISAM對於select count(*)這類操做是超級快的。
InnoDB的趨勢會是一個很是複雜的存儲引擎,對於一些小的應用會比MyISAM還慢,可是支持「行鎖」,因此在寫操做比較多的時候會比較優秀。而且,它支持不少的高級應用,例如:事物。
[if !supportLists]3. [endif]用not exists代替not in
Not exists用到了鏈接可以發揮已經創建好的索引的做用,not in不能使用索引。Not in是最慢的方式要同每條記錄比較,在數據量比較大的操做紅不建議使用這種方式。
[if !supportLists]4. [endif]對操做符的優化,儘可能不採用不利於索引的操做符
如:in not in is null is not null <> 等
某個字段總要拿來搜索,爲其創建索引:
Mysql中能夠利用alter table語句來爲表中的字段添加索引,語法爲:alter table 代表 add index (字段名);
[if !supportLists]6. [endif]必看sql面試題(學生表_課程表_成績表_教師表)(2017-11-25-wzz)
給你們推薦一篇很是好的博客,該博客中收集了最多見的Mysql常見面試題和筆試題。
博客連接:http://www.cnblogs.com/qixuejia/p/3637735.html
[if !supportLists]7. [endif]Mysql數據庫架構圖(2017-11-25-wzz)
MyISAM和InnoDB是最多見的兩種存儲引擎,特色以下。
MyISAM存儲引擎
MyISAM是MySQL官方提供默認的存儲引擎,其特色是不支持事務、表鎖和全文索引,對於一些OLAP(聯機分析處理)系統,操做速度快。
每一個MyISAM在磁盤上存儲成三個文件。文件名都和表名相同,擴展名分別是.frm(存儲表定義)、.MYD (MYData,存儲數據)、.MYI (MYIndex,存儲索引)。這裏特別要注意的是MyISAM不緩存數據文件,只緩存索引文件。
InnoDB存儲引擎
InnoDB存儲引擎支持事務,主要面向OLTP(聯機事務處理過程)方面的應用,其特色是行鎖設置、支持外鍵,並支持相似於Oracle的非鎖定讀,即默認狀況下讀不產生鎖。InnoDB將數據放在一個邏輯表空間中(相似Oracle)。InnoDB經過多版本併發控制來得到高併發性,實現了ANSI標準的4種隔離級別,默認爲Repeatable,使用一種被稱爲next-key locking的策略避免幻讀。
對於表中數據的存儲,InnoDB採用相似Oracle索引組織表Clustered的方式進行存儲。
InnoDB 存儲引擎提供了具備提交、回滾和崩潰恢復能力的事務安全。可是對比Myisam的存儲引擎,InnoDB 寫的處理效率差一些而且會佔用更多的磁盤空間以保留數據和索引。
InnoDB體系架構
[if !supportLists]8. [endif]Mysql架構器中各個模塊都是什麼?(2017-11-25-wzz)
(1)、鏈接管理與安全驗證是什麼?
每一個客戶端都會創建一個與服務器鏈接的線程,服務器會有一個線程池來管理這些 鏈接;若是客戶端須要鏈接到MYSQL數據庫還須要進行驗證,包括用戶名、密碼、 主機信息等。
(2)、解析器是什麼?
解析器的做用主要是分析查詢語句,最終生成解析樹;首先解析器會對查詢語句的語法進行分析,分析語法是否有問題。還有解析器會查詢緩存,若是在緩存中有對應的語句,就返回查詢結果不進行接下來的優化執行操做。前提是緩存中的數據沒有被修改,固然若是被修改了也會被清出緩存。
(3)、優化器怎麼用?
優化器的做用主要是對查詢語句進行優化操做,包括選擇合適的索引,數據的讀取方式,包括獲取查詢的開銷信息,統計信息等,這也是爲何圖中會有優化器指向存儲引擎的箭頭。以前在別的文章沒有看到優化器跟存儲引擎之間的關係,在這裏我我的的理解是由於優化器須要經過存儲引擎獲取查詢的大體數據和統計信息。
(4)、執行器是什麼?
執行器包括執行查詢語句,返回查詢結果,生成執行計劃包括與存儲引擎的一些處理操做。
[if !supportLists]9. [endif]Mysql存儲引擎有哪些?(2017-11-25-wzz)
(1)、InnoDB存儲引擎
InnoDB是事務型數據庫的首選引擎,支持事務安全表(ACID),支持行鎖定和外鍵,InnoDB是默認的MySQL引擎。
(2)、MyISAM存儲引擎
MyISAM基於ISAM存儲引擎,並對其進行擴展。它是在Web、數據倉儲和其餘應用環境下最常使用的存儲引擎之一。MyISAM擁有較高的插入、查詢速度,但不支持事物。
(3)、MEMORY存儲引擎
MEMORY存儲引擎將表中的數據存儲到內存中,未查詢和引用其餘表數據提供快速訪問。
(4)、NDB存儲引擎
DB存儲引擎是一個集羣存儲引擎,相似於Oracle的RAC,但它是Share Nothing的架構,所以能提供更高級別的高可用性和可擴展性。NDB的特色是數據所有放在內存中,所以經過主鍵查找很是快。
關於NDB,有一個問題須要注意,它的鏈接(join)操做是在MySQL數據庫層完成,不是在存儲引擎層完成,這意味着,複雜的join操做須要巨大的網絡開銷,查詢速度會很慢。
[if !supportLists](5)[endif]、Memory (Heap) 存儲引擎
Memory存儲引擎(以前稱爲Heap)將表中數據存放在內存中,若是數據庫重啓或崩潰,數據丟失,所以它很是適合存儲臨時數據。
[if !supportLists](6)[endif]、Archive存儲引擎
正如其名稱所示,Archive很是適合存儲歸檔數據,如日誌信息。它只支持INSERT和SELECT操做,其設計的主要目的是提供高速的插入和壓縮功能。
[if !supportLists](7)[endif]、Federated存儲引擎
Federated存儲引擎不存放數據,它至少指向一臺遠程MySQL數據庫服務器上的表,很是相似於Oracle的透明網關。
(8)、Maria存儲引擎
Maria存儲引擎是新開發的引擎,其設計目標是用來取代原有的MyISAM存儲引擎,從而成爲MySQL默認的存儲引擎。
上述引擎中,InnoDB是事務安全的存儲引擎,設計上借鑑了不少Oracle的架構思想,通常而言,在OLTP應用中,InnoDB應該做爲核心應用表的首先存儲引擎。InnoDB是由第三方的Innobase Oy公司開發,現已被Oracle收購,創始人是Heikki Tuuri,芬蘭赫爾辛基人,和著名的Linux創始人Linus是校友。
[if !supportLists]10. [endif]MySQL事務介紹(2017-11-25-wzz)
MySQL和其它的數據庫產品有一個很大的不一樣就是事務由存儲引擎所決定,例如MYISAM,MEMORY,ARCHIVE都不支持事務,事務就是爲了解決一組查詢要麼所有執行成功,要麼所有執行失敗。
MySQL事務默認是採起自動提交的模式,除非顯示開始一個事務。
SHOW VARIABLES LIKE 'AUTOCOMMIT';
修改自動提交模式,0=OFF,1=ON
注意:修改自動提交對非事務類型的表是無效的,由於它們自己就沒有提交和回滾的概念,還有一些命令是會強制自動提交的,好比DLL命令、lock tables等。
SET AUTOCOMMIT=OFF或 SET AUTOCOMMIT=0
數據庫事務transanction正確執行的四個基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔離性(Isolation)、持久性(Durability)。
(1)原子性:整個事務中的全部操做,要麼所有完成,要麼所有不完成,不可能停滯在中間某個環節。事務在執行過程當中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務歷來沒有執行過同樣。
(2)一致性:在事務開始以前和事務結束之後,數據庫的完整性約束沒有被破壞。
(3)隔離性:隔離狀態執行事務,使它們好像是系統在給定時間內執行的惟一操做。若是有兩個事務,運行在相同的時間內,執行 相同的功能,事務的隔離性將確保每一事務在系統中認爲只有該事務在使用系統。這種屬性有時稱爲串行化,爲了防止事務操做間的混淆, 必須串行化或序列化請求,使得在同一時間僅有一個請求用於同一數據。
(4)持久性:在事務完成之後,該事務所對數據庫所做的更改便持久的保存在數據庫之中,並不會被回滾。
隔離級別髒讀不可重複讀幻讀
Read uncommitted(讀未提交)是是是
Read committed(讀已提交)否是是
Repeatable read(可重複讀)否否是
Serializable(串行讀)否否否
讀未提交(READ UNCOMMITTED):未提交讀隔離級別也叫讀髒,就是事務能夠讀取其它事務未提交的數據。
讀已提交(READ COMMITTED):在其它數據庫系統好比SQL Server默認的隔離級別就是提交讀,已提交讀隔離級別就是在事務未提交以前所作的修改其它事務是不可見的。
可重複讀(REPEATABLE READ):保證同一個事務中的屢次相同的查詢的結果是一致的,好比一個事務一開始查詢了一條記錄而後過了幾秒鐘又執行了相同的查詢,保證兩次查詢的結果是相同的,可重複讀也是mysql的默認隔離級別。
可串行化(SERIALIZABLE):可串行化就是保證讀取的範圍內沒有新的數據插入,好比事務第一次查詢獲得某個範圍的數據,第二次查詢也一樣獲得了相同範圍的數據,中間沒有新的數據插入到該範圍中。
[if !supportLists]11. [endif]MySQL怎麼建立存儲過程(2017-11-25-wzz)
MySQL存儲過程是從MySQL5.0開始增長的新功能。存儲過程的優勢有一籮筐。不過最主要的仍是執行效率和SQL 代碼封裝。特別是 SQL 代碼封裝功能,若是沒有存儲過程,在外部程序訪問數據庫時,要組織不少 SQL 語句。特別是業務邏輯複雜的時候,一大堆的 SQL 和條件夾雜在代碼中,讓人毛骨悚然。如今有了 MySQL 存儲過程,業務邏輯能夠封裝存儲過程當中,這樣不只容易維護,並且執行效率也高。
1、建立MySQL存儲過程
下面代碼建立了一個叫pr_add 的MySQL 存儲過程,這個MySQL 存儲過程有兩個int 類型的輸入參數 「a」、「b」,返回這兩個參數的和。
1)drop procedure if exists pr_add; (備註:若是存在pr_add的存儲過程,則先刪掉)
2)計算兩個數之和(備註:實現計算兩個整數之和的功能)
create procedure pr_add ( a int, b int ) begin declare c int;
if a is null then set a = 0;
end if;
if b is null then set b = 0;
end if;
set c = a + b;
select c as sum;
[if !supportLists]2、[endif]調用MySQL 存儲過程
call pr_add(10, 20);
[if !supportLists]12. [endif]MySQL觸發器怎麼寫?(2017-11-25-wzz)
MySQL包含對觸發器的支持。觸發器是一種與表操做有關的數據庫對象,當觸發器所在表上出現指定事件時,將調用該對象,即表的操做事件觸發表上的觸發器的執行。
在MySQL中,建立觸發器語法以下:
CREATE TRIGGER trigger_name
trigger_time
trigger_event ON tbl_name
FOR EACH ROW
trigger_stmt
其中:
trigger_name:標識觸發器名稱,用戶自行指定;
trigger_time:標識觸發時機,取值爲 BEFORE 或 AFTER;
trigger_event:標識觸發事件,取值爲 INSERT、UPDATE 或 DELETE;
tbl_name:標識創建觸發器的表名,即在哪張表上創建觸發器;
trigger_stmt:觸發器程序體,能夠是一句SQL語句,或者用 BEGIN 和 END 包含的多條語句。
因而可知,能夠創建6種觸發器,即:BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE、AFTER DELETE。
另外有一個限制是不能同時在一個表上創建2個相同類型的觸發器,所以在一個表上最多創建6個觸發器。
假設系統中有兩個表:
1)班級表 class(班級號 classID, 班內學生數 stuCount)
2)學生表 student(學號 stuID, 所屬班級號 classID)
要建立觸發器來使班級表中的班內學生數隨着學生的添加自動更新,代碼以下:
create trigger tri_stuInsert after insert
on student for each row
begin
declare c int;
set c = (select stuCount from class where classID=new.classID);
update class set stuCount = c + 1 where classID = new.classID;
查看觸發器:
和查看數據庫(show databases;)查看錶格(show tables;)同樣,查看觸發器的語法以下:
SHOW TRIGGERS [FROM schema_name];
其中,schema_name 即 Schema 的名稱,在 MySQL 中 Schema 和 Database 是同樣的,也就是說,能夠指定數據庫名,這樣就沒必要先「USE database_name;」了。
刪除觸發器:
和刪除數據庫、刪除表格同樣,刪除觸發器的語法以下:
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name
[if !supportLists]13. [endif]MySQL語句優化(2017-11-26-wzz)
能夠,好比select id from t where num is null 這樣的sql也是能夠的。可是最好不要給數據庫留NULL,儘量的使用 NOT NULL填充數據庫。不要覺得 NULL 不須要空間,好比:char(100) 型,在字段創建時,空間就固定了,無論是否插入值(NULL也包含在內),都是佔用100個字符的空間的,若是是varchar 這樣的變長字段,null不佔用空間。能夠在num上設置默認值0,確保表中num列沒有null值,而後這樣查詢:select id from t where num = 0。
13.2 select * from admin left join log on admin.admin_id = log.admin_id where log.admin_id>10如何優化?
優化爲:select * from (select * from admin where admin_id>10) T1 lef join log on T1.admin_id = log.admin_id。
使用JOIN 時候,應該用小的結果驅動大的結果(left join 左邊表結果儘可能小若是有條件應該放到左邊先處理,right join 同理反向),同時儘可能把牽涉到多表聯合的查詢拆分多個query(多個連表查詢效率低,容易到以後鎖表和阻塞)。
例如:select * from admin order by admin_id limit 100000,10
優化爲:select * from admin where admin_id between 100000 and 100010 order by admin_id。
例如:select * from admin where year(admin_time)>2014
優化爲:select * from admin where admin_time> '2014-01-01′
[if !supportLists]14. [endif]MySQL中文亂碼問題完美解決方案(2017-12-07-lwl)
解決亂碼的核心思想是統一編碼。咱們在使用MySQL建數據庫和建表時應儘可能使用統一的編碼,強烈推薦的是utf8編碼,由於該編碼幾乎能夠兼容世界上全部的字符。
數據庫在安裝的時候能夠設置默認編碼,在安裝時就必定要設置爲utf8編碼。設置以後再建立的數據庫和表若是不指定編碼,默認都會使用utf8編碼,省去了不少麻煩。
數據庫軟件安裝好以後能夠經過以下命令查看默認編碼:
[if !supportLists]一、[endif]查詢數據庫軟件使用的默認編碼格式
show variables like「%colla%」;
show varables like「%char%」
其中collation,表明了字符串排序(比較)的規則,若是值是utf8_general_ci,表明使用utf8字符集大小寫不敏感的天然方式比較。
若是character_set的值不爲utf8,那麼能夠使用以下命令修改成utf8。
[if !supportLists]二、[endif]修改數據庫默認編碼爲utf8
SET character_set_client='utf8';
SET character_set_connection='utf8';
SET character_set_results='utf8';
若是不想設置數據庫軟件的全局默認編碼,也能夠單獨修改或者設置某個具體數據庫的編碼也能夠單獨修改或設置某個數據庫中某個表的編碼。
[if !supportLists]三、[endif]建立數據庫的時候指定使用utf8編碼
CREATE DATABASE `test`
CHARACTER SET 'utf8'
COLLATE 'utf8_general_ci';
[if !supportLists]四、[endif]建立表的時候指定使用utf8編碼
CREATE TABLE `database_user` (
`ID` varchar(40) NOT NULL default '',
`UserID` varchar(40) NOT NULL default '',
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
若是數據庫已經建立好了,能夠使用show database 數據庫名;和 show create table 表名;查看一下數據庫和表的字符集是否爲utf8,若是不是則在命令行下面能夠用以下命令,將數據庫和表編碼修改成utf8.
[if !supportLists]五、[endif]修改具體某數據庫或表的編碼
ALTER DATABASE `db_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `tb_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
[if !supportLists]15. [endif]如何提升MySQL的安全性(2017-12-8-lwl)
1.若是MySQL客戶端和服務器端的鏈接須要跨越並經過不可信任的網絡,那麼須要使用ssh隧道來加密該鏈接的通訊。
2.使用set password語句來修改用戶的密碼,先「mysql -u root」登錄數據庫系統,而後「mysql> update mysql.user set password=password(’newpwd’)」,最後執行「flush privileges」。
3.MySQL須要提防的攻擊有,防偷聽、篡改、回放、拒絕服務等,不涉及可用性和容錯方面。對全部的鏈接、查詢、其餘操做使用基於ACL(ACL(訪問控制列表)是一種路由器配置和控制網絡訪問的一種有力的工具,它可控制路由器應該容許或拒絕數據包經過,可監控流量,可自上向下檢查網絡的安全性,可檢查和過濾數據和限制沒必要要的路由更新,所以讓網絡資源節約成本的ACL配置技術在生活中愈來愈普遍應用。)即訪問控制列表的安全措施來完成。
4.設置除了root用戶外的其餘任何用戶不容許訪問mysql主數據庫中的user表;
5.使用grant和revoke語句來進行用戶訪問控制的工做;
6.不要使用明文密碼,而是使用md5()和sha1()等單向的哈系函數來設置密碼;
7.不要選用字典中的字來作密碼;
8.採用防火牆能夠去掉50%的外部危險,讓數據庫系統躲在防火牆後面工做,或放置在DMZ(DMZ是英文「demilitarized zone」的縮寫,隔離區,它是爲了解決安裝防火牆後外部網絡的訪問用戶不能訪問內部網絡服務器的問題,而設立的一個非安全系統與安全系統之間的緩衝區。)區域中;
9.從因特網上用nmap來掃描3306端口,也可用telnet server_host 3306的方法測試,不容許從非信任網絡中訪問數據庫服務器的3306號tcp端口,須要在防火牆或路由器上作設定;
10.服務端要對SQL進行預編譯,避免SQL注入攻擊,例如where id=234,別人卻輸入where id=234 or 1=1。
11.在傳遞數據給mysql時檢查一下大小;
12.應用程序鏈接到數據庫時應該使用通常的用戶賬號,開放少數必要的權限給該用戶;
13.學會使用tcpdump和strings工具來查看傳輸數據的安全性,例如tcpdump -l -i eth0 -w -src or dst port 3306 strings。以普通用戶來啓動mysql數據庫服務;
14.確信在mysql目錄中只有啓動數據庫服務的用戶才能夠對文件有讀和寫的權限;
15.不準將process或super權限付給非管理用戶,該mysqladmin processlist能夠列舉出當前執行的查詢文本;super權限可用於切斷客戶端鏈接、改變服務器運行參數狀態、控制拷貝複製數據庫的服務器;
16.若是不相信dns服務公司的服務,能夠在主機名稱容許表中只設置ip數字地址;
17.使用max_user_connections變量來使mysqld服務進程,對一個指定賬戶限定鏈接數;
18.grant語句也支持資源控制選項;
19.啓動mysqld服務進程的安全選項開關,–local-infile=0或1,如果0則客戶端程序就沒法使用local load data了,賦權的一個例子grant insert(user) on mysql.user to ‘user_name’@'host_name’;若使用–skip-grant-tables系統將對任何用戶的訪問不作任何訪問控制,但能夠用 mysqladmin flush-privileges或mysqladmin reload來開啓訪問控制;默認狀況是show databases語句對全部用戶開放,能夠用–skip-show-databases來關閉掉。
23.碰到error 1045(28000) access denied for user ‘root’@'localhost’ (using password:no)錯誤時,你須要從新設置密碼,具體方法是:先用–skip-grant-tables參數啓動mysqld,而後執行 mysql -u root mysql,mysql>update user set password=password(’newpassword’) where user=’root’;mysql>flush privileges;,最後從新啓動mysql就能夠了。
[if !supportLists]2、[endif]Oracle
[if !supportLists]1. [endif]什麼是存儲過程,使用存儲過程的好處?(2017-11-25-wzz)
存儲過程(Stored Procedure )是一組爲了完成特定功能的SQL 語句集,經編譯後存儲在數據庫中。用戶經過指定存儲過程的名字並給出參數(若是該存儲過程帶有參數)來執行它。存儲過程是數據庫中的一個重要對象,任何一個設計良好的數據庫應用程序都應該用到存儲過程。
優勢:
(1)容許模塊化程序設計,就是說只須要建立一次過程,之後在程序中就能夠調用該過程任意次。
(2)容許更快執行,若是某操做須要執行大量 SQL 語句或重複執行,存儲過程比 SQL 語句執行的要快。
(3)減小網絡流量,例如一個須要數百行的 SQL 代碼的操做有一條執行語句完成,不須要在網絡中發
送數百行代碼。
(4) 更好的安全機制,對於沒有權限執行存儲過程的用戶,也可受權他們執行存儲過程。
存儲過程的具體使用詳見:http://www.cnblogs.com/yank/p/4235609.html
[if !supportLists]2. [endif]Oracle存儲過程怎麼建立?(2017-11-25-wzz)
存儲過程建立語法:
create or replace procedure存儲過程名(param1 in type,param2 out type)
as
變量1類型(值範圍);
變量2類型(值範圍);
Begin
Select count(*) into變量1 from 表A where列名=param1;
If (判斷條件) then
Select列名 into 變量2 from 表A where列名=param1;
Dbms_output。Put_line(‘打印信息’);
Elsif (判斷條件) then
Dbms_output。Put_line(‘打印信息’);
Else
Raise異常名(NO_DATA_FOUND);
End if;
Exception
When others then
Rollback;
End;
注意事項:
1. 存儲過程參數不帶取值範圍,in表示傳入,out表示輸出
2. 變量帶取值範圍,後面接分號
3. 在判斷語句前最好先用count(*)函數判斷是否存在該條操做記錄
4. 用select 。。。into。。。給變量賦值
5. 在代碼中拋異經常使用 raise+異常名
[if !supportLists]3. [endif]如何使用Oracle的遊標?(2017-11-25-wzz)
參考博客:https://www.cnblogs.com/sc-xx/archive/2011/12/03/2275084.html
(1)、Oracle中的遊標分爲顯示遊標和隱式遊標
(2)、顯示遊標是用cursor...is命令定義的遊標,它能夠對查詢語句(select)返回的多條記錄進行處理;
(3)、隱式遊標是在執行插入 (insert)、刪除(delete)、修改(update) 和返回單條記錄的查詢(select)語句時由PL/SQL自動定義的。
(4)、顯式遊標的操做:打開遊標、操做遊標、關閉遊標;PL/SQL隱式地打開SQL遊標,並在它內部處理SQL語句,而後關閉它。
[if !supportLists]4. [endif]Oracle中字符串用什麼鏈接?(2017-11-25-wzz)
Oracle中使用 || 這個符號鏈接字符串 如 ‘abc’ || ‘d’ 的結果是abcd。
[if !supportLists]5. [endif]Oracle中是如何進行分頁查詢的?(2017-11-25-wzz)
Oracle中使用rownum來進行分頁, 這個是效率最好的分頁方法,hibernate也是使用rownum來進行Oralce分頁的
select * from
( select rownum r,a from tabName where rownum <= 20 )
where r > 10
[if !supportLists]6. [endif]存儲過程和存儲函數的特色和區別?(2017-11-25-wzz)
特色:
[if !supportLists](1)[endif]、通常來講,存儲過程實現的功能要複雜一點,而函數的實現的功能針對性比較強。
[if !supportLists](2)[endif]、對於存儲過程來講能夠返回參數,而函數只能返回值或者表對象。
[if !supportLists](3)[endif]、存儲過程通常是做爲一個獨立的部分來執行,而函數能夠做爲查詢語句的一個部分來調用,因爲函數能夠返回一個表對象,所以它能夠在查詢語句中位於FROM關鍵字的後面。
區別:
(1)、函數必須有返回值,而過程沒有.
(2)、函數能夠單獨執行.而過程必須經過execute執行.
(3)、函數能夠嵌入到SQL語句中執行.而過程不行.
其實咱們能夠將比較複雜的查詢寫成函數.而後到存儲過程當中去調用這些函數.
[if !supportLists]7. [endif]存儲過程與SQL的對比?(2017-11-21-gxb)
優點:
一、提升性能 SQL語句在建立過程時進行分析和編譯。 存儲過程是預編譯的,在首次運行一個存儲過程時,查詢優化器對其進行分析、優化,並給出最終被存在系統表中的存儲計劃,這樣,在執行過程時即可節省此開銷。二、下降網絡開銷 存儲過程調用時只需用提供存儲過程名和必要的參數信息,從而可下降網絡的流量。三、便於進行代碼移植 數據庫專業人員能夠隨時對存儲過程進行修改,但對應用程序源代碼卻毫無影響,從而極大的提升了程序的可移植性。四、更強的安全性 1)系統管理員能夠對執行的某一個存儲過程進行權限限制,避免非受權用戶對數據的訪問 2)在經過網絡調用過程時,只有對執行過程的調用是可見的。 所以,惡意用戶沒法看到表和數據庫對象名稱、嵌入本身的 Transact-SQL 語句或搜索關鍵數據。 3)使用過程參數有助於避免 SQL 注入攻擊。 由於參數輸入被視做文字值而非可執行代碼,因此,攻擊者將命令插入過程內的 Transact-SQL 語句並損害安全性將更爲困難。 4)能夠對過程進行加密,這有助於對源代碼進行模糊處理。
劣勢:
一、存儲過程須要專門的數據庫開發人員進行維護,但實際狀況是,每每由程序開發員人員兼職
二、設計邏輯變動,修改存儲過程沒有SQL靈活