工作記錄筆記
面試題:
http://www.importnew.com/22056.html
http://marlonyao.iteye.com/blog/344876
http://cache.baiducontent.com/c?m=9f65cb4a8c8507ed4fece763104687270e54f72864879b5468d4e419ce3b46454762e0b82c3510738983233915ea141cbcff2102471453b08cb98b5daec885295f9f564267688c5613a30edfbd5151c337e150fed96af0bb806ac0ea81c4de2444bb52120d84e7fa291762cc78f1642692d78e3f15&p=9a769a4786cc41af59a6e6285c07cf&newp=8067c64ad4934eaf58eccb3861648b231610db2151d6d0106b82c825d7331b001c3bbfb423241200d7c07a6c00ac4e56ecf73d78350025a3dda5c91d9fb4c57479&user=baidu&fm=sc&query=%BB%A5%C1%AA%CD%F8%BD%F0%C8%DAjava%C3%E6%CA%D4%CC%E2&qid=d6cd91620001a7e2&p1=1
http://blog.csdn.net/luanlouis/article/details/40422941
http://ms.csdn.net/geek/79519
http://blog.csdn.net/u010154380/article/details/53557507
http://blog.csdn.net/qq_35124535/article/details/64129964
http://blog.csdn.net/pistolove/article/details/46753275
Redis併發注意點:http://qsalg.com/?p=423
RPC原理:http://www.cnblogs.com/LBSer/p/4853234.html
Java相關原理和深入學習:http://wiki.jikexueyuan.com/list/java/
微服務的一種開源實現方式——dubbo+zookeeper http://blog.csdn.net/zhdd99/article/details/52263609
RPC框架:
http://www.cnblogs.com/luxiaoxun/p/5272384.html
https://my.oschina.net/huangyong/blog/361751
JDK集合框架原理:http://yikun.github.io/tags/Java/
牛逼的博客:http://www.cnblogs.com/LBSer/p/4753112.html
編程狂人週刊:http://www.tuicool.com/mags
碼農週刊:http://weekly.manong.io/issues/
http://blog.csdn.net/zhangliangzi/article/details/52526125
成爲一個Java的架構師要學習哪些知識? 搜索知乎
soa和微服務架構的區別?搜索知乎
美團點評技術: http://tech.meituan.com/
http://blog.csdn.net/zhangliangzi/article/details/50995326
Java精華:
http://blog.csdn.net/kobejayandy/article/category/1216487
Java併發集合的實現原理:http://www.cnblogs.com/luxiaoxun/p/4638748.html
Java併發原理:http://blog.csdn.net/column/details/14531.html
數據庫索引原理和B+樹算法分析!!!數據庫索引的實現(B+樹介紹、和B樹、R樹區別)
分佈式的冪等性和分佈式CAP原理!!!
多讀書:
JVM公衆號推薦:你假笨
《core Java》多線程部分 《effective java》《深入理解Java虛擬機》《Java併發編程實戰》
《Java多線程編程核心技術》《實戰java高併發程序設計》《Java併發編程實戰》《jcip》《深入理解Java虛擬機:JVM高級特性與最佳實踐》《深入分析Java Web技術內幕》《Spring源碼深度解析》
《大型網站技術架構 核心原理與案例分析》《大型網站系統與Java中間件實踐》《高性能MySQL》
《重構 改善既有代碼的設計》《企業應用架構模式》《從Paxos到ZooKeeper 分佈式一致性原理與實踐》
《分佈式系統常用技術及案例分析》《精通Spring 4.x》
《高性能網站構建實戰》《實用負載均衡技術:網站性能優化攻略》
書籍推薦:http://blog.jobbole.com/106093/
http://www.importnew.com/7099.html
阿里面試:http://www.importnew.com/22056.html
http://www.importnew.com/22637.html
http://yemengying.com/2016/06/05/interview/
面試必看:https://github.com/kdn251/interviews/blob/master/README-zh-cn.md
https://github.com/it-interview/easy-job
http://taoxiaoran.top/pages/tags.html#Java
https://github.com/hadyang/interview
數據結構:
動畫版展示數據結構: http://zh.visualgo.net/
架構師技能:http://www.zhihu.com/question/29031276
AOP必看博客,架構分析+優化
http://blog.csdn.net/xvshu/article/details/46288953
http://blog.csdn.net/xvshu/article/category/2110821/2
http://blog.csdn.net/chenleixing/article/details/47099725
http://www.cnblogs.com/java-zhao/category/776210.html
http://www.iteye.com/topic/1122859
http://www.cnblogs.com/davidwang456/p/4213652.html
http://blog.csdn.net/z69183787/article/category/2175689/2
http://www.cnblogs.com/java-zhao/p/5106189.html
http://blog.csdn.net/xyw591238/article/category/6083265
https://github.com/jcalaz/jcalaBlog
Java併發容器源碼分析:http://blog.csdn.net/Architect0719/article/category/6193805
書籍推薦:
http://blog.csdn.net/u013256816/article/details/52091850
讀書:
深入理解計算機系統、Java併發編程實戰、深入理解Java虛擬機第二版、tcp/ip詳解 卷一、二、三,Java數據結構與算法
幾個流行Java框架:
Spring MVC/Spring Boot
Spark
Dropwizard
Ninja framework
ZK
Ratpack 用於構建現代化HTTP程序的Java庫
Jodd
jHipster
分佈式協調框架:zookeeper etcd
Java 應用監控 https://github.com/stagemonitor/stagemonitor
http://wsmajunfeng.iteye.com/blog/1744587
7個監控項目: http://www.codeceo.com/article/7-monitor-tools.html
Swagger學習:
http://blog.csdn.net/u010827436/article/details/44417637
Java博客推薦:http://www.tuicool.com/articles/jUrQ7r6
http://www.cnblogs.com/swiftma/category/816241.html
http://www.cnblogs.com/skywang12345/category/489072.html
Java常見類底層實現分析:http://blog.csdn.net/column/details/chenssy-javaimpr.html
開發者頭條、簡書、碼農週刊、推酷、http://www.importnew.com/ 、極客頭條、stackoverflow、java world
深入Java:http://www.codeceo.com/article/tag/java 、http://hugnew.com/?cat=17 、 http://www.hollischuang.com/archives/1001 、
http://www.ibm.com/developerworks/cn/java/ 、 http://www.programcreek.com/ 、http://www.open-open.com/lib/tag/Spring 、
http://blog.csdn.net/pkueecser/article/details/50670601 、猿天地 http://cxytiandi.com/blog 、技術乾貨:http://www.primeton.com/pr/tech.php
國外技術趨勢網站:
https://www.infoq.com
https://seroter.wordpress.com/category/aws/
http://www.theserverside.com/
https://dzone.com
https://www.javacodegeeks.com/2015/12/profile-successful-java-developer-2016.html?spm=5176.100239.blogcont54071.5.YXZzqC
http://ifeve.com/tech-related-sites/
Chrome插件:
掘金:http://gold.xitu.io/extension/
Postman:
需要熟練使用版本控制工具 Git(閱讀:《Git 權威指南》),以及項目構建工具 Maven(閱讀:《Maven實戰》)。另外,在這個階段可以嘗試 TDD 開發。
進階(2-6 個月)
目標:獨立負責某個服務端項目。
技能:
掌握 web 開發最佳實踐,掌握 Restful API 設計,理解 Spring 原理。推薦閱讀《Spring 揭祕》。掌握項目分層、子模塊劃分。推薦閱讀:《J2EE 核心模式》。
掌握 web 架構設計。包括 Http 反向代理,數據緩存,負載均衡,水平擴展和垂直擴展。推薦閱讀:《分佈式Java應用:基礎與實踐》。
掌握關係型數據庫。包括設計 MySQL 表結構,根據業務特點分表分庫,基於執行計劃的 SQL 分析優化,以及數據庫容量規劃。推薦閱讀:《MySQL 必知必會》、《高性能 MySQL》。
瞭解 NoSQL。我們大規模使用 Hadoop、HBase、Hive,同時部分項目使用 Redis、Storm。你需要學會這些工具最基本的使用。
學習 web 安全知識。瞭解 web 前端安全問題。設計安全 web 服務,包括加解密、防僞造、防重放攻擊等。
掌握 Http(推薦閱讀:《圖解 Http》、《Http 權威指南》)、Thrift 等協議。
掌握服務容量規劃,性能調優,可靠性保證,以及故障處理。學習容量規劃和性能調優知識,梳理業務監控點,熟練使用我們的監控報警系統。推薦閱讀:《深入理解 Java 虛擬機》。
其他。設計模式:從項目中學習,有時間可以看看《深入淺出設計模式》、《JDK 裏的設計模式》。學習Java Socket 編程與多線程知識,可以看看《Java 併發編程實戰》,並翻翻併發編程網的文章。
深入(6 個月-)
目標:分佈式系統和中間件開發。
構建知識體系:《大型網站系統與 Java 中間件實踐》、《大型網站技術架構:核心原理與案例分析》。
原理與設計:《大規模存儲式系統》、《UNIX 網絡編程 卷1:套接字聯網 API》、《How Tomcat Works》。
學習開源項目:Apache Thrift、Zipkin、Netty、Rose、Jade、淘寶 RPC 系統 Dubbo 等。分析項目中的設計思路。比如,同樣是RPC框架,Finagle 和 Dubbo 有什麼異同。
其他。根據參與的項目加深學習吧。比如,如果需要寫 DSL,可以讀一下《領域特定語言》,對 Redis 感興趣推薦讀一下:《Redis 設計與實現》。有兩本書,無論做什麼項目,都推薦讀:《Unix 編程藝術》、《UNIX 環境高級編程(第3版)》。
HashMap、TreeMap、數據庫索引 原理,及常見的平衡二叉樹,紅黑樹原理和實現,redis的幾種數據結構原理和優缺點(Hash結構的ziplist和quicklist原理),Hash算法原理和實現
https://github.com/spotify/apollo
http://blog.csdn.net/column/details/java-vitual-machine.html
http://blog.csdn.net/column/details/yrp-java-algorithm.html?&page=2
http://blog.csdn.net/column/details/datastructureinjava.html
http://blog.csdn.net/yannanying/article/details/46956355
http://blog.csdn.net/column/details/datastructure-phn.html
http://blog.csdn.net/column/details/zhonghua.html
http://blog.csdn.net/column/details/datastructure.html
https://github.com/shekhargulati/52-technologies-in-2016
十二個程序員必備的優質資源推薦: http://blog.csdn.net/proginn/article/details/51614131
美團點評技術團隊:http://tech.meituan.com/
阿里中間件團隊:http://jm.taobao.org/
BAT 技術團隊博客:http://blog.csdn.net/tengdazhang770960436/article/details/49963983
技術博客推薦:http://www.cnblogs.com/newpanderking/p/4366174.html
NB的技術社區:https://yq.aliyun.com/tags/type_blog-tagid_41/?spm=5176.100239.rightarea.8.4qenGu
Spring-session源碼解析:https://yq.aliyun.com/articles/57425?spm=5176.100239.blogrightarea58510.22.DTpSqZ
Java上線用到的項目:
JMeter 測試
FindBugs SparkJava
JProfiler 約束內存泄漏和修復線程的問題。
Takipi
消息中間件:
Nats --- 速度超快 每秒能處理千萬級別消息,佔用CPU少,但不支持離線
Kafka --- 速度快,每秒處理百萬級消息,能存儲,適合大數據
推薦需要看的幾本書:
《輕量級微服務架構(上冊)》
<<Web Scalability for Startup Engineers--互聯網創業核心技術:構建可伸縮的web應用>>
《Spring源碼深度解析》《大型網站技術架構 核心原理與案例分析》《大型網站系統與Java中間件實踐》《Effective Java中文版》《HotSpot實戰》
《從Paxos到ZooKeeper 分佈式一致性原理與實踐》《深入分析Java Web技術內幕》《java多線程編程核心技術》《實戰Java高併發程序設計》
《深入Java虛擬機第2版》《重構 改善既有代碼的設計》 《高性能MySQL第3版》 《Java編程思想第4版》 《HTTP權威指南》 《精通正則表達式必知必會》
《Java解惑》 《Java併發編程實踐》 《鳥哥的Linux私房菜》《How Tomcat Works(中英文版)》 《Maven權威指南》
《Java併發編程實戰》
要求:
其次掌握的技能樹主要有三個方面:
第一個是基礎,比如對集合類,併發包,IO/NIO,JVM,內存模型,泛型,異常,反射,等有深入瞭解,最好是看過源碼瞭解底層的設計。比如一般面試都會問ConcurrentHashMap,CopyOnWrite,線程池,CAS,AQS,虛擬機優化等知識點,因爲這些對互聯網的企業是絕對重要的。而且一般人這關都過不了,還發鬧騷說這些沒什麼用,爲什麼要面試。舉一例子,在使用線程池時,因爲使用了無界隊列,在遠程服務異常情況下導致內層飆升,怎麼去解決?你要是連線程池都不清楚,你怎麼去玩?再舉一例,由於對ThreadLocal理解出錯,使用它做線程安全的控制,導致沒能實現真的線程安全,你怪我哦?所以作爲一個拿兩萬的JAVA程序員這點基礎是必須的。
第二你需要有全面的互聯網技術相關知識。從底層說起,你起碼得深入瞭解mysql,redis,mongodb,nginx,tomcat,rpc,jms等方面的知識。你要問需要瞭解到什麼程度,我可以給你說個大慨。首先對於MySQL,你要知道常見的參數設置,存儲引擎怎麼去選擇,還需要了解常見的索引引擎,知道怎麼去選擇。知道怎麼去設計表,怎麼優化sql,怎麼根據執行計劃去調優。高級的你需要去做分庫分表的設計和優化,一般互聯網企業的數據庫都是讀寫分離,還會垂直與水平拆分,所以這個也有經驗的成分在裏面。然後redis,mongodb都是需要了解原理,需要會調整參數的,而nginx和tomcat幾乎都是JAVA互聯網方面必配,其實很阿里的技術棧選擇有點關係。至於rpc相關的就多的去,必須各種網絡協議,序列化技術,SOA等等,你要有一個深入的理解。現在應用比較廣的rpc框架,在國內就是dubbo了,可以自行搜索。至於jms相關的起碼得了解原理吧,一般情況下不是專門開發中間件系統和支撐系統的不需要了解太多細節,國內企業常用的主要是activeMQ和kafka。你能對我說的都研究的比較深入,阿里p6我覺得是沒問題的,當然這個還需要看你的架構能力方面的面試表現了。
第三就是編程能力,編程思想,算法能力,架構能力的考量。首先2W程序員對算法的要求我覺得還是比較低,再高級也最多紅黑樹吧,但是排序和查詢的基本算法得會。編程思想是必須的,問你個AOP和IOC你起碼的清清楚楚,設計模式不說每種都用過,但是也能深入理解個十四五種。編程能力這個我覺得不好去評價,但是拿一個2000W用戶根據姓名年齡排序這種題目也能信手拈來。最後就是架構能力,這種不是說要你設計個多牛逼多高併發的系統,起碼讓你做一個秒殺系統,防重請求的設計能快速搞定而沒有坑吧。
#深入理解Java虛擬機第2版
#Java併發編程實戰
#MongoDB權威指南
#Netty權威指南
Netty Mina框架源碼
2016新興互聯網公司前300:http://www.askci.com/news/hlw/20160425/941447185.shtml
面試:
1.數據傳入的安全性解決方案?認證 SSL HTTPS 原理(eg:遊戲中數據傳輸給服務器,如何保證數據安全和完整,防止外掛?)
2.如何使用多線程處理同一個大的任務?
3.蹲坑算法(數據量大的時候,根據內存空間的有序性,爲每個數找各自對應的內存空間地址)
4.如何防止內存被擊穿,最大併發限制,降級策略?(eg:遊戲服務器,併發最多5000,超過就回擊穿服務器內存,如果玩家>5000,如何處理?)
百萬級訪問量網站架構:http://www.biaodianfu.com/thinking-before-building-site.html
http://www.wtoutiao.com/p/12aqbAi.html
初期架構一般比較簡單,web負載均衡+數據庫主從+緩存+分佈式存儲+隊列。
大方向上也確實就這幾樣東西,細節上也無數文章都重複過了,按照將來會有N多WEB,N多主從關係,N多緩存,N多xxx設計就行,基本方案都是現成的,
只是您比其他人厲害之處就在於設計上考慮到緩存失效時的雪崩效應、主從同步的數據一致性和時間差、隊列的穩定性和失敗後的重試策略、文件存儲的效率和備份方式等等意外情況。
緩存總有一天會失效,數據庫複製總有一天會斷掉,隊列總有一天會寫不進去,電源總有一天會燒壞。根據墨菲定律,如果不考慮這些,網站早晚會成爲茶几
需要加強學習的東西:
網絡: Netty,mina,NIO,REST,OAuth
Web:Tapestry,DWR,GWT,WebX
搜索: ElasticSearch,Solr
緩存/DB: mongoDB、HBASE,Cassandra,Redis
中間件:RPC,Dubbo,Thrift,Zookeeper,ActiveMQ, Kafka
技能: Shell編程、性能調優、MAT、救火、故障排查
跨語言: shell、Go、NodeJS
大數據: hadoop、hbase、storm、hive、pig、spark
其他框架:Spring Boot、Spring Cloud、 Disruptor(Disruptor是一個用於在線程間通信的高效低延時的消息組件,它像個增強的隊列)、Guava、Trove(高性能集合框架)
多線程推薦:
http://www.cnblogs.com/dolphin0520/category/602384.html
http://blog.csdn.net/qilixiang012/article/category/2857487
http://blog.csdn.net/column/details/java-dxc.html
1.Eclipse安裝Activiti插件 http://activiti.org/designer/update/
2.Spring MVC 輸出頁面亂碼 在mvc.xml中配置 StringHttpMessageConverter 編碼格式
<mvc:message-converters>
<beans:bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
<beans:property name="supportedMediaTypes">
<beans:list>
<beans:value>text/plain;charset=UTF-8</beans:value>
<beans:value>text/html;charset=UTF-8</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></beans:bean>
</mvc:message-converters>
3.Spring MVC註解式統一異常處理:
--- 可根據異常,返回數據或指定頁面
BaseController中添加方法處理異常
@ExceptionHandler(RuntimeException.class)
public @ResponseBody ResponseVo runtimeExceptionHandler(RuntimeException ex) {
ResponseVo responseVo = new ResponseVo();
responseVo.setSuccess(false);
responseVo.setMsg(ex.getMessage());
responseVo.setData(ex);
return responseVo;
}
4.幾個最常用的Eclipse快捷鍵
1. ctrl+shift+r:打開資源,在workspace中快速按文件名查找
2. ctrl+o:快速outline,查看代碼結構,顯示類中方法和屬性,能快速定位類的方法和屬性
3. ctrl+e:快速轉換編輯器
4. ctrl+2,L:爲本地變量賦值
5. alt+shift+r:重命名
6. Alt+左右方向鍵
我們經常會遇到看代碼時Ctrl+左鍵,層層跟蹤,然後迷失在代碼中的情況,
這時只需要按“Alt+左方向鍵”就可以退回到上次閱讀的位置,
同理,按“Alt+右方向鍵”會前進到剛纔退回的閱讀位置
7.ctrl+shift+x和ctrl+shift+y:英文字母大小寫的轉換
8.ctrl+shift+f:格式化代碼
9.ctrl+m:當前編輯頁面窗口最大/小化
10.ctrl+shift+o:自動引入包和刪除無用包
11.Ctrl+T 快速顯示當前類的繼承結構
12.Ctrl+W 關閉當前窗口
13.Alt+Shift+R 重命名
14.Alt+Shift+M 抽取方法
15.【Ct rl+K】、【Ct rl++Shift +K】 快速向下和向上查找選定的內容,從此不再需要用鼠標單擊查找對話框
16.Ctrl+Shift+G 查找類、方法和屬性的引用
17.Alt+Shift+w 查找當前文件所在項目中的路徑
18.Ctrl+Shift+w 關閉所有文件
最實用的:
【Alt + ← 】 查看代碼時,返回上次查看位置 --- 後退上次代碼記錄
【Alt + → 】 查看代碼時,跟蹤,回到下次瀏覽位置
點中類名+F4 查看類的繼承關係
5.常見的內存移除的3種情況
1.JVM Heap(堆)溢出 java.lang.OutOfMemoryError:Java heap space
JVM在啓動的時候會自動設置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。
Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的時間是用於GC,且可用的Heap size 不足2%的時候將拋出此異常信息
解決方法:
設置JVM Heap(堆)大小
即:-Xmn -Xms -Xmx等選項,以及 年輕代與年老代的比例 ratio等參數
2.PermGen space溢出 java.lang.OutOfMemoryError:PermGen space
永久帶溢出 --- 一般發生在程序啓動階段
永久帶被JVM存放Class和Meta信息,如果載入很多Class,可能出現PermGen space溢出
解決方法:
通過-XX:PermSize和-XX:MaxPermSize設置永久代大小即可
3.棧溢出 java.lang.StackOverflowError : Thread Stack space
可能原因:遞歸層次太多,導致棧溢出
解決方法:
1.修改程序
2.通過 -Xss 設置每個線程的Stack大小
Server容器啓動時,需要設置的幾個JVM參數:
-Xms:java Heap 堆初始大小, 默認是物理內存的1/64。
-Xmx:java Heap 堆最大值,不可超過物理內存。
-Xmn:young generation的heap堆大小,一般設置爲Xmx的3、4分之一 。增大年輕代後,將會減小年老代大小,可以根據監控合理設置。
-Xss:每個線程的Stack大小,而最佳值應該是128K,默認值好像是512k。
-XX:PermSize:設定內存的永久保存區初始大小,缺省值爲64M。
-XX:MaxPermSize:設定內存的永久保存區最大大小,缺省值爲64M。
-XX:SurvivorRatio:Eden區與Survivor區的大小比值,設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10
-XX:+UseParallelGC:F年輕代使用併發收集,而年老代仍舊使用串行收集.
-XX:+UseParNewGC:設置年輕代爲並行收集,JDK5.0以上,JVM會根據系統配置自行設置,所無需再設置此值。
-XX:ParallelGCThreads:並行收集器的線程數,值最好配置與處理器數目相等 同樣適用於CMS。
-XX:+UseParallelOldGC:年老代垃圾收集方式爲並行收集(Parallel Compacting)。
-XX:MaxGCPauseMillis:每次年輕代垃圾回收的最長時間(最大暫停時間),如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值。
-XX:+ScavengeBeforeFullGC:Full GC前調用YGC,默認是true。
實例如:JAVA_OPTS=”-Xms4g -Xmx4g -Xmn1024m -XX:PermSize=320M -XX:MaxPermSize=320m -XX:SurvivorRatio=6″
6.Service或Dao層獲取request和IP方法?
1.獲取IP InetAddress.getLocalHost().getHostAddress()
2.獲取request對象 http://my.oschina.net/u/2007041/blog/420956
7.JDK6在Linux下的安裝
第一:用linux 的命令運行它: sh jdk-6u2-linux-i586-rpm.bin
第二:按多次回車後出現
Do you agree to the above license terms? [yes or no]
輸入yes
第三:編輯環境變量
$gedit ~/.bashrc
加入如下五行:
JAVA_HOME=/usr/java/jdk1.6.0_02
JAVA_BIN=/usr/java/jdk1.6.0_02/bin
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME JAVA_BIN PATH CLASSPATH
第四步是必須的,不然它總是調用FC6自帶的jdk1.4
第四:創建鏈接
#cd /usr/bin
#ln -s -f /usr/local/jdk1.5.0_05/jre/bin/java
#ln -s -f /usr/local/jdk1.5.0_05/bin/javac
或 環境變量配置 vi /etc/profile
#for java
export JAVA_HOME=/home/hetiewei/software/java/jdk1.8.0_40
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
#for go
export GOROOT=/home/hetiewei/go/soft/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/hetiewei/go/soft/go/pkg
#for node
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
PATH=/usr/local/ssl/bin:/sbin/:$PATH:/usr/sbin
export PATH
#for hadoop
export HADOOP_HOME=/home/hetiewei/software/bigdata/hadoop/hadoop-2.6.4
export PATH=.:$HADOOP_HOME/bin:$PATH
source /etc/profile
8.Tomcat啓動時,在initialing Spring root context 卡死:
解決:
1.查看數據庫連接
2.清除Tomcat下的work目錄
9.Maven install時出現編碼異常解決方法:
mvn clean install -Dmaven.javadoc.skip=true
maven 安裝時跳過 測試用例:
mvn clean install -Dmaven.test.skip=true
10.Spring和Mybatis整合時無法讀取properties的處理方案:
方法一:
修改<property name="sqlSessionFactory" ref="sqlSessionFactory"/>爲<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
原理:使用sqlSessionFactoryBeanName注入,不會立即初始化sqlSessionFactory, 所以不會引發提前初始化問題。
方法二:
直接刪掉<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
注意:在沒有配置這一行時,必須配置一個以sqlSessionFactory命名的org.mybatis.spring.SqlSessionFactoryBean。
11.token,Session的區別?
服務器在生成表單的時候同時生成一個CSRF token,插入到表單的一個hidden field裏面,並且把這個token記錄在服務器端,通常是用戶的Session數據裏面。
客戶端啥都不用幹,照常提交表單。當表單被提交的時候,服務端檢查一個表單裏面的token跟自己之前記錄下來的是否匹配,匹配才繼續處理。
CSRF劫持的請求也會帶上網站的cookie的,所以光驗證session並不能避免CSRF
token的關鍵是在於你在發送請求的時候一定要保證你是讀取了這個頁面的。。 而不是憑空就發送請求
對於HTTP協議來說,就是url,header,body
對於WEB頁面來說,就是url,form/post body,cookie
好了,現在有的就是這麼多東西,要怎麼用呢?
首先是第一個問題,HTTP請求是無狀態的,我怎麼知道誰是誰?
解:讓用戶每次訪問的時候告訴你它叫什麼,怎麼告訴?url,form/post body,cookie
然後是第二個問題,用戶訪問的時候說他自己是張三,他騙你怎麼辦?
解:在服務器端保存張三的信息,給他一個id,讓他下次用id訪問。id保存在url,form/post body,cookie中。這叫做session
現在是第三個問題,用戶提交了一筆訂單,你怎麼保證他是在你的訂單頁面提交的?(referer可能是一個辦法)
解:在你訂單頁面中放入一個加密的信息,只有真正打開了訂單頁才能知道,提交的時候將這個信息返回回來。這個東西,可以被叫做token
Token實現防止表單重複提交?
表單重複提交的兩種情形:
1.通過瀏覽器回退功能,回到原來頁面重複提交表單,服務器端應避免用戶重複註冊
2.提交完成後,單擊瀏覽器的“刷新”按鈕,瀏覽器會彈出對話框,詢問是否重新提交數據。單擊“是”,瀏覽器會重新提交數據。
如何防止?
每次請求都生產一個token標識
1.表單提交後,先匹配(使用Aop做)token,判斷當前用戶會話中token令牌值與當前請求參數的token令牌值是否一致
2.每次請求都創建一個新的token令牌,將其保存在當前會話(session)範圍內
3.token在服務器端匹配後,就把session中的toke令牌值刪除
12.Java線程安全的本質:線程中並沒有存放任何對象數據,而是在執行時,去主內存(堆)中去同步數據,所有的對象數據都存在JVM的堆中,因此需要對資源進行共享鎖!!!
堆 --- JVM的核心數據存儲區 --- 線程共享的主內存
堆中爲JVM的所有對象分配了內存空間用以存儲和維護變量值等
棧 --- 線程私有的內存區,由值棧(線程棧)組成,存放8中基本數據類型和對象引用
每個線程都會生成一個自有的線程棧,線程棧中用存儲了該線程的基本數據常量,變量值,以及對象長變量的引用
每個線程執行時,根據代碼順序,壓棧 值棧(棧內存)
對象變量在線程執行時的過程:!!! --- 由JVM內存模型決定
1.線程根據棧中的引用去堆上同步該對象數據下來,然後在線程自己的內存中進行操作
2.操作之後再將線程棧撒花姑娘的運算結果同步到堆(主內存)中
3.多線程時,因爲每個線程都操作自己從主內存(JVM堆)中同步過來的數據,如果不加鎖,會導致線程安全問題(數據提交到主內存時不一致)
13.堆 --- JVM中所有對象的內存空間 分爲: Young Gen, Old Gen
Young Gen 又分爲:Eden區和兩個大小相同的Survivor區(from 和 to)
Eden和Survivor默認比例 8:1 由 -XX:SurvivorRation設置
堆大小 -Xmx -Xms 設置
Young Gen -Xmn 設置
-XX:NewSize和-XX:MaxNewSize
用於設置年輕代的大小,建議設爲整個堆大小的1/3或者1/4,兩個值設爲一樣大。
Minor GC --- 發生在新生代的垃圾回收,Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快,
年輕代的GC使用複製算法(將內存分爲兩塊,每次只用其中一塊,當這塊內存用完,就將還活着的對象複製到另外一塊上面,複製算法不會產生內存碎片)
Full GC --- 發生在年老代的GC, Full GC比較慢,儘量避免
新創建對象都會被分配到Eden區(一些大對象特殊處理),當Eden區滿則進行Minor GC,
這些對象經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區, 對象在Survivor區中每熬過一次Minor GC,年齡增加1歲,
當年齡增加到一定程度後(默認15歲),會被移動到年老代中,
當年老代滿時,經常Full GC
線程Stack 每個線程獨有的操作數棧局部變量表方法入口 -Xss 設置
方法區 -XX:PermSize和-XX:MaxPermSize設置
JVM中的類加載機制: http://www.cnblogs.com/ITtangtang/p/3978102.html
14.Go,MongoDB,redis,node.js在Linux下的安全和配置
1.go
下載tar.gz後,
解壓:tar -zxvf go1.6.2.linux-amd64.tar.gz
環境變量:
vi /etc/profile
在profile中添加一下內容:
GOPATH和GOROOT修改爲你的路徑即可!!!
export GOROOT=/home/forward/tools/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/forward/tools/gopkg
刷新環境變量:
source /etc/profile
驗證:
go version
編譯Go程序:
go build xx.go
直接運行Go程序:
go run xx.go
2.mongodb
解壓:
tar -zxvf mongodb-linux-i686-3.2.6.tgz
環境變量:
--- (只當前用戶有效)
export PATH=<mongodb-install-directory>/bin:$PATH
或
--- (全局有效)
vi /etc/profile
在profile中添加一下內容:
export PATH=<mongodb-install-directory>/bin:$PATH
刷新環境變量:
source /etc/profile
3.node.js
下載解壓:
wget --no-check-certificate https://nodejs.org/dist/v6.2.0/node-v6.2.0-linux-x64.tar.gz
環境變量:
NODE_HOME 是node.js安全目錄
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
刷新環境變量: 讓環境變量生效
source /etc/profile
驗證:
命令行輸入:node -v,查看node.js的版本
4.redis
參考官方源碼安裝說明
Redis Cluster集羣的搭建與實踐 http://lib.csdn.net/article/redis/60796
15.通過Spring在工具類中獲取HttpServletRequest對象:
/**
* 獲取當前Request
* @return
*/
private HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return requestAttributes.getRequest();
}
16.Spring mvc在redirect請求中傳遞數據 http://shmilyaw-hotmail-com.iteye.com/blog/2246344
3種方式:1.Session 2.url template 3. flash attribute
17.logback與ActiveMQ的slf4j jar包衝突解決??? --- MQ使用指定版本, 不使用用activemq-all
<!-- Spring 整合ActiveMQ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- activeMQ begin -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.7.0</version>
</dependency>
18.logback配置打印MyBatis執行的sql?
在logback.xml中配置 : dao所在的包名
<logger name="com.tuniu.plat.config.dao" level="DEBUG"/>
19.Spring MVC接收不到前端的參數???
試試: data:JSON.stringify(formData)
20.File文件目錄中文無法解析的問題:
String path = Config.class.getClassLoader().getResource("").toURI().getPath();
21.Spring MVC 轉發和重定向的傳參問題?
1.轉發 forward
1. this.getServletContext().getRequestDispatcher("/rentHouse.htm?method=display").forward(request,response);
return null;
2. return new ModelAndView("forward:/xxx.htm", map);
或
ModelAndView mv = new ModelAndView("forward:/xxx.htm");
mv.addAtrribute("param", value)
return mv;
2.重定向 redirect
1.無參數
return new ModelAndView("redirect:/toList");
return "redirect:/ toList ";
2.有參數
1.手動拼url
new ModelAndView("redirect:/toList?param1="+value1+"¶m2="+value2);
這樣有個弊端,就是傳中文可能會有亂碼問題。
2.用RedirectAttributes
redirectAttributes.addFlashAttribute("message", "保存用戶成功!");//使用addFlashAttribute,參數不會出現在url地址欄中
public String save(@ModelAttribute("form") Bean form,RedirectAttributes attr)
throws Exception {
String code = service.save(form);
if(code.equals("000")){
attr.addFlashAttribute("name", form.getName());
attr.addFlashAttribute("success", "添加成功!");
return "redirect:/index";
}else{
attr.addAttribute("projectName", form.getProjectName());
attr.addAttribute("enviroment", form.getEnviroment());
attr.addFlashAttribute("msg", "添加出錯!錯誤碼爲:"+rsp.getCode().getCode()+",錯誤爲:"+rsp.getCode().getName());
return "redirect:/maintenance/toAddConfigCenter";
}
}
注意:
1.使用RedirectAttributes的addAttribute方法傳遞參數會跟隨在URL後面,如上代碼即爲http:/index.action?a=a
2.使用addFlashAttribute不會跟隨在URL後面,會把該參數值暫時保存於session,待重定向url獲取該參數後從session中移除,
這裏的redirect必須是方法映射路徑,jsp無效。你會發現redirect後的jsp頁面中b只會出現一次,刷新後b再也不會出現了,
這驗證了上面說的,b被訪問後就會從session中移除。對於重複提交可以使用此來完成.
22.QQ 網頁上的登陸模塊(全程HTTP/GET請求) --- 前端數據的安全
QQ 在登陸時,對用戶輸入的密碼加密的JS代碼爲:
function getEncryption(password, uin, vcode, isMd5) {
var str1 = hexchar2bin(isMd5 ? password : md5(password));
var str2 = md5(str1 + uin);
var str3 = md5(str2 + vcode.toUpperCase());
return str3
}
白話就是: md5(md5(md5(密碼) + 用戶的QQ號) + 驗證碼)
驗證碼是一次性的, 所以,在你在網絡層拿到本次的請求之後,無法做 重放攻擊, 因爲驗證碼是不正確的.
23.git常用命令:
git init 將當前目錄初始化爲git倉庫
git status 查看倉庫狀態
git add xx 添加文件,該文件等待提交
git add -A 或 git add . 添加當前目錄下所有文件
git commit -m "備註" 提價到本地git倉庫
git remote add origin https://github.com/jayfeihe/xx.git 爲本地倉庫指定遠程倉庫
git remote rm origin 斷開遠程倉庫
git remote -v 查看遠程倉庫
git pull origin master 從遠程倉庫拉取文件
git push origin master 向遠程倉庫推送已提交內容
1,如何在提交代碼前看看我的代碼中不同文件的狀態?
git status
2,如何把別人的代碼拉取下來?
git clone url
url:一般都是在github上的倉庫地址
執行這個命令後,倉庫就會被下載到你指定的目錄
3,如何把新的文件加入到git的索引中?
git add file1 file2 file3
這麼增加很多文件的話一定很煩,那麼請使用git add . 把所有文件加入到git索引中
git索引:代表了你的文件已經被git管理
4,如何看我將要提交到遠程倉庫的文件?
git diff --cached
5,如何給我的提交增加備註說明?
git commit -m “xxxx說明”這個命令是提交代碼必須的
6,如何通過圖形化的界面查看該項目的所有歷史提交記錄?
gitk
7,如何查看項目的日誌?也就是你的提交記錄
git log
8,如何合併git add 和git commit 命令的效果?
git commit -a -m“xxx說明”
注意,這會把所有文件add到git索引中,可能你會有不想被git管理的文件,所以你需要事先通過忽略文件來控制。
9,如何創建一個分支?
git branch a
a就是新分支,然後使用git checkout a來切換到a分支,創建分支的意義是,你可以在自己的分支下開發,在開發完成後和主版本master合併,尤其在團隊中尤爲重要
10,如何合併分支到主分支?
git merge a master
11,如何刪除已經合併的分支?
git branch -D a
12,如何暫時保存我們的工作記錄,去看一個例如修復版本bug的事情?
git stash “xxxxx” 暫時記錄你的工作狀態
進行你的修復工作
git commit -a -m “xxx提交你的修復”
git stash apply 回到你的工作
13,如何搞定遠程分支和本地主版本的合併?
git branch --track [遠程分支的名稱] origin/[遠程分支的名稱]
例如:git branch --track a origin/a
git pull a
pull:這個命令同時做了下載遠程a分支,併合併到本地master的動作。如果有衝突是會合並失敗的,但是不會造成下載a分支失敗。
14,如何根據關鍵字搜索其在代碼中出現的位置?
git grep -n [key]
15,如果我想重置我的版本如何做?
git reset --hard HEAD
這個命令會把你做過的所有未提交(git commit -m)的內容清空
16,如果我只想重置一個文件怎麼做?
git checkout --file
這時只會check出一個未修改過的文件
17,如何修復一個已經提交文件中的錯誤?
雖然有2種做法,創建一個新提交和checkout出老提交併修復,但是建議通過創建新提交去做。因爲git對於歷史內容被改動會出現不能合併的情況
24.ActiveMQ整合Spring,監聽隊列
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd ">
<!-- ActiveMQ連接工廠 -->
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL"
value="failover:(tcp://mq.master.jd.tuniu-cie.org:61616)" />
</bean>
<!-- <bean id="productDetailListener" class="com.tuniu.plat.service.mq.ProductDetailListener"/> -->
<!--方式一:只監聽一個隊列 -->
<!-- 監聽器隊列 -->
<!--
<bean id="productDetailDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="${product_mq_queue}" />
-->
</bean>
<!-- 消息監聽容器(Queue),配置連接工廠,監聽的隊列,監聽器是:mq的產品消息監聽器 -->
<!--
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="amqConnectionFactory" />
<property name="destination" ref="productDetailDestination" />
<property name="messageListener" ref="productDetailListener" />
</bean>
-->
<!--方式二:監聽多個隊列 -->
<jms:listener-container destination-type="queue" container-type="default" connection-factory="amqConnectionFactory" acknowledge="auto">
<jms:listener destination="${product_mq_queue}" ref="productDetailListener"/>
</jms:listener-container>
<!-- Spring JmsTemplate 的消息生產者 start -->
<!-- 定義JmsTemplate的Queue類型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- 非pub/sub模型(發佈/訂閱),即隊列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定義JmsTemplate的Topic類型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- pub/sub模型(發佈/訂閱) -->
<property name="pubSubDomain" value="true" />
</bean>
<!--Spring JmsTemplate 的消息生產者 end -->
</beans>
25.JSP中無法解析後端傳遞的數據(jstl,el表達式不生效)?
1.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
2.jsp頁面
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page isELIgnored="false" %>
26.關於LinkedBlockQueue
阻塞的線程安全隊列,底層採用鏈表實現,LinkedBlockingQueue不接受null
添加元素: 都是向隊尾添加元素
put 向隊尾添加元素,如果隊列滿了會發生阻塞,一直等待空間,以加入元素
add 添加元素時,超出隊列長度會直接拋異常
offer 添加元素時,如果隊列已滿,直接返回false
移除元素: 都是從隊頭移除元素
poll 隊列爲空,返回null
remove 隊列爲空,拋出NoSuchElementException異常
take 隊列爲空,發送阻塞,等到有元素
BlockingQueue阻塞隊列的4個實現類:
ArrayBlockingQueue:規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的
LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的對象是以FIFO(先入先出)順序排序的 ,不允許放null
PriotityBlockingQueue:類似於LinkedBlockQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數的Comparator決定的順序
SynchronousQueue:對其的操作必須是放和取交替完成的
LinkedBlockingQueue的數據吞吐量要大於ArrayBlockingQueue,但在線程數量很大時其性能的可預見性低於ArrayBlockingQueue
27.MyBatis批量更新參考:
http://my.oschina.net/zouqun/blog/405424
http://blog.csdn.net/tolcf/article/details/39213217
28.Spring 中配置的@Aspect 不起作用:
1.添加註解:@Component
2.在Spring MVC的配置文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>
29.Spring Boot 遇到的問題?
Spring Boot Mapper無法注入:啓動類上添加註解 @MapperScan("com.jay.spring.boot.demo10.multidb.dao")
30.Maven指定打包環境:http://haohaoxuexi.iteye.com/blog/1900568
3個地方可以指定profile環境:
web.xml
maven的setting.xml --- 默認是:dev
項目的pom.xml
eg:pom.xml
<profiles>
<profile>
<id>development</id>
<properties>
<profile.path>config/dev</profile.path>
</properties>
</profile>
<profile>
<id>sit</id>
<properties>
<profile.path>config/sit</profile.path>
</properties>
</profile>
<profile>
<id>product</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.path>config/prd</profile.path>
</properties>
</profile>
</profiles>
1.通過下面配置指定默認環境:
<activation>
<activeByDefault>true</activeByDefault>
</activation>
或
2.通過命令指定環境:(說明:-P profile_id)
mvn package –P product
31.Spring中的幾個Listener監聽器和類 http://www.cnblogs.com/damowang/p/4305153.html
1.ServletContextListener接口 --- Web容器啓動時執行,此時Bean還未初始化,不能在裏面獲取依賴的bean
--- 適合做一些容器初始化工作
Why ServletContextListener接口無法獲取Spring中定義的Bean?
eg:
public class ConfigListener implements ServletContextListener {
@Autowired
private ConfigService configService;
@Override
public void contextInitialized(ServletContextEvent sce) {
configService.initConfig(); //這裏會報空指針異常,無法獲取注入的configService
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
要理解這個問題,首先要區分Listener的生命週期和spring管理的bean的生命週期
(1)Listener的生命週期是由servlet容器(例如tomcat)管理的,項目啓動時上例中的ConfigListener是由servlet容器實例化並調用其contextInitialized方法,而servlet容器並不認得@Autowired註解,因此導致ConfigService實例注入失敗。
(2)而spring容器中的bean的生命週期是由spring容器管理的。
(3)即:此時Spring管理的Bean還未初始化完成
修改:
@Override
public void contextInitialized(ServletContextEvent sce) {
ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
configService.initConfig();
}
特別注意:
以上代碼有一個前提,那就是servlet容器在實例化ConfigListener並調用其方法之前,要確保spring容器已經初始化完畢!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然後在配置自己的Listener,配置如下
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>example.ConfigListener</listener-class>
</listener>
2.ApplicationListener<T>接口 --- Spring容器初始化完成,Bean加載完成後執行, 可獲取所依賴的Bean,
---- 適合完成一些項目的初始化工作(eg:加載或清空過期緩存,執行初始化sql等)
T 表示事件:
ApplicationEvent,每次請求都會執行
ContextRefreshedEvent, Spring容器啓動後執行
參考:http://www.cnblogs.com/rollenholt/p/3612440.html
3.項目加載完畢後,執行一些初始化工作,eg:數據庫初始化或查詢數據,緩存初始化等 --- 3 種 方式
1.實現BeanPostProcessor接口
接口有兩個方法
(1)postProcessBeforeInitialization方法,在spring中定義的bean初始化前調用這個方法;
(2)postProcessAfterInitialization方法,在spring中定義的bean初始化後調用這個方法;
2.實現ApplicationListener接口
實現其onApplicationEvent()方法
Spring容器初始化完成,Bean加載完成後執行, 可獲取所依賴的Bean
特別注意:
ApplicationListener接口中的onApplicationEvent被調用多次的問題?
解決:
onApplicationEvent事件參數,添加final 即:final ContextRefreshedEvent event
eg:
@Component
public class NatsInitListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private NatsService natsService;
/**
* 容器啓動時,添加Nats的消息監聽
* @param contextRefreshedEvent
*/
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
try {
natsService.getMsgAsync("foo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.實現InitializingBean接口
實現afterPropertiesSet()方法
項目在加載完畢後立刻執行afterPropertiesSet 方法 ,並且可以使用spring 注入好的bean
32.Map的遍歷
entrySet()和 keySet() 方式, 前者快,後者滿
entrySet() 是map的一個節點,是橫向的,生成鍵和映射關係的視圖 不需要再get一次。所以效率明顯快
keySet() 是map中所有鍵的集合,是縱向的,先獲取出map的key,然後再通過key,get出value
keySet是鍵的集合,Set裏面的類型即key的類型
entrySet是 鍵-值 對的集合,Set裏面的類型是Map.Entry
Map<String, String> maps = new HashMap<String, String>();
//方法一: 用entrySet()
Iterator<Entry<String,String>> it = maps.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,String> m = it.next();
String key = m.getKey();
String value= m.getValue();
}
// 方法二:jdk1.5支持,用entrySet()和For-Each循環()
for (Map.Entry<String, String> m : maps.entrySet()) {
String key = m.getKey();
String value= m.getValue();
}
// 方法三:用keySet()
Iterator<String> it2 = maps.keySet().iterator();
while (it2.hasNext()){
String key = it2.next();
String value= maps.get(key);
}
// 方法四:jdk1.5支持,用keySet()和For-Each循環
for(String m: maps.keySet()){
String key = m;
String value= maps.get(m);
}
33.解決CORS跨域問題的3種方式?
1.Tomcat配置
1.下載cors-filter-1.7.jar,java-property-utils-1.9.jar這兩個庫文件,放到lib目錄下
2.項目的web.xml中添加如下配置
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.攔截器設置響應頭 --- Spring MVC 4.2以下版本推薦方式
<!-- API 接口跨域配置 -->
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="*"
allowed-methods="POST, GET, OPTIONS, DELETE, PUT"
allowed-headers="Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
allow-credentials="true" />
</mvc:cors>
3.使用Spring MVC 4.2+以上版本的
在對應的接口上添加註解: @CrossOrigin(origins="http://xxx", maxAge = 3600)
34.HttpSessionListener和HttpSessionAttributeListener接口的用法?
1.HttpSessionListener --- 2個方法
sessionCreated() --- 新建一個會話時觸發,可以說是客戶端第一次和服務器交互時觸發
sessionDestroyed() --- 銷燬會話時, 按鈕觸發進行銷燬或配置定時銷燬會話(關閉瀏覽器時不會銷燬)
2.HttpSessionAttributeListener --- 3個方法
attributeAdded() --- 在session中添加對象時觸發此操作,即:調用setAttribute這個方法時候會觸發
attributeRemoved() --- 修改、刪除session中添加對象時觸發,即:調用 removeAttribute這個方法時候會觸發
attributeReplaced() --- 在Session屬性被重新設置時觸發
eg:
統計在線會話數的功能,並讓超時的自動銷燬
web.xml
<listener>
<listener-class>
org.xiosu.listener.onlineListener (實現session監聽器接口的類的名字,包也要寫上)
</listener-class>
</listener>
<!--默認的會話超時時間間隔,以分鐘爲單位 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
OnlineListener.java
public class onlineListener implements HttpSessionListener,
HttpSessionAttributeListener {
// 參數
ServletContext sc;
ArrayList list = new ArrayList();
// 新建一個session時觸發此操作
public void sessionCreated(HttpSessionEvent se) {
sc = se.getSession().getServletContext();
System.out.println("新建一個session");
}
// 銷燬一個session時觸發此操作
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("銷燬一個session");
if (!list.isEmpty()) {
list.remove((String) se.getSession().getAttribute("userName"));
sc.setAttribute("list", list);
}
}
// 在session中添加對象時觸發此操作,在list中添加一個對象
public void attributeAdded(HttpSessionBindingEvent sbe) {
list.add((String) sbe.getValue());
System.out.println(sbe.getValue());
sc.setAttribute("list", list);
}
// 修改、刪除session中添加對象時觸發此操作
public void attributeRemoved(HttpSessionBindingEvent arg0) {
System.out.println("5555555");
}
public void attributeReplaced(HttpSessionBindingEvent arg0) {
System.out.println("77777777");
}
}
eg:會話計數器
https://github.com/appfuse/appfuse/blob/master/web/common/src/main/java/org/appfuse/webapp/listener/UserCounterListener.java
35.4種提交表單的方式 http://www.aikaiyuan.com/6324.html
base64與byte[]相互轉換:
import sun.misc.BASE64Decoder;//將base64轉換爲byte[]
import sun.misc.BASE64Encoder;//轉byet[]換爲base64
eg:
// 定義一個BASE64Encoder
BASE64Encoder encode = new BASE64Encoder();
// 將byte[]轉換爲base64
String base64 = encode.encode("五筆字型電子計算機".getBytes());
// 輸出base64
System.out.println(base64);
// 新建一個BASE64Decoder
BASE64Decoder decode = new BASE64Decoder();
// 將base64轉換爲byte[]
byte[] b = decode.decodeBuffer(base64);
// 輸出轉換後的byte[]
System.out.println(new String(b));
36.失敗重試
public class Test1{
public static void main(String[] args) {
new Test1().comparePrice(6, 3);
}
/*
* 3次嘗試,如果有一次成功,則不再嘗試,如果有異常,會重試3次
*
*/
public String comparePrice(int m, int n) {
int i=3;
while(i-->0){
System.out.println("第"+(3-i)+"次");
try {
int a = m/n;
return "success";
} catch (Exception e) {
System.out.println("出現異常");
}
}
return "error";
}
}
37.關於有返回值的多線程應用問題:
Future --- Callable
FutureTask --- Callable
eg;
對list遍歷,每個item都要請求一次網絡,可通過Future--Callable,用線程池方式,list遍歷中多線程調用
注意:
1.線程池可以JDK自帶的,也可用Spring的線程池
2.先通過線程池提交Callable任務,然後將返回的Future或FutureTask存放到一個List集合中
3.遍歷Future任務,通過調用future.get()得到每個線程的返回值,將多個線程的返回值組裝即可
public List<JdProductWare> getSkuWareDetail(List<JdProductWare> list) throws JdException {
//創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
List<JdProductWare> result = new ArrayList<>();
List<Future<JdProductWare>> furuteList = new ArrayList<Future<JdProductWare>>();
try{
//遍歷List,每個item都用一個線程執行,同時將線程的Future存入一個List集合
for(JdProductWare jdProductWare:list){
Future<JdProductWare> future = executorService.submit(new SkuDetailTask(url,accesstoken, appkey, appsecret, jdProductWare));
furuteList.add(future);
}
//遍歷Future集合,得到每個線程的返回值,並將返回值存入指定結果集
for(Future<JdProductWare> future:furuteList){
JdProductWare ware = future.get();
result.add(ware);
}
//銷燬線程池
executorService.shutdown();
}catch(Exception e){
LOG.info("獲取Sku的商品title和sku子標題出錯,【{}】", e.getMessage());
}
return result;
}
eg:
/*
* 景點操作控制器
*/
@Controller
@RequestMapping("/menpiao/")
public class MenPiaoScenicController {
private final static Logger LOG = LoggerFactory.getLogger(MenPiaoScenicController.class);
@Autowired
private MenPiaoSecnicService secnicService;
/*
* 推送京東景點信息
*/
@RequestMapping(value = "/send/{holidayId}", method = RequestMethod.GET)
public MenPiaoScenicInfo sendScenicToJd(@PathVariable("holidayId") String holidayId) throws JdException {
MenPiaoScenicInfo info = secnicService.sendSenicInfo(holidayId);
return info;
}
/*
* 使用異步多線程方式推送京東景點信息 holidayIds:多個holidayId 用 , 分隔
*/
@RequestMapping(value = "/send/batch/{holidayIds}", method = RequestMethod.GET)
public List<MenPiaoScenicInfo> sendScenicBatch(@PathVariable("holidayIds") String holidayIds) {
String ids[] = holidayIds != null ? holidayIds.split(",") : null;
List<MenPiaoScenicInfo> list = new ArrayList<>();
List<Future<MenPiaoScenicInfo>> futureList = new ArrayList<>();
try{
// 創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (String id : ids) {
futureList.add(executorService.submit(new sendScenicTask(secnicService, id)));
}
//遍歷Future集合,得到每個線程的返回值,並將返回值存入指定結果集
for(Future<MenPiaoScenicInfo> future:futureList){
MenPiaoScenicInfo info = future.get();
list.add(info);
}
//銷燬線程池
executorService.shutdown();
}catch(Exception e){
LOG.info("獲取Sku的商品title和sku子標題出錯,【{}】", e.getMessage());
}
return list;
}
}
class sendScenicTask implements Callable<MenPiaoScenicInfo> {
private MenPiaoSecnicService scenicService;
private String holidayId;
public sendScenicTask() {
super();
}
public sendScenicTask(MenPiaoSecnicService scenicService, String holidayId) {
super();
this.scenicService = scenicService;
this.holidayId = holidayId;
}
public MenPiaoSecnicService getScenicService() {
return scenicService;
}
public void setScenicService(MenPiaoSecnicService scenicService) {
this.scenicService = scenicService;
}
public String getHolidayId() {
return holidayId;
}
public void setHolidayId(String holidayId) {
this.holidayId = holidayId;
}
@Override
public MenPiaoScenicInfo call() throws Exception {
return scenicService.sendSenicInfo(holidayId);
}
}
38.MyBatis傳入多個參數(太多了可以封裝成Vo,少於3個的情況),3種方式?
1.Map傳遞
Dao層:
TicketOrderCoupon findOrderCoupon(Map<String, Long> map);
Mapper.xml:
<select id="findOrderCoupon" resultMap="BaseResultMap" parameterType="java.util.Map">
select
<include refid="Base_Column_List" />
from ticket_order_coupon
where sku_id = #{skuId,jdbcType=BIGINT}
and order_id = #{orderId,jdbcType=BIGINT}
</select>
Service層:
Map<String, Long> map = new HashMap<String, Long>();
map.put("skuId", item.getOutSkuId());
map.put("orderId", item.getOutOrderId());
TicketOrderCoupon orderCoupon = orderCouponMapper.findOrderCoupon(map);
2. 參數佔位符
DAO層:
Public User selectUser(String name,String area);
Mapper.xml:
<select id="selectUser" resultMap="BaseResultMap">
select * from user_user_t where user_name = #{0} and user_area=#{1}
</select>
其中:
#{0}代表接收的是dao層中的第一個參數,#{1}代表dao層中第二參數,更多參數一致往後加即可
3.參數標識
Dao層:
List<TicketOrderLog> selectLogList(@Param("type") String type, @Param("status") Integer status);
Mapper.xml:
<select id="selectLogList" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM ticket_order_log
WHERE
<![CDATA[ retry < 3 ]]>
and status = #{status,jdbcType=TINYINT}
<if test="type != null">
and type = #{type,jdbcType=VARCHAR}
</if>
</select>
注意:
@Param註解 org.apache.ibatis.annotations.Param
39.MyBatis批量操作和緩存,表關聯
批量操作參考:http://blog.csdn.net/u012562943/article/details/50425747
緩存參考:http://blog.csdn.net/u012562943/article/details/50403163
表關聯:http://blog.csdn.net/u012562943/article/details/50403144
40.Redis配置主從複製 + Spring Boot 整合Redis集羣
主Master:redis.conf
port 6379
maxmemory-policy noeviction
appendonly yes
appendfilename "appendonly.aof"
requirepass mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
從Slave1:redis.conf
port 6380
slaveof 127.0.0.1 6379 #主redis的IP port(注意:用IP不要用localhost)
slave-read-only no #slave是否只讀
masterauth mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
Spring Boot 連接Redis集羣
1.Ubuntu下創建redis集羣
1.源碼安裝redis,設置redis.conf中的cluster-enabled yes ,並拷貝3份分別爲redis1, redis2, redis3
# redis.conf
bind node1 # for node1
cluster-enabled yes
cluster-node-timeout 5000
2.啓動3個redis節點:
./redis-server ../redis.conf --port 6379
./redis-server ../redis.conf --port 6380
./redis-server ../redis.conf --port 6381
說明:
1.調試信息最重要的一行:
No cluster configuration found, I'm a1eec932d923b55e23a5fe6a488ed7a97e27c826
這表示我們的redis服務器正在運行在cluster mode
3.連接啓動的3個節點
node1 6379
node1 6380
node1 6381
說明:
它們都處於失聯狀態,我們現在開始配置將它們彼此連接起來,Redis有一個連接節點的工具稱爲redis-trib.rb.
它是一個ruby文件,需要 redis gem被安裝
1.安裝ruby
sudo apt-get install ruby
sudo apt-get install rubygems #如果可以執行gem命令,則不需要此步驟
2.安裝redis
gem install redis
3.執行創建集羣命令
./redis-trib.rb create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
每個節點負責數據的1/3,鍵入 'yes'
4.連接其中的一個節點,查看redis集羣狀態
src/redis-cli -h node2 cluster nodes
./redis-cli -p 6379
>cluster info
5.關於Redis集羣節點的管理:
參考:blog.51yip.com/nosql/1726.html
2.Spring Boot + Redis集羣
1.添加pom依賴
<!-- Spring data redis, 通過配置使用Redis集羣 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置文件
application.yml 或 application.properties中添加redis集羣的配置
spring:
redis:
cluster:
nodes:
- 127.0.0.1:6379
- 127.0.0.1:6380
- 127.0.0.1:6381
3.注入RedisTemplate,操作redis集羣
@Autowire
private RedisTemplate redisTemplate;
4.啓動Spring Boot項目,驗證數據
41.關於MyBatis的接口註解實現:
1.insert方法,返回操作對象保存後的id --- 即:這裏的 User 在執行save(user)方法後,user中的id會被自動賦值
/* 下面的註解,可以在操作完save方法後,得到保存後的id
* @SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
*/
@Insert("insert into micro_user(name, username, password, salt) values(#{name},#{username}, #{password}, #{salt})")
@SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
int save(User user);
2.註解版批量保存
/*
* 批量添加 --- 註解版
*/
@InsertProvider(type=UserMapperProvider.class, method = "batchInsert")
Integer batchSave(List<User> list);
public static class UserMapperProvider{
public String batchInsert(Map<String, List<User>> map) {
List<User> list = map.get("list");
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.append("insert into micro_user(name, username, password, salt) values ");
MessageFormat messageFormat = new MessageFormat("(#'{'list[{0}].name},#'{'list[{0}].username},#'{'list[{0}].password},#'{'list[{0}].salt})");
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(messageFormat.format(new Integer[]{i}));
stringBuilder.append(",");
}
stringBuilder.setLength(stringBuilder.length() - 1);
return stringBuilder.toString();
}
}
3.註解版查詢
@Select("select * from micro_user where del_flag = 0")
List<User> findAll();
@Results(
{
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "salt", property = "salt"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "login_time", property = "loginTime")
})
@Select("select * from micro_user where username = #{username}")
User findByUsername(String username);
42.Jenkins相關:
http://10.10.30.116:8888/jenkins/ 登錄名,密碼:admin admin
1.配置文件路徑:/root/.jenkins/
2.初始密碼:/root/.jenkins/secrets/initialAdminPassword
3.安裝參考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
jenkins+github持續集成:
參考:
http://www.jianshu.com/p/22b7860b4e81
http://www.jianshu.com/p/b2ed4d23a3a9
4.Jenkins無法下載插件的解決方法:http://blog.csdn.net/russ44/article/details/52266953
常見的無法安裝插件下載地址:
http://updates.jenkins-ci.org/download/plugins/
步驟:
從http://10.10.30.116:8888/jenkins/updateCenter/ 中查看爲安裝成功的插件,進行手動安裝即可
安裝順序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
8.deploy
9.email-ext
5.常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
6.Jenkins構建svn項目
1.General : 填寫項目名稱和描述
2.源碼管理:
選 Subversion
Modules:
Repository URL:http://boy.tuniu.com/svn/JDB/PPLA/test/jd-plat
Credentials: 填svn的賬號密碼
3.構建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals: clean package -Dmaven.test.skip=true (先clean再打包)
7.Jenkins構建git項目(github) --- 配置參考:http://10.10.30.116:8888/jenkins/job/cloudDemo1/
1.General : 填寫項目名稱和描述
2.源碼管理:
選Git
Repositories:
Repository URL:https://github.com/jayfeihe/spring-cloud-demo.git
Credentials: 填Github的賬號密碼
Branches : 填寫分支(默認:*/master)
源碼庫瀏覽器: githubweb
URL: https://github.com/jayfeihe/spring-cloud-demo/
3.構建觸發器: Build when a change is pushed to GitHub
4.構建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals: clean package -Dmaven.test.skip=true (先clean再打包)
點擊高級:
pom: workspace/CloudShopEurekaServer (如果git下有多個項目,可以選擇基於哪一個項目下的pom構建)
8.配置Tomcat登錄名和密碼 Tomcat --- > conf --> tomcat-users.xml
添加登錄名和密碼: deploy tomcat
<role rolename="tomcat"/>
<role rolename="role1"/>
<role rolename="manager-gui" />
<role rolename="manager-script" />
<role rolename="manager-status" />
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/>
<user username="deploy" password="tomcat" roles="manager-gui,manager-script,manager-status" />
42.WebStorm開發Node.js啓用代碼提示:
File -> Settings... -> Languages&Frameworks -> Node.js and NPM 頁
Code Assistatant啓用Node.js庫的代碼提示即可(點擊Enable按鈕)
43.WebStorm/Idea 與 Eclipse 快捷鍵對比大全
http://blog.csdn.net/quincylk/article/details/18256697
默認配置-Eclipse的常用快捷鍵對照表
查找/代替
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+shift+N ctrl+shift+R 通過文件名快速查找工程內的文件(必記)
ctrl+shift+alt+Nctrl+shift+alt+N 通過一個字符快速查找位置(必記)
ctrl+F ctrl+F 在文件內快速查找代碼
F3 ctrl+K查找下一個
shift+F3 ctrl+shift+K 查找上一個
ctrl+R ctrl+F 文件內代碼替換
ctrl+shift+R 指定目錄內代碼批量替換
ctrl+shift+F ctrl+H 指定目錄內代碼批量查找
界面操作
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+shift+A ctrl+shift+A 快速查找並使用編輯器所有功能(必記)
alt+[0-9] alt+[0-9] 快速拆合功能界面模塊
ctrl+shift+F12ctrl+shift+F12 最大區域顯示代碼(會隱藏其他的功能界面模塊)
alt+shift+F alt+shift+F 將當前文件加入收藏夾
ctrl+alt+s ctrl+alt+s 打開配置窗口
ctrl+tab ctrl+tab 切換代碼選項卡(還要進行此選擇,效率差些)
alt+←/→ alt+←/→ 切換代碼選項卡
ctrl+F4 ctrl+F4 關閉當前代碼選項卡
代碼編輯
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+D ctrl+shift+↑ 複製當前行
ctrl+W alt+shift+↑ 選中單詞
ctrl+←/→
ctrl+←/→
以單詞作爲邊界跳光標位置
alt+insert alt+insert 新建一個文件或其他
ctrl+alt+L ctrl+alt+L 格式化代碼
shift+tab/tab shift+tab/tab 減少/擴大縮進(可以在代碼中減少行縮進)
ctrl+Y ctrl+D 刪除一行
shift+enter shift+enter 重新開始一行(無論光標在哪個位置)
導航
Webstorm快捷鍵 Eclipse快捷鍵 說明
esc esc進入代碼編輯區域
alt+F1 alt+F1 查找代碼在其他界面模塊的位置,頗爲有用
ctrl+G ctrl+L 到指定行的代碼
ctrl+]/[ ctrl+]/[ 光標到代碼塊的前面或後面
alt+up/down ctrl+shift+up/down 上一個/下一個方法
44.新的架構模式:
前後端分離: 參考:http://developer.51cto.com/art/201404/435984.htm
前端:負責View和Controller層
後端:負責Model層、業務處理、數據等
前後端的接口
前端: (Front UI View展示) (Node中間層 Model + Controller處理) (Restful、SOAP)
展示(Angular、React)<--- 數據獲取(Node) <--- 後端數據接口
後端:
數據接口 <--- 業務邏輯 <--- 緩存+DB
45.Filebeat5+Kafka+ELK Docker搭建日誌系統: http://www.jianshu.com/p/9dfac37885cb
Docker筆記:http://www.jianshu.com/users/110149e3a887/latest_articles
46.分隔List集合,按指定大小,將集合分成多個
/**
* 常用工具類
* @author hetiewei(賀鐵偉)
*
*/
public class JayCommonUtil {
/**
* 按指定大小,分隔集合,將集合按規定個數分爲n個部分
*
* @param list
* @param len
* @return
*/
public static List<List<?>> splitList(List<?> list, int len) {
if (list == null || list.size() == 0 || len < 1) {
return null;
}
List<List<?>> result = new ArrayList<List<?>>();
int size = list.size();
int count = (size + len - 1) / len;
for (int i = 0; i < count; i++) {
List<?> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
result.add(subList);
}
return result;
}
}
47.Spring Data MongoDB註解
@Id - 文檔的唯一標識,在mongodb中爲ObjectId,它是唯一的,通過時間戳+機器標識+進程ID+自增計數器(確保同一秒內產生的Id不會衝突)構成。
@Document - 把一個java類聲明爲mongodb的文檔,可以通過collection參數指定這個類對應的文檔。
@DBRef - 聲明類似於關係數據庫的關聯關係。ps:暫不支持級聯的保存功能,當你在本實例中修改了DERef對象裏面的值時,單獨保存本實例並不能保存DERef引用的對象,它要另外保存,如下面例子的Person和Account。
@Indexed - 聲明該字段需要索引,建索引可以大大的提高查詢效率。
@CompoundIndex - 複合索引的聲明,建複合索引可以有效地提高多字段的查詢效率。
@GeoSpatialIndexed - 聲明該字段爲地理信息的索引。
@Transient - 映射忽略的字段,該字段不會保存到mongodb。
@PersistenceConstructor - 聲明構造函數,作用是把從數據庫取出的數據實例化爲對象。該構造函數傳入的值爲從DBObject中取出的數據
eg:
@Document(collection="person")
@CompoundIndexes({
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
})
public class Person<T extends Address> {
@Id
private String id;
@Indexed(unique = true)
private Integer ssn;
private String firstName;
@Indexed
private String lastName;
private Integer age;
@Transient
private Integer accountTotal;
@DBRef
private List<Account> accounts;
private T address;
public Person(Integer ssn) {
this.ssn = ssn;
}
@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
}
48. MongoDB根據已有數據文檔,批量造大數據,(指數級成倍增長)
原理:
1.將已有文件放到遊標
2.遍歷遊標得到文檔數組
3.批量插入文檔數組
> var cur = db.ticket_order_detail.find({},{_id:0,id:1,outOrderId:1,fullName:1,phone:1,address:1,orderRemark:1,venderRemark:1,bossRemark:1});
> var data =[];
> for(var i=0;i<cur.size();i++){ data.push(cur[i]); }
> db.ticket_order_detail.insert(data);
MongoDB數據備份與還原:
一、集合備份
mongoexport -h 集合所在服務器地址 -d 數據庫 -c 集合 -o 本地文件存儲位置
注:文件格式可選json、cvs等
官方參數說明:mongoexport --help
二、集合還原
mongoimport --host 目標IP --port 目標端口 --db 目標數據庫 --collection 目標集合 --file 備份文件所在位置
官方參數說明:mongoimport --help
49.MongoDB內存使用過大的問題,如果不設置默認會佔用系統總內存的50%~80%,
內存問題官方參考:https://docs.mongodb.com/manual/faq/storage/
設置 --wiredTigerCacheSizeGB 參數可防止佔用過多內存
eg:最多佔用2G內存
mongod -dbpath=../db --wiredTigerCacheSizeGB 2
說明:
1.如果是在Docker容器中運行,該參數不能超過容器分配的內存,建議:1或2G
解決MongoDB佔用內存過duo的幾個方法:!!!
1.啓動時,調整佔用內存大小(默認佔用主機內存的50%)
mongod -dbpath=../db --wiredTigerCacheSizeGB 1
2.壓縮集合,減少內存佔用
//進入集合所在的db
use db
//執行命令,壓縮指定集合
db.runCommand({compact:'collection_name'})
3.Linux下執行切換日誌命令 --- MongoDB內存有很大一部分跟日誌有關
use admin
db.runCommand("logRotate");
通過配置文件方式啓動MongoDB
./mongod --config /data/mongodb3/mongo.conf
monggo.conf內容:
storageEngine = wiredTiger
wiredTigerCacheSizeGB = 2
syncdelay = 30
wiredTigerCollectionBlockCompressor = snappy
port=38019
dbpath=/data/mongodb30/db
oplogSize=2048
logpath=/data/mongodb30/logs/mongodb.log
logappend=true
fork=true
rest=true
journal = true
解析:
storageEngine 是設置存儲引擎;wiredTigerCacheSizeGB 是設置mongodb存儲引擎所用的內容,默認爲系統內存的50%;
syncdelay 是設置從內存同步到硬盤的時間間隔,默認爲60秒,可以設置的少一些,在mongodb故障時,丟失的日誌會少一些;
wiredTigerCollectionBlockCompressor 是設定壓縮策略 snappy 是一種壓縮速度非常快的壓縮策略
50.使用MongoExpress操作MongoDB數據庫 --- Web頁面操作MongoDB
1.安裝mongo-express
npm install -g mongo-express
2.以管理員賬號啓動
mongo-express -a -u username -p password
連接到指定的數據庫
mongo-express -u username -p password -d database
連接到遠程庫
mongo-express -u username -p password -d database -H mongoDBHost -P mongoDBPort
3.操作數據庫
localhost:8081
4.Docker使用
$ docker run -it --rm -p 8081:8081 --link YOUR_MONGODB_CONTAINER:mongo mongo-express
51.使用elasticsearch-HQ 管理ElasticSearch
1.啓動ElasticSearch,並開啓跨域支持 --- elasticsearch.yml中http模塊下配置:
#是否支持跨域,默認false
http.cors.enabled: true
#支持跨域的路徑
http.cors.allow-origin: http://localhost
2.將elasticsearch-HQ解壓到指定路徑:eg: D:\new_tech\ELK\royrusso-elasticsearch-HQ-6a0f138
3.使用Nginx訪問elasticsearch-HQ,配置Nginx如下:
location / {
#訪問的靜態資源根路徑
root D:/new_tech/ELK/royrusso-elasticsearch-HQ-6a0f138;
#默認頁面
index index.html index.htm;
#允許跨域訪問
add_header 'Access-Control-Allow-Origin' '*';
}
4.訪問 http://localhost/即可
5.連接到指定的ElasticSearch,按提示,在左上角中填寫 http://localhost:9200,連接
52.Spring MVC 配置各種模板解析引擎
http://blog.csdn.net/zhuzhoulin/article/details/52371530
53.Spring Boot中處理靜態資源(自定義資源映射)
1.Spring Boot默認使用resources下的靜態資源進行映射
2.自定義資源映射的2種方式:
1.實現類繼承 WebMvcConfigurerAdapter 並重寫方法 addResourceHandlers
2.
eg:
以 /myres/* 映射到 classpath:/myres/* 爲例的代碼處理爲
方式1:
@Configuration
public class MyWebAppConfigurer
extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
super.addResourceHandlers(registry);
}
}
解析:
1.addResourceLocations 的參數是動參,可以這樣寫 addResourceLocations(“classpath:/img1/”, “classpath:/img2/”, “classpath:/img3/”);
2.如果我們要指定一個絕對路徑的文件夾(如 D:/data/api_files ),則只需要使用 addResourceLocations 指定即可
3.可以直接使用addResourceLocations 指定磁盤絕對路徑,同樣可以配置多個位置,注意路徑寫法需要加上file:
registry.addResourceHandler("/api_files/**").addResourceLocations("file:D:/data/api_files");
方式2:
#Spring MVC對靜態文件的配置
spring.resources.static-locations=classpath:/static/
54.Spring Boot 中,添加入參和出參解析器
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
/*
* 配置簡易的View, 默認頁面
* /, /index都指向 index頁面
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index");
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/login").setViewName("/user/login");
}
/**
* 配置mvc請求入參解析器
* @param argumentResolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
//添加入參Base64解碼
argumentResolvers.add(new JayRequestBase64Resolver());
//添加入參解密
// argumentResolvers.add(new JayRequestDecryptResovler());
}
/**
* 配置mvc參數返回解析器
* @param returnValueHandlers
*/
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
super.addReturnValueHandlers(returnValueHandlers);
//添加出參Base64編碼
returnValueHandlers.add(new JayResponseBase64Resovler());
//添加出參加密
// returnValueHandlers.add(new JayResponseEncryptResovler());
}
}
55.Java中創建對象的5種方式
1、用new語句創建對象,這是最常見的創建對象的方法。
2、通過工廠方法返回對象,如:String str = String.valueOf(23);
3、運用反射手段,調用java.lang.Class或者java.lang.reflect.Constructor類的newInstance()實例方法。如:Object obj = Class.forName("java.lang.Object").newInstance();
4、調用對象的clone()方法。 implements Cloneable
5、通過I/O流(包括反序列化),如運用反序列化手段,調用java.io.ObjectInputStream對象的 readObject()方法。
56.MyBatis返回數字類型結果:
將resultMap改爲resultType=Java對應的數字類型即可
eg:
<select id="selectByHolidayId" resultType="java.lang.Integer"
parameterType="java.lang.Integer">
select count(id) from t_menpiao_product_info
where holiday_id=#{holidayId,jdbcType=INTEGER}
</select>
57.JDK動態代理和cglib字節碼技術代理的區別?
1.JDK動態代理:
1.靜態代理 --- 代理對象和目標對象實現了相同的接口,目標對象作爲代理對象的一個屬性,
具體接口實現中,可以調用目標對象相應方法前後加上其他業務處理邏輯
2.JDK動態代理只能針對實現了接口的類生成代理
2.CGLIB代理 --- 通過字節碼技術,爲目標對象生成一個與其功能一樣的子類
1.針對類實現代理
2.主要是對指定的類生產一個子類,覆蓋其中的所有方法
3.被代理類或方法不能聲明爲final
3.區別:
1.JDK動態代理只能對實現了接口的類生成代理, 動態代理只能對於接口進行代理
2.cglib針對類實現代理,主要是對指定的類生成一個子類,覆蓋中的方法,因爲是繼承,所以該類或方法最好不要聲明成final ,final可以阻止繼承和多態
3.Spring實現中,如果有接口,默認使用JDK動態代理,如果目標對象沒有實現接口,使用cglib代理,
如果目標對象實現了接口,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
4.動態代理的應用
AOP(Aspect-OrientedProgramming,面向切面編程),AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現方式就是通過對目標對象的代理在連接點前後加入通知,完成統一的切面操作。
實現AOP的技術,主要分爲兩大類:
一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;
二是採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。
默認的策略是如果目標類是接口,則使用JDK動態代理技術,如果目標對象沒有實現接口,則默認會採用CGLIB代理。
如果目標對象實現了接口,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
5.參考:http://www.cnblogs.com/linghu-java/p/5714769.html
58.Spring boot + redis 實現消息隊列 --- 可用於分佈式系統
PK Spring的事件監聽器 --- 適合單系統(非分佈式)
1.依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.redis作爲消息隊列的配置
@Configuration
public class RedisConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 定義redis作爲消息隊列時的容器
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("queue1"));
return container;
}
//定義一個隊列消息監聽器,指定監聽器類和監聽方法
@Bean
MessageListenerAdapter listenerAdapter(Receiver receiver){
return new MessageListenerAdapter(receiver, "receiveMessage");
}
//定義一個監聽器對象
@Bean
Receiver receiver(CountDownLatch latch){
return new Receiver(latch);
}
@Bean
CountDownLatch latch() {
return new CountDownLatch(1);
}
}
3.消息監聽器
/**
* 在任何一個基於消息的應用中,都有消息發佈者和消息接收者(或者稱爲消息訂閱者)。
* 創建消息的接收者,我們只需一個普通POJO,在POJO中定義一個接收消息的方法即可
* Created by hetiewei on 2017/3/9.
*/
/*
該Receiver類被註冊爲一個消息監聽者,處理消息的方法可以任意命名,
給Receiver的構造函數通過@AutoWired標註注入了一個CountDownLatch實例,當接收到消息時,調用countDown()方法
*/
public class Receiver {
private Logger logger = LoggerFactory.getLogger(getClass());
private CountDownLatch latch;
@Autowired
public Receiver(CountDownLatch latch){
this.latch = latch;
}
public void receiveMessage(String message){
logger.info("Received <"+message+">");
latch.countDown();
}
}
4.發送消息
@RestController
@RequestMapping("/redis/queue")
public class RedisQueueController {
@Autowired
private CountDownLatch latch;
@Autowired
private StringRedisTemplate template;
/**
* 發送消息
* @param msg
* @return
*/
@GetMapping("/send/{msg}")
public String sendMsg(@PathVariable("msg") String msg) throws InterruptedException {
template.convertAndSend("queue1", msg);
latch.await();
return msg;
}
@GetMapping("/batch/send")
public String batchSend() throws InterruptedException {
for (int i=0;i<1000;i++){
template.convertAndSend("queue1", "msg"+i);
latch.await();
}
return "success";
}
}
59.關於Spring 聲明式事務的原理
參考:http://yemengying.com/2016/11/14/something-about-spring-transaction/
Spring的聲明式事務:
1.JavaConfig方法 --- 在需要管理事務的類或方法上添加 @Transactional註解,然後在配置類上添加 @EnableTransactionManagement註解
2.Xml方式 --- 添加 <tx:annotation-driven />
Spring會利用Aop在相關方法調用的前後進行事務管理
問題:
public class JayServiceImpl implements JayService {
public void A(List<Giraffe> giraffes) {
for (Giraffe giraffe : giraffes) {
B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
說明:
Service中A方法調用B方法,方法A沒有事務管理,方法B採用聲明式事務,通過在方法上聲明 @Transactional註解來做事務管理
問題:
Junit 測試方法 A 的時候發現方法 B 的事務並沒有開啓, 而直接調用方法 B 事務是正常開啓的???
// 沒有開啓事務
@Test
public void testA() {
giraffeService.A();
}
// 正常開啓事務
@Test
public void testB() {
giraffeService.B();
}
}
原理分析:
Spring在加載目標Bean時,會爲聲明瞭@Transactional的Bean創建一個代理類,而目標類本身並不能感知到代理類的存在,
調用通過Spring上下文注入的Bean的方法,而不是直接調用目標類的方法
即:
先調用代理類的方法,代理類再調用目標類的方法
Calling Code
--call--> Proxy --->foo()
---> Pojo --> pojo.foo()
對於加了@Transactional註解的方法,在調用代理類方法時,會先通過攔截器 TransactionInterceptor開啓事務,
然後再調用目標類的方法,最後在調用結束後, TransactionInterceptor會提交或回滾事務
問題解析:
對於第一段的代碼,我在方法 A 中調用方法 B,實際上是通過“this”的引用,也就是直接調用了目標類的方法,而非通過 Spring 上下文獲得的代理類,所以。。。事務是不會開啓滴
解決方法:
通過實現ApplicationContextAware接口獲得 Spring 的上下文,(或自動注入Context對象),然後獲得目標類的代理類,通過代理類的對象,調用方法 B,即可
public class GiraffeServiceImpl implements GiraffeService,ApplicationContextAware{
@Setter
private ApplicationContext applicationContext;
public void A(List<Giraffe> giraffes) {
GiraffeService service = applicationContext.getBean(GiraffeService.class);
for (Giraffe giraffe : giraffes) {
service.B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
60.Java類加載機制
裝載 ---> 鏈接(驗證 --> 準備 --> 解析) ---> 初始化
1.JVM類加載機制:
裝載:
1.找到該類型的class文件,產生一個該類型的class文件二進制數據流(ClassLoader需要實現的loadClassData()方法)
2.解析該二進制數據流爲方法區內的數據結構
3.創建一個該類型的java.lang.Class實例
最終:通過defineClass()創建一個Java類型對象(Class對象)
找到二進制字節碼,並加載到JVM中
JVM通過類全限定名(包名.類名) + 類加載器 完成類的加載,生成類對應的Class對象
鏈接:
驗證:
負責對二進制字節碼進行校驗、類信息是否符合JVM規範,有沒有安全問題、對class文件長度和類型進行檢查
參考:http://www.importnew.com/17105.html
準備:
初始化類中靜態變量、並將其初始化爲默認值 --- 只初始化靜態變量默認值 !!!,給其類變量賦值發生在初始化階段!!!
對於final類型的變量,準備階段直接賦初始值
該內存分配發生在方法區
解析:
解析類中調用的接口、類、字段、方法的符號引用,把虛擬機常量池中的符號引用轉換爲直接引用
初始化:
1.對static類變量指定初始值!!!(2種方式:一種是通過類變量的初始化語句,一種是靜態初始化語句)
2.一個類的初始化需要先初始化其父類,並遞歸初始化其祖先類
2.JVM必須在每個類或接口主動使用時進行初始化:
主動使用的情況:
1.創建類的實例(無論是new、還是反射、克隆、序列化創建的)
2.使用某個類的靜態方法
3.訪問某個類或即可的靜態字段
4.調用Java API中的某些反射方法
5.初始化某個類的子類(先初始化其父類)
6.啓動某個標明爲啓動類的類(含main()方法)
主動使用會導致類的初始化,其超類均將在該類的初始化之前被初始化,但通過子類訪問父類的靜態字段或方法時,對於子類(或子接口、接口的實現類)來說,這種訪問就是被動訪問,或者說訪問了該類(接口)中的不在該類(接口)中聲明的靜態成員
3.創建對象時,類中各成員的執行順序:
父靜態塊 <-- 子靜態塊 <-- 父普通代碼塊 <-- 父構造器 <-- 子普通代碼塊 <-- 子構造器
1.父類靜態成員和靜態初始化快,按在代碼中出現的順序依次執行。
2.子類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行。
3. 父類的實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
4.執行父類的構造方法。
5.子類實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
6.執行子類的構造方法。
eg:
public class Test {
public static void main(String[] args) {
Son s = new Son();
}
}
class Parent{
{
System.out.println("parent中的初始化塊");
}
static{
System.out.println("parent中static初始化塊");
}
public Parent(){
System.out.println("parent構造方法");
}
}
class Son extends Parent{
{
System.out.println("son中的初始化塊");
}
static{
System.out.println("son中的static初始化塊");
}
public Son(){
System.out.println("son構造方法");
}
}
結果:
parent中static初始化塊
son中的static初始化塊
parent中的初始化塊
parent構造方法
son中的初始化塊
son構造方法
61.MySQL性能優化
參考:
MySQL性能優化總結:http://www.cnblogs.com/luxiaoxun/p/4694144.html
http://blog.chinaunix.net/uid-29435603-id-4275475.html
1.存儲引擎選擇
參考:http://www.jb51.net/article/38178.htm
MyISAM:
不支持事務處理,爲每個表創建3個文件,分別存儲不同內容
支持表級鎖,表的寫操作會阻塞其他用戶對同一個表的讀和寫操作,併發度低
1.myISAM表的讀操作,不會阻塞其他用戶對同一個表的讀請求,但會阻塞對同一個表的寫請求。
2.myISAM表的寫操作,會阻塞其他用戶對同一個表的讀和寫操作。
3.myISAM表的讀、寫操作之間、以及寫操作之間是串行的
eg:
tb_Demo表,那麼就會生成以下三個文件:
1.tb_demo.frm,存儲表定義;
2.tb_demo.MYD,存儲數據;
3.tb_demo.MYI,存儲索引
適合場景:
1.選擇密集型表 --- MyISAM引擎在篩選大量數據時非常迅速 --- 查詢快
2.插入密集型表 --- 併發插入特性允許同時選擇和插入數據,適合管理:郵件或Web服務器日誌數據
總結:
1.適合做count的計算 (注意:不含where條件的統計,因爲MyISAM會記錄表的行數)
2.插入不頻繁,查詢非常頻繁
3.沒有事務需求
InnoDB: 默認引擎
支持事務處理
引入了行級鎖(併發高)和外鍵約束
不支持全文索引
適合場景:
1.更新密集型表 --- 特別適合處理多重併發的更新請求
2.事務
3.自動災難恢復 --- InnoDB表能夠自動從災難中恢復
4.外鍵約束 --- MySQL支持外鍵的存儲引擎只有InnoDB
5.支持自動增加列 Auto_INCREMNET屬性
6.InnoDB是爲處理巨大數據量時的最大性能設計
總結:
可靠性要求高,需要事務支持,並有較高的併發讀取頻率,適合InnoDB
行鎖機制必然決定了寫入時的更多性能開銷,而它的強項在於多線程的併發處理
表更新和查詢都相當的頻繁,並且表鎖定的機會比較大的情況指定數據引擎的創建
細節和具體實現的差別:
1.InnoDB不支持FULLTEXT類型的索引。
2.InnoDB 中不保存表的具體行數,也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可。注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的。
3.對於AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯合索引。
4.DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除。
5.LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導入數據後再改成InnoDB表,但是對於使用的額外的InnoDB特性(例如外鍵)的表不適用。
另外,InnoDB表的行鎖也不是絕對的,如果在執行一個SQL語句時MySQL不能確定要掃描的範圍,InnoDB表同樣會鎖全表,例如update table set num=1 where name like “%aaa%”
任何一種表都不是萬能的,只用恰當的針對業務類型來選擇合適的表類型,才能最大的發揮MySQL的性能優勢。
存儲引擎選擇依據?
是否需要支持事務;
是否需要使用熱備;
崩潰恢復:能否接受崩潰;
是否需要外鍵支持;
是否需要全文索引
經常使用什麼樣的查詢模式
數據量大小
eg:
需要事務和外鍵約束 -- InnoDB
需要全文索引 -- MyISAM
數據量大,傾向於InnoDB,因爲它支持事務處理和故障恢復,InnoDB可以利用事務日誌進行數據恢復,這會比較快。而MyISAM可能會需要幾個小時甚至幾天來幹這些事,InnoDB只需要幾分鐘
操作數據表的習慣,也會影響性能
eg:
COUNT() 在 MyISAM 表中會非常快,而在InnoDB 表下可能會很痛苦(
因爲InnoDB不保存表的行數,即:執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可,
注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的)
主鍵查詢在InnoDB下非常快,但如果主鍵太長也會導致性能問題
大批的inserts語句在MyISAM下回快一些,但updates語句在InnoDB下更快(尤其在併發量大的時候)
提示InnoDB性能的方法:
InnoDB支持事務,存儲過程,視圖和行級鎖,在高併發下,表現比MyISAM強很多
影響性能的配置:
innodb_flush_log_at_trx_commit 這個選項,如果設置爲1的話,那麼每次插入數據的時候都會自動提交,導致性能急劇下降,應該是跟刷新日誌有關係,設置爲0效率能夠看到明顯提升
當然,同 樣你可以SQL中提交“SET AUTOCOMMIT = 0”來設置達到好的性能
設置innodb_buffer_pool_size能夠提升InnoDB的性能
設置查詢緩存
2.配置文件my.ini參數優化
1.max_connections --- 最大併發連接數,允許的同時客戶連接數, 默認100, 建議根據需求設定,eg:1024
2.query_cache_size=0 --- 查詢緩存,用於緩存select 查詢結果,如果有許多返回相同查詢結果的SELECT查詢,並且很少改變表,可以設置query_cache_size大於0,可以極大改善查詢效率。而如果表數據頻繁變化,就不要使用這個,會適得其反
3.table_cache=256
4.thread_cache_size --- 緩存的最大線程數
5.sort_buffer --- 每個需要進行排序的線程分配該大小的一個緩衝區
6.wait_timeout --- 默認是28800秒,也就是說一個connection空閒超過8個小時,Mysql將自動斷開該connection,通俗的講就是一個連接在8小時內沒有活動,就會自動斷開該連接。 不要設置太長,建議 7200
7.default-storage-engine=INNODB # 創建新表時將使用的默認存儲引擎
配置示例,2G內存,針對站多,抗壓型的設置,最佳:
table_cache=1024 物理內存越大,設置就越大.默認爲2402,調到512-1024最佳
innodb_additional_mem_pool_size=4M 默認爲2M
innodb_flush_log_at_trx_commit=1
(設置爲0就是等到innodb_log_buffer_size列隊滿後再統一儲存,默認爲1)
innodb_log_buffer_size=2M 默認爲1M
innodb_thread_concurrency=8 你的服務器CPU有幾個就設置爲幾,建議用默認一般爲8
key_buffer_size=256M 默認爲218 調到128最佳
tmp_table_size=64M 默認爲16M 調到64-256最掛
read_buffer_size=4M 默認爲64K
read_rnd_buffer_size=16M 默認爲256K
sort_buffer_size=32M 默認爲256K
max_connections=1024 默認爲1210
thread_cache_size=120 默認爲60
query_cache_size=64M
一般:
table_cache=512
innodb_additional_mem_pool_size=8M
innodb_flush_log_at_trx_commit=0
innodb_log_buffer_size=4M
innodb_thread_concurrency=8
key_buffer_size=128M
tmp_table_size=128M
read_buffer_size=4M
read_rnd_buffer_size=16M
sort_buffer_size=32M
max_connections=1024
更多參考:
http://www.cnblogs.com/adolfmc/p/6056392.html
3.Query查詢優化
1.explain sql 查看執行效率,定位優化對象的性能瓶頸
2.永遠用小結果驅動大的結果集
3.儘可能在索引中完成排序
4.只取出自己需要的column,而不是*
5.使用最有效的過濾條件
6.用表連接代替子查詢
7.當只要一行數據時,使用limit 1
8.爲搜索字段建立索引
9.千萬不要ORDER BY RAND(),避免select *
10.儘可能使用NOT NULL
11.開啓查詢緩存,併爲查詢緩存優化查詢語句
eg:
select username from user where add_time >= now()
注意:
1.這樣的語句不會使用查詢緩存,
2.像NOW()和RAND()或是其它的諸如此類的SQL函數都不會開啓查詢緩存,因爲這些函數的返回是會不定的易變的。所以,你所需要的就是用一個變量來代替MySQL的函數,從而開啓緩存
3.修改, 對now()進行處理,只取年月日 yyyy-MM-dd,變爲一個不衣變的值
62.Java常見的鎖類型有哪些?請簡述其特點。
1、synchronized對象同步鎖:synchronized是對對象加鎖,可作用於對象、方法(相當於對this對象加鎖)、靜態方法(相當於對Class實例對象加鎖,鎖住的該類的所有對象)以保證併發環境的線程安全。同一時刻只有一個線程可以獲得鎖。
其底層實現是通過使用對象監視器Monitor,每個對象都有一個監視器,當線程試圖獲取Synchronized鎖定的對象時,就會去請求對象監視器(Monitor.Enter()方法),如果監視器空閒,則請求成功,會獲取執行鎖定代碼的權利;如果監視器已被其他線程持有,線程進入同步隊列等待。
2、Lock同步鎖:與synchronized功能類似,可從Lock與synchronized區別進行分析:
1、Lock可以通過tryLock()方法非阻塞地獲取鎖而。如果獲取了鎖即立刻返回true,否則立刻返回false。這個方法還有加上定時等待的重載方法tryLock(long time, TimeUnit unit)方法,在定時期間內,如果獲取了鎖立刻返回true,否則在定時結束後返回false。在定時等待期間可以被中斷,拋出InterruptException異常。而Synchronized在獲得鎖的過程中是不可被中斷的。
2、Lock可以通過lockInterrupt()方法可中斷的獲取鎖,與lock()方法不同的是等待時可以響應中斷,拋出InterruptException異常。
3、Synchronized是隱式的加鎖解鎖,而Lock必須顯示的加鎖解鎖,而且解鎖應放到finnally中,保證一定會被解鎖,而Synchronized在出現異常時也會自動解鎖。但也因爲這樣,Lock更加靈活。
4、Synchronized是JVM層面上的設計,對對象加鎖,基於對象監視器。Lock是代碼實現的。
3、可重入鎖:ReentrantLock與Synchronized都是可重入鎖。可重入意味着,獲得鎖的線程可遞歸的再次獲取鎖。當所有鎖釋放後,其他線程纔可以獲取鎖。
4、公平鎖與非公平鎖:“公平性”是指是否等待最久的線程就會獲得資源。如果獲得鎖的順序是順序的,那麼就是公平的。不公平鎖一般效率高於公平鎖。ReentrantLock可以通過構造函數參數控制鎖是否公平。
5、ReentrantReadWriteLock讀寫鎖:是一種非排它鎖, 一般的鎖都是排他鎖,就是同一時刻只有一個線程可以訪問,比如Synchronized和Lock。讀寫鎖就多個線程可以同時獲取讀鎖讀資源,當有寫操作的時候,獲取寫鎖,寫操作之後的讀寫操作都將被阻塞,直到寫鎖釋放。讀寫鎖適合寫操作較多的場景,效率較高。
6、樂觀鎖與悲觀鎖:在Java中的實際應用類並不多,大多用在數據庫鎖上,可參看:http://blog.csdn.net/sdyy321/article/details/6183412
7、死鎖:是當兩個線程互相等待獲取對方的對象監視器時就會發生死鎖。一旦出現死鎖,整個程序既不會出現異常也不會有提示,但所有線程都處於阻塞狀態。死鎖一般出現於多個同步監視器的情況。
63.volatile與automicInteger是什麼?如何使用?
在併發環境中有三個因素需要慎重考量,原子性、可見性、有序性。
voatile 保證了有序性(防止指令衝排序)和變量的內存可見性(每次都強制取主存數據),每次取到volatile變量一定是最新的
volatile主要用於解決可見性,它修飾變量,相當於對當前語句前後加上了“內存柵欄”。使當前代碼之前的代碼不會被重排到當前代碼之後,當前代碼之後的指令不會被重排到當前代碼之前,一定程度保證了有序性。而volatile最主要的作用是使修改volatile修飾的變量值時會使所有線程中的緩存失效,並強制寫入公共主存,保證了各個線程的一致。可以看做是輕量級的Synchronized。詳情可參看:http://www.cnblogs.com/dolphin0520/p/3920373.html。
automicXXX主要用於解決原子性,有一個很經典的問題:i++是原子性的操作碼?答案是不是,它其實是兩步操作,一步是取i的值,一步是++。在取值之後如果有另外的線程去修改這個值,那麼當前線程的i值就是舊數據,會影響最後的運算結果。使用automicXXX就可以非阻塞、保證原子性的對數據進行增減操作。詳情可參看:http://ifeve.com/java-atomic/
volatile原理:
1.volatile可以保證線程可見性,且提供了一定的有序性,但無法保證原子性。
1.保證可見性,不保證原子性
2.禁止指令重排序
2.JVM底層,volatile採用 "內存屏障" 來實現
可見性實現:
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值
synchronize和鎖都可以保證可見性。
線程本身並不直接與主存進行數據交換,而是通過線程的工作內存來完成相應的操作 --- 線程間數據不可見的根本原因!!!
volatile實現可見性,直接從這方面入手,
1.修改volatile變量時,會強制將修改後的值刷新到主內存中
2.修改volatile變量後,會導致其他線程工作內存中對應的變量值失效,再讀取該變量值時,要重新從主內存中讀取
有序性實現:
關於重排序:
編譯器重排序:不改變單線程語義的前提下,可以重新安排語句的執行順序
處理器重排序:不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序
指令重排序對單線程無影響,但會影響多線程的正確性,
JVM如何禁止重排序?
happens-before 原則:保證程序的有序性
參考:http://www.cnblogs.com/chenssy/p/6393321.html
注:在此列舉的只是Java多線程最基礎的知識,也是面試官最常問到的,先打牢基礎,再去探討底層原理或者高級用法,除了這十個問題,在此再推薦一些其他的資料:
JVM底層又是如何實現synchronized的:http://www.open-open.com/lib/view/open1352431526366.html
Java線程池詳解:http://blog.csdn.net/zhangliangzi/article/details/52389766
Java線程池深度解析:http://www.cnblogs.com/dolphin0520/p/3932921.html
ConcurrentHashMap原理分析:http://www.cnblogs.com/ITtangtang/p/3948786.html
Java阻塞隊列詳解:http://ifeve.com/java-blocking-queue/
64.MyBatis中timestamp時間類型,在Spring MVC出參時無法轉爲正確的時間類型?
在xml中配置: javaType爲 java.sql.Timestamp
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" javaType="java.sql.Timestamp"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" javaType="java.sql.Timestamp"/>
65.Datatable自定義搜索
//自定義搜索,每次只能根據一個維度進行搜索(按渠道或產品類型)
$("#channel_select,#brand_select").change(function(){
var tsval = $(this).val()
table.search(tsval, false, false).draw();
});
66.synchronized和Lock的底層實現原理?
參考:http://www.open-open.com/lib/view/open1352431526366.html
鎖原理:http://blog.csdn.net/Luxia_24/article/details/52403033
synchronized 在軟件層面依賴JVM
Lock 在硬件層面依賴特殊的CPU指令 --- CAS + JNI調用CPU指令來實現
synchronized 可以吧任何一個非 null 對象作爲 "鎖",
作用於方法上時,鎖住的是對象實例this,
作用於靜態方法,鎖住的是對象對應的Class實例,因爲Class數據存儲在永久帶,因此靜態方法鎖相當於該類的全局鎖,
作用於某個對象實例,鎖住的是對應的代碼塊
HotSpot JVM中,鎖 --- 對象監視器(對象來監視線程的互斥) --- synchronized的實現原理
對象監視器,設置幾種狀態來區分請求的線程:
Contention Set: 所有請求鎖的線程,被首先放置到該競爭隊列 --- 先進後出的虛擬隊列,會被線程併發訪問
Entry Set:等待獲取鎖的線程(來自Contention Set)排隊隊列 --- 等待獲取對象鎖運行
Wait Set:獲取鎖後,調用wait()方法,被阻塞的線程隊列 --- 等待再次獲取對象鎖
OnDeck: 任何時刻最多只能有一個線程正在競爭鎖,該線程稱爲OnDeck
Owner: 獲得鎖的線程稱爲Owner
!Owner: 釋放鎖的線程
說明:
1.Entry Set 和Contention Set 同屬等待隊列,
2.Contention Set會被線程併發訪問,爲了降低對Contention Set隊尾的爭用(爲了減少加入與取出兩個線程對於contentionList的競爭),而建立Entry Set,如果Entry Set爲空,則從Contention Set隊尾取出節點
3.Owner線程在unlock時,會從Contention Set中遷移線程到Entry Set,並會指定Entry Set中的某個線程(一般爲Head)爲Read(OnDeck)線程
4.Owner線程並不是把鎖傳遞給OnDeck線程,只是把競爭鎖的權利交給OnDeck,OnDeck線程需要重新競爭鎖
5.OnDeck線程獲得鎖喉變爲Owner線程,無法獲得鎖的線程依然留在Entry Set中
6.如果Owner線程被wait()方法阻塞,則轉移後WaitSet中,如果某個時刻被notify/notifyAll喚醒,則再次轉移到EntrySet
線程的互斥,其實是線程對同一個對象的監視器monitor的操作:
每個對象都有一個監視器(monitor)!,當monitor被佔就會處於鎖定狀態,線程執行monitorentry指令時嘗試獲取monitor的所有權
1.如果monitor的進入數爲0,則線程進入monitor,然後將進入數設置爲1,線程即爲monitor的所有者
2.如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數 +1 --- 鎖可重入 --- ReentrantLock 和synchronized 都是 可重入鎖
3.其他線程已經佔用了monitor,則該線程進入阻塞狀態,知道monitor的進入數爲0,再嘗試獲取monitor的所有權
4.線程調用一次unlock()釋放鎖,monitor的進入數就 -1 (只有monitor進入數爲0,才能被其他線程搶佔)
5.一個線程獲取多少次鎖,就必須釋放多少次鎖,對於synchronized內置鎖 ,每一次進入和離開synchronized方法(代碼塊),就是一個完整的鎖獲取和釋放
sleep()不會釋放鎖,等待指定時間後繼續運行
wait()會釋放鎖,進入對象監視器的 Wait Set隊列,等待被喚醒,被喚醒後,需要重新獲取鎖
wait()和notify/notifyAll必須成對出現,而且必須放在synchronized中,
yield() 不會釋放鎖, 只是讓當前線程讓出CPU佔用權
Synchronized底層優化 --- 偏向鎖、輕量級鎖
參考:http://www.cnblogs.com/paddix/p/5405678.html
http://www.jianshu.com/p/5dbb07c8d5d5
Synchronized效率低的原因?
Synchronized是通過對象內部的對象監視器鎖(monitor)來實現的,monitor本質是依賴於底層的操作系統的Mutex Lock(互斥鎖)來實現,
操作系統實現線程間切換需要從用戶態轉到內核態(JVM轉到操作系統內核),這個成本非常高,狀態轉換需要相對比較長的時間,這就是爲什麼Synchronized效率低的原因
Synchronized底層優化:
1.鎖的4種狀態:
無鎖狀態、偏向鎖、輕量級鎖、重量級鎖
隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖(鎖升級只能從低到高升級,不會出現鎖的降級)
JDK1.6默認開啓偏向鎖和輕量級鎖,通過-XX:-UseBiasedLocking來禁用偏向鎖
鎖的狀態保存在對象的頭文件中
重量級鎖 --- 依賴於操作系統Mutex Lock所實現的鎖, 需要從JVM轉到操作系統內核,進行互斥操作
輕量級鎖 --- 並不是用來代替重量級鎖,本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗
輕量級鎖目的:
爲了在線程交替執行同步塊時提高性能 !!!
輕量級鎖適用場景:
線程交替執行同步塊的情況 ---- 鎖競爭不激烈的情況!
如果存在同一時間訪問同一個鎖,就會導致輕量級鎖升級爲重量級鎖
偏向鎖 --- 爲了在無多線程競爭的情況下,儘量減少不必要的輕量級鎖執行路徑
一旦線程第一次獲得了監視對象,之後讓監視對象 "偏向"這個線程,在該線程重複獲取鎖時,避免CAS操作
即:
設置一個變量,如果發現是true,無需再走加鎖、解鎖的流程!
偏向鎖目的:
解決無競爭下的鎖性能問題,在只有一個線程執行同步塊時,進一步提高性能
總結:
ynchronized的底層實現主要依靠Lock-Free的隊列,基本思路是自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量 !!!
67.Spring如何解決Bean的循環依賴? --- 只支持Singleton作用域的, setter方式的循環依賴!!!
參考:https://my.oschina.net/yibuliushen/blog/737640
http://blog.csdn.net/caomiao2006/article/details/46511123
Spring容器循環依賴包括構造器循環依賴和setter循環依賴
如果是構造器循環依賴,Spring容器將無法啓動,報循環依賴異常BeanCurrentlInCreationException
解決方式:
將構造器注入方式改爲屬性注入方式 --- setter
Spring 支持setter方法注入屬性方式的循環依賴
Spring中將循環依賴的處理分3中情況:
1.構造器循環依賴 --- 原理, Spring 不支持構造器方式的循環依賴
通過構造器注入構成的循環依賴是無法解決的,只能在容器啓動時拋出BeanCurrentlInCreationException異常 --- 表示循環依賴
Spring容器將每一個正在創建的Bean的標識符(id)放到 "當前創建bean池" 中,bean標識符在創建過程中將一直保持在這個池中,
如果在創建bean過程中,發現自己已經在 "當前創建bean池"裏時,將拋出 BeanCurrentlInCreationException異常,表示循環依賴,
而對於創建完畢的bean將從 "當前創建bean池"中清除掉
eg:
如在創建TestA類時,構造器需要TestB類,那將去創建TestB,在創建TestB類時又發現需要TestC類,則又去創建TestC,
最終在創建TestC時發現又需要TestA,從而形成一個環,沒辦法創建 --- 循環依賴,拋出 BeanCurrentlInCreationException異常
配置文件;
<bean id="testA" class="com.bean.TestA">
<constructor-arg index="0" ref="testB"/>
</bean>
<bean id="testB" class="com.bean.TestB">
<constructor-arg index="0" ref="testC"/>
</bean>
<bean id="testC" class="com.bean.TestC">
<constructor-arg index="0" ref="testA"/>
</bean>
測試用例:
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("test.xml");
} catch (Exception e) {
//因爲要在創建testC時拋出;
Throwable ee1 = e.getCause().getCause().getCause();
throw e1;
}
}
分析:
Spring容器創建"testA"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testB",並將"testA"標識符放到"當前創建bean池"。
Spring容器創建"testB"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testC",並將"testB"標識符放到"當前創建bean池"。
Spring容器創建"testC"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testA",並將"testC"標識符放到"當前創建Bean池"。
到此爲止Spring容器要去創建"testA"bean,發現該bean標識符在"當前創建bean池"中,因爲表示循環依賴,拋出BeanCurrentlyInCreationException。
說明:
Spring中bean默認是單例的,對於singleton作用於的Bean,可通過setAllowCircularReferences(false)來禁用循環引用
2.Setter循環依賴 --- 原理, Spring支持setter方式注入屬性的循環依賴!
setter注入方式構成的循環依賴,通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(eg:setter注入)的bean來完成的,
而且只能解決Singleton單例作用域的bean循環依賴,通過提前暴露一個單例工廠方法,從而使其他bean能引用到該bean(注意:此時僅僅只是生了一個bean,該bean還未調用其他方法,如setter注入)
對單例Bean循環依賴的處理:通過遞歸方法,找出當前Bean的所有依賴Bean,然後提前緩存起來
原理:
創建Bean A時,先通過無參構造器創建一個A實例,此時屬性都是空的,但對象引用已經創建創建出來,然後把Bean A的引用提前暴露出來,
然後setter B屬性時,創建B對象,此時同樣通過無參構造器,構造一個B對象的引用,並將B對象引用暴露出來。
接着B執行setter方法,去池中找到A(因爲此時,A已經暴露出來,有指向該對象的引用了),這樣依賴B就構造完成,也初始化完成,然後A接着初始化完成,
循環依賴就這麼解決了!!!
總結:
先創建對象引用,再通過setter()方式,給屬性賦值,層層創建對象 !!!
Bean A初始化時,先對其依賴B進行初始化,同時,通過默認無參構造器,生成自己的引用,而不調用其setter()方法,
當B對象創建時,如果還依賴C,則也通過無參構造器,生成B的引用,
C對象創建時,如果引用了A,則去對象池中查到A的引用,然後調用setter()方式,注入A,完成C對象的創建
C創建完成後,B使用setter()方式,注入C,完成B對象創建,
B對象場景完成後,A使用setter()方式,注入B,完成A對象創建,
最終,完成setter()方式的循環依賴!
如果循環依賴的都是單例對象(都是通過setter方式注入屬性的),那麼這個肯定沒問題,放心使用即可!!!
如果一個是單例,一個是原型,那麼一定要保證單例對象能提前暴露出來,纔可以正常注入屬性!!!
3.prototype範圍的依賴處理
對於"prototype"作用域bean,Spring容器無法完成依賴注入,因爲Spring容器不進行緩存"prototype"作用域的bean,因此無法提前暴露一個創建中的bean
這個spring也無能爲力,因爲是原型對象,A創建的時候不會提前暴露出來,所以,每次都是要創建,創建的時候,發現有相同的對象正在創建,同樣報錯,循環依賴錯誤
4.Spring創建Bean的源碼解釋:
1.創建Bean的入口
AbstractBeanFactory-->doGetBean()
Object sharedInstance = getSingleton(beanName); //從緩存中查找,或者如果當前創建池中有並且已經暴露出來了,就返回這個對象
2.創建單例Bean方法
DefaultSingletonBeanRegistry-->getSingleton(String beanName, ObjectFactory<?> singletonFactory)
3.創建真正對象
AbstractAutowireCapableBeanFactory-->doCreateBean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
} 注意這一步很關鍵,是調用構造方法創建一個實例對象,如果這個構造方法有參數,而且就是循環依賴的參數,那麼這個對象就無法創建了,
因爲到這裏對象沒有創建,也沒有暴露當前對象,如果是無參的構造方法,那麼就可以,先創建一個對象,儘管所有的屬性都爲空
68.Spring事務管理的原理?
參考:http://www.codeceo.com/article/spring-transactions.html
聲明式事務管理,在Service之上或Service的方法之上,添加 @Transactional註解
@Transactional如何工作?
Spring在啓動時,會去解析生成相關的Bean,這是會查看擁有相關注解的類和方法,
並且爲這些類和方法生成代理,並根據 @Transactional的相關參數進行相關配置注入,
這樣就在代理中把相關的事務處理掉了(開啓正常提交事務,異常回滾事務)
真正的數據庫層,事務提交和回滾是通過binlog和redo log實現的
Spring事務管理機制實現原理:
參考:
http://www.jianshu.com/p/4312162b1458
http://www.cnblogs.com/duanxz/p/3750845.html
http://www.92to.com/bangong/2016/11-05/12533010.html
在調用一個需要事務的組件時,管理器首先判斷當前調用(即:當前線程)有沒有事務,如果沒有事務則啓動一個事務,並把事務與當前線程綁定,
Spring使用TransactionSynchronizationManager的bindResource方法將當前線程與一個事務綁定,採用的方式就是ThreadLocal,
參考:DataSourceTransactionManager的啓動事務用的代碼 doBegin()
通過動態代理或AOP方式,對所有需要事務管理的Bean進行加載,生成代理對象,並根據配置在invoke()方法中對當前調用的方法名進行判定,
並在method.invoke()方法前後爲其加上合適的事務管理代碼,根據method.invoke()執行結果,正常提交事務,異常回滾事務
實現了EntityManager接口的持久化上下文代理,包含3個組成部分:
1.EntityManager Proxy本身
2.事務的切面
3.事務管理器
遇到過的問題:
參考:59,爲何聲明式事務沒有生效?
69.Spring如何處理高併發?高併發下,如何保證性能?
1.單例模式 + ThreadLocal
單例模式大大節省了對象的創建和銷燬,有利於性能提高,ThreadLocal用來保證線程安全性
Spring單例模式下,用ThreadLocal來切換不同線程直接的參數,用ThreadLocal是爲了保證線程安全,實際上,ThreadLocal的key就是當前線程的Thread實例
單例模式下,Spring把每個線程可能存在線程安全問題的參數值放進了ThreadLocal,雖然是一個實例,但在不同線程下的數據是相互隔離的,
因爲運行時創建和銷燬的bean大大減少了,所以大多數場景下,這種方式對內存資源的消耗較少,並且併發越高,優勢越明顯
2.ThreadLocal
相比同步機制,ThreadLocal則從另一個角度來解決多線程的併發訪問。ThreadLocal會爲每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。
因爲每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,
在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal !!!
3.Spring MVC在併發訪問時,是否會存在線程安全問題?
參考:http://blog.csdn.net/csluanbin/article/details/50930138
http://blog.csdn.net/a236209186/article/details/61460211
Struts2是基於類的攔截(每次處理請求,都會實例化一個對象Action,不會有線程安全問題)
Spring MVC 是基於方法的攔截,粒度更細,而Spring的Controller默認是Singleton的,即:每個request請求,系統都會用同一個Controller去處理,
Spring MVC和Servlet都是方法級別的線程安全,如果單例的Controller或Servlet中存在實例變量,都是線程不安全的,而Struts2確實是線程安全的
優點:
不用每次創建Controller,減少了對象創建和銷燬
缺點:
Controller是單例的,Controller裏面的變量線程不安全
解決方案:
1.在Controller中使用ThreadLocal變量,把不安全的變量封裝進ThreadLocal,使用ThreadLocal來保存類變量,將類變量保存在線程的變量域中,讓不同的請求隔離開來
2.聲明Controller爲原型 scope="prototype",每個請求都創建新的Controller
3.Controller中不使用實例變量
Spring MVC 如何保證request對象線程安全?
參考:http://blog.csdn.net/csluanbin/article/details/50930138
InvocationHandler接口:這是springmvc保證request對象線程安全的核心。
通過實現該接口,開發者能夠在Java對象方法執行時進行干預,搭配Threadlocal就能夠實現線程安全
問題:判斷一下程序是否線程安全?
@Controller
public class UserController{
@Autowired
private HttpSession session
@RequestMapping(xxxxxxx)
public void getUser{
session.get ...
session.set...
....
}
}
結論:
該程序是線程安全的
解析:
項目啓動和運行時,Controller對象中的HttpSession並不是HttpSession實例,而是一個代理,
是org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler代理了HttpSession ,可通過這個代碼求證:System.out.println(Proxy.getInvocationHandler(session));
只要當你真正調用HttpSession中的非java.lang.Object方法時纔會真真去調用被代理的HttpSession裏面的方法
說一下session.get ...過程:首先從對象工廠從Threadlocal中取得HttpSession實例,然後通過反射調用該實例的set方法
特別注意:
1.Spring 應該是在請求進來的時候ThreadLocal.set(Session),然後在請求的生命週期中都是一個Thread ,執行完後ThreadLocal.remove(Session)
2.一個請求使用一個ThreadLocal,綁定對應的HttpSession,所以是線程安全的
3.對於 "注入" 到Controller中的單例對象, 都是由Spring統一管理的,Spring對注入Controller的對象使用了ThreadLocal + 代理機制,保證了線程安全
4.但是,對於在Controller中直接定義的實例變量,是線程不安全的!!!
eg:
@RestController
@RequestMapping("/test1")
public class ControllerTest1 {
private int i = 0;
@GetMapping("/count")
public void test1(){
System.out.println(i++);
}
//驗證Controller中的實例變量,線程不安全
@GetMapping("/t1")
public void test3(){
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i=0;i<1000;i++) {
service.execute(new Runnable() {
@Override
public void run() {
HttpUtils.sendGet("http://localhost:8080/test1/count", null);
}
});
}
}
}
調用:http://localhost:8080/test1/t1 方法,使用多線程對i進行操作,發現i的結果不是999,證明Controller中的實例變量是線程不安全的!
結論:
1.對於單例的Controller,Service中定義的實例變量,都不是線程安全的!!!
2.儘量避免在Controller和Service中定義多線程共享的實例變量
3.Spring使用ThreadLocal + InvocationHandler(動態代理)提供了高併發訪問的性能
4.對於Controller和Service中的實例變量,多線程訪問時,需要加鎖處理 或 設置 scope = "prototype"爲每個請求創一個對象
5.對於 @Autowire注入的HttpServletRequest和HttpSession,Spring進行了特殊處理,不會有線程安全問題
70.ConcurrentLinkedQueue與BlockingQueue
ConcurrentLinkedQueue源碼解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
2類線程安全的隊列:
1.阻塞隊列 --- 阻塞算法 --- 隊列使用一個鎖(入隊和出隊用同一把鎖)或兩個鎖(入隊和出隊用不同的鎖)等方式實現
2.同步隊列 --- 非阻塞算法 --- 使用循環CAS方式實現
LinkedBlockingQueue 線程安全的阻塞隊列,實現了BlockingQueue接口,BlockingQueue繼承自java.util.Queue接口,
並在接口基礎上增加了take()和put()方法, 這2個方法正式隊列操作的阻塞版本
先進先出,可以指定容量,默認最大是Integer.MAX_VALUE;其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來!!!
put() 向隊列中放數據 take() 從隊列中取數據
ConcurrentLinkedQueue 是Queue的一個安全實現.Queue中元素按FIFO原則進行排序.採用CAS操作,來保證元素的一致性。
非阻塞方式實現的無界線程安全隊列 !!!
offer()添加元素, poll()獲取元素 isEmpty()判斷隊列是否爲空 (特別注意,不用size(),效率低,會遍歷隊列,儘量要避免用size而改用isEmpty())
採用CAS操作,允許多個線程併發執行,並不會因爲你加鎖而阻塞線程,使得併發性能更好!!!
使用:http://www.cnblogs.com/dmir/p/4907515.html
http://blog.csdn.net/sunxianghuang/article/details/52046150
ConcurrentLinkedQueue源碼解析:http://ifeve.com/concurrentlinkedqueue/
總結:
多數生產消費模型的首選數據結構就是隊列(先進先出)。Java提供的線程安全的Queue可以分爲阻塞隊列和非阻塞隊列,
其中阻塞隊列的典型例子是BlockingQueue,非阻塞隊列的典型例子是ConcurrentLinkedQueue,在實際應用中要根據實際需要選用阻塞隊列或者非阻塞隊列
Java中的7種阻塞隊列
阻塞隊列: --- 阻塞的是線程操作(拿和取元素)
常用於生產者和消費者場景,是生產者用來存放元素、消費者用來獲取元素的容器
put()阻塞:隊列滿時,阻塞插入元素的線程,直到隊列不滿
take()阻塞:隊列空時,阻塞獲取元素的線程,直到隊列不空
ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。 生產者和消費者直接傳遞數據,不對數據作緩存,生產者和消費者通過在隊列裏排隊的方式來阻塞和喚醒 --- 速度快
線程數少時,使用SynchronousQueue 速度更快!!!
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列
關於PriorityBlockingQueue優先級隊列:
傳統隊列都是FIFO,優先級隊列,通過Comparator或Comparable接口,根據元素的比較排序,確定出隊列的順序(優先級:即在compare()中指定的順序)
1.PriorityQueue是基於優先堆的一個無界隊列,元素默認按自認排序或通過提供的Comparator比較器在實例化隊列時排序
2.優先級隊列不允許空值(無法排序),而且不支持不可比較的對象
eg:
對於自定義類,優先級隊列要求,1:類實現Comparable接口 或 2:通過Comparator對對象排序,並且在排序時會按照優先級處理其中的元素。
3.優先級隊列的頭,是基於自然排序或Comparator排序的最小元素,
如果多個對象擁有同樣的順序,則坑你隨機取其中一個,當獲取隊列時,返回隊列的頭對象
4.優先隊列的大小是不受限制的,但在創建時可以指定初始大小。當我們向優先隊列增加元素的時候,隊列大小會自動增加
5.PriorityQueue是非線程安全的,所以Java提供了PriorityBlockingQueue(實現BlockingQueue接口)用於Java多線程環境。
ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue的區別和使用場景?
區別:
1.三者都是線程安全的
2.2個BlockingQueue是阻塞的,ConcurrentLinkedQueue是併發的
3.2個BlockingQueue使用鎖機制實現阻塞和線程安全(通過ReentrantLock + Condition阻塞容量爲空時的取操作和容量滿時的寫操作),
ConcurrentLinkedQueue使用cas算法保證線程安全
4.ArrayBlockingQueue使用一個鎖(lock + 2個Condition),而LinkedBlockingQueue使用2個鎖(鎖分離,取用takeLock + Condition,寫用putLock+Condition),所以LinkedBlockingQueue的吞吐量大,併發性能比Array高
LinkedBlockingQueue,對頭和尾採用不同的鎖,提高了吞吐量,適合 "消費者生產者" 模式
ArrayBlockingQueue, 數組實現,使用一把全局鎖並行對queue的讀寫操作,同時使用2個Condition阻塞容量爲空時的讀操作和容量滿時的寫操作
5.正因爲LinkedBlockingQueue使用兩個獨立的鎖控制數據同步,所以可以使存取兩種操作並行執行,從而提高併發效率。
而ArrayBlockingQueue使用一把鎖,造成在存取兩種操作爭搶一把鎖,而使得性能相對低下。LinkedBlockingQueue可以不設置隊列容量,默認爲Integer.MAX_VALUE.其容易造成內存溢出,一般要設置其值
使用場景:
阻塞隊列優點:
多線程操作不需要同步,
隊列會自動平衡負載,即:生產和消費兩邊,處理快了會被阻塞,減少兩邊的處理速度差距,
自動平衡負載特性,造成它能被用於多生產者隊列,隊列滿了就要阻塞等着,直到消費者使隊列不滿才能繼續生產
ConcurrentLinkedQueue:
允許多線程共享訪問一個集合,多用於消息隊列!!!
多消費者消費同一個 用 ConcurrentLinkedQueue:
BlockingQueueue:
多線程共享時阻塞,多用於任務隊列!!!
單消費者用 BlockingQueueue:
總結: 單個消費者用LinkedBlockignQueue, 多消費者用ConcurrentLinkedQueue !!!
單生產者,單消費者 用 LinkedBlockingqueue
多生產者,單消費者 用 LinkedBlockingqueue
單生產者 ,多消費者 用 ConcurrentLinkedQueue
多生產者 ,多消費者 用 ConcurrentLinkedQueue
71.Java多線程同步機制:3種類型
volatile 變量:輕量級多線程同步機制,不會引起上下文切換和線程調度。僅提供內存可見性保證,不提供原子性。 --- 只保證可見性,不保證原子性,不絕對線程安全!!!
CAS 原子指令:輕量級多線程同步機制,不會引起上下文切換和線程調度。它同時提供內存可見性和原子化更新保證。
內部鎖(synchronized)和顯式鎖(各種Lock):重量級多線程同步機制,可能會引起上下文切換和線程調度,它同時提供內存可見性和原子性。
參考:
非阻塞算法在併發容器中的實現:ConcurrentLinkedQueue https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
72.CAS在JDK中的實現
參考:http://blog.csdn.net/canot/article/details/50759424
1.Synchronized鎖機制存在的問題:
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險
優化:
偏向鎖、輕量級鎖、減小鎖粒度
2.鎖分類
悲觀鎖 --- 獨佔鎖 --- synchronized是獨佔鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖
樂觀鎖 --- 每次不加鎖,而是假設沒有衝突,而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止
3.CAS原理 --- 樂觀鎖 實現
CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做
CAS操作:
CAS有3個操作數:
V 內存值
A 舊的預期值
B 要修改的新值
當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做!!!
非阻塞算法: 一個線程的失敗或掛起,不應該影響其他線程的失敗或掛起的算法
CAS的硬件基礎和實現原理:
現代的CPU提供了特殊指令,可以自動更新共享數據,而且能夠檢測到其他線程的干擾,
而compareAndSet()就用這些代替了鎖定, compareAndSet利用JNI,藉助調用的C語言來完成CPU指令的操作
CAS實現原理:
Java 中通過unsafe類的額compareAndSwap()方法實現的
eg:
AtomicInteger 如何實現無鎖下的線程安全?
//在沒有鎖的機制下可能需要藉助volatile原語,保證線程間的數據是可見的(共享的)。這樣才獲取變量的值的時候才能直接讀取。
private volatile int value;
public final int get(){
return value;
}
//i++操作, 每次從內存中讀取數據,然後將此數據和 +1 後的結果進行CAS操作,如果成功就返回結果,否則重試直到成功爲止
public final int incrementAndGet(){
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
4.CAS的實現 compareAndSet() ---> unsafe.compareAndSwap()
compareAndSwap的4個參數:
參數1:需要改變的對象
參數2:偏移量
參數3:期待的值
參數4:更新之後的值
方法解析:
若調用該方法,value值與expect值相等,則將value修改爲update值,並返回true,
如果調用該方法,value值與expect值不相等,則不做任何操作,並返回false
eg:
參考java.util.concurrent.atomic.AtomicLong的實現 i++操作
//+1操作, 一直執行CAS,失敗則重試,直到操作成功,返回結果
public final long getAndIncrement() {
while (true) {
long current = get();
long next = current + 1;
//當+1操作成功的時候直接返回,退出此循環
if (compareAndSet(current, next))
return current;
}
}
//調用JNI實現CAS
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
5.CAS的缺點: 參考:http://www.cnblogs.com/zhuawang/p/4196904.html
http://blog.csdn.net/ghsau/article/details/17609747
1.存在ABA問題
ABA問題:
一個變量V,如果變量V初次讀取時是A,並在準備賦值時檢測到它還是A,
那能說明它的值沒有被其他線程修改過了嗎? --- 不一定
如果這段時間,它的值被其他線程改爲了B,然後又改回了A, CAS操作會誤認爲它沒有被修改過!!!
如何解決CAS中的ABA問題?
解決方案:
參考樂觀鎖實現機制,
在CAS操作是,帶上版本號或時間戳,每修改一次,版本號+1,
不但比較對象是否相等,還有比較版本號是否一致!!!
JDK的解決方案:
java併發包提供了一個帶有標記的原子引用類 "AtomicStampedReference",它可以通過控制變量值的版本來保證CAS的正確性!!!
參考:http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2.循環時間長,開銷大 --- 自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷
3.只能保證一個共性變量的原子操作
--- 對一個共享變成可以使用CAS進行原子操作,但是多個共享變量的原子操作就無法使用CAS,這個時候只能使用鎖
--- JDK1.5提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作
73.Java內存模型
參考:http://www.cnblogs.com/dongguacai/p/5970076.html
併發的2個關鍵問題:
1.線程間如何通信 --- 通信是指線程之間以何種機制來交換信息,在命令式編程中,通信機制有兩種:共享內存和消息傳遞;JAVA的併發採用的是共享內存,線程之間的通信總是隱式進行
2.線程間如何同步 --- 同步指程序中用於控制不同線程間操作發生相對順序的機制,在共享內存併發模型中,同步是顯式進行的
Java內存模型:
1.共享變量 --- 分配在堆內存中元素都是共享變量,eg:實例變量、靜態變量、數組和對象
2.非共享變量 --- 分配在棧上的都是非共享變量,主要指:局部變量,該變量爲線程私有,不會在線程間共享,也不存在內存可見性問題
如果線程A和線程B需要進行通信,必須經過以下2個過程:
主內存 <---> JVM堆
本地內存 <---> 線程工作內存
1.線程A把本次內存中修改過的共享變量屬性到主內存中
2.線程B到主內存中讀取線程A已經更新過的共享變量到B的線程本地內存中
內存間交互操作:
主要指工作內存(線程本地內存)與主內存之間的交互,即:一個變量如何從主內存拷貝到工作內存,如何從工作內存刷新到主內存的實現細節
Java內存模型定義了8種操作:
lock: 作用於主內存, 把一個變量標識爲某個線程獨佔狀態
unlock: 作用於主內存,把一個處於鎖定狀態的變量釋放,釋放後變量可被其他線程鎖定
read: 作用於主內存,把一個變量從主內存傳輸到工作內存,用於後面的load操作
load: 作用於工作內存,把read操作從主內存中得到的變量值放入工作內存的變量副本中
use: 作用於工作內存,把變量值傳遞給執行引擎,每當虛擬機需要使用變量的字節碼指令時,將會執行這個操作
assign: 作用於工作內存,把從執行引擎接收到的值,賦值給工作內存的變量,每當虛擬機遇到需要給該變量賦值的字節碼指令時執行這個操作
store: 作用於工作內存,把工作內存中的一個變量值傳到主內存,以便後續的write操作
write: 作用於主內存,把store操作從工作內存中獲取的值賦值給主存中的變量
8個操作的7個原則:
1、不允許read和load,store和write操作單獨出現。
2、不允許一個線程丟棄它最近的assign操作,即變量在工作內存中的更新需要同步到主內存中。
3、不允許線程無原因地(沒有發生過任何assign操作)把數據同步到主內存。
4、一個新的變量只能在主內存中產生,不能在工作內存中直接使用未被初始化的變量。
5、一個變量在同一時刻只能被一個線程lock,並且lock和unlock需要成對出現。
6、如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要執行load或者assgin操作。
7、對一個變量執行unclock之前,必須把此變量同步到主內存中。
74.Zookeeper搭建單機的僞分佈式集羣
參考:http://www.cnblogs.com/tenghoo/p/windows_zookeeper_pseudo_cluster.html
1.修改3個配置文件
修改confg下的zoo_sample.cfg 爲zoo.cfg,修改每個節點的clientPort, 並設置每個節點的dataDir和dataLogDir
Master主節點:
clientPort=2181
dataDir=/tmp/zookeeper/cluster/master/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/master/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave1節點:
clientPort=2182
dataDir=/tmp/zookeeper/cluster/slave1/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave1/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave2點:
clientPort=2183
dataDir=/tmp/zookeeper/cluster/slave2/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave2/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
2.添加data和log文件
在/tmp/zookeeper/cluster/目錄下,添加master/data、master/log文件,2個salve同理
3.創建myid
在/tmp/zookeeper/cluster/目錄下的 master/data、slave1/data、salve2/data下創建文件myid,並分別添加內容1,2,3
4.啓動3個Server
用jps查看 Zookeeper進程
75.ReentrantLock + Condition 鎖的複用,實現線程間通信
面試題:
子線程循環10次,接着主線程循環100,接着又到子線程循環10次,接着再回到主線程循環100,如此循環50次
分析:
1.因爲是2個線程的操作,需要線程間通信,wait、notify 或 ReentrantLock + Condition
2.Condition:定義鎖的條件隊列,當在線程中調用其await()方法時,就會將其存放到鎖的條件隊列中,
比內置鎖的優點:可以對一個鎖定義多個條件隊列
signal()方法喚醒隊列中線程,FIFO順序喚醒 !!! , 特別注意:Condition是按順序喚醒隊列中線程,不是隨機!!!
3.Condition,將Object監視器方法(wait、notify和notifyAll)分解成截然不同的對象,
以便通過將這些對象與任意Lock實現組合使用,爲每個對象提供多個等待Set(wait-set), !!!(最重要:Condition爲每個對象,通過多個等待隊列,按照FIFO先進先出的屬性喚醒線程)
Lock替代了synchronized方法和語句,Condition替代了Object監視器方法的使用
、
4.Condition的強大之處:它可以爲多個線程間建立不同的Condition
5.Condition,這個條件 可以爲鎖定的對象,創建多個線程等待隊列,實現鎖的多路複用!!!
static Lock locks = new ReentrantLock();
static Condition conditonMain = locks.newCondition();
static Condition conditonSun = locks.newCondition();
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(100);
for(int bb = 0; bb < 10 ; bb++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
locks.lock();
for(int i=0 ; i<10 ; i++){
String name = Thread.currentThread().getName();
System.err.println("子線程循環:"+name+"-----"+i);
}
conditonMain.signal();
conditonSun.await();
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
});
try {
locks.lock();
conditonSun.signal();
conditonMain.await();
for(int i = 0 ; i< 100 ; i++){
String name = Thread.currentThread().getName();
System.err.println("主線程循環:"+name+"-----"+i);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
}
關於條件變量Condition
參考:http://www.tuicool.com/articles/6vANna
--- 很大一個程度是爲了解決 Object.wait/notify/notifyAll難以使用的問題!
--- Condition(條件隊列或條件變量),主要用來等待某個條件的發生,
條件發生後,可以喚醒等待在該線程上的一個線程,或所有線程。
必須與Lock鎖一起協同工作!!!
--- 每個Lock可以有任意多個Condition對象,Condition與Lock是綁定的,
--- 如果Lock是公平鎖,線程按照FIFO的順序從Condition.await()中釋放,如果是非公平鎖,後續的鎖競爭就不保證FIFO順序了
await* 對應於 Object.wait , signal 對應於 Object.notify , signalAll 對應於 Object.notifyAll
--- 實例:concurrent包中阻塞隊列的實現,基本都是基於ReentrantLock + Condition的
76.Java併發編程的3個輔助類 CountDownLatch、CyclicBarrier、Semaphore
參考:http://blog.csdn.net/zhangliangzi/article/details/52526125
77.ConcurrentHashMap並不是絕對線程安全的?
ConcurrentHashMap原理: http://www.cnblogs.com/ITtangtang/p/3948786.html
78.HashMap、LinkedHashMap、TreeMap
HashMap: 用key做hash算法,然後將hash值映射到內存地址,直接取key所對應的的數據value
底層實現:數組(hash尋址)+鏈表(衝突解決),內存地址即:數組的下標索引
存入元素無序,不可重複
高性能保證: 只要HashCode() 和 Hash() 方法實現得足夠好,能夠儘可能地減少衝突的產生,那麼對 HashMap 的操作幾乎等價於對數組的隨機訪問操作
如果 HashCode() 或者 Hash() 方法實現較差,在大量衝突產生的情況下,HashMap 事實上就退化爲幾個鏈表,對 HashMap 的操作等價於遍歷鏈表,此時性能很差
LinkedHashMap: 繼承自HashMap,在其基礎上,內部增加了一個鏈表,用以存放元素的順序
底層實現:Hash表+鏈表, 依靠雙向鏈表保證了迭代屬性是插入的順序,維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序
存放元素有序,按存放順序
TreeMap: 通過紅黑樹實現的有序Key-Value集合,
元素有序,默認:按key的自然順序排序
可通過Comparator比較器,對key進行排序
//雙重檢查鎖的單例
public class Sig{
public volatile static sig = null;
private Sig(){}
public static getSig(){
if(sig==null){
synchorinzed(Sig.class){
if(sig==null){
sig = new Sig();
}
}
}
}
}
79.ThreadLocal源碼分析:
ThreadLocal類中有一個Map對象,這個Map以每個Thread對象爲key,保存了這個線程對應的局部變量值!!!!
實現方式:
http://blog.csdn.net/haoyifen/article/details/51126074
80.MySQL隔離級別和鎖的關係
1.事務的4個特徵
原子性:事務中包含的操作,看做一個邏輯單元,要麼全成功,要麼全失敗!
一致性:只有合法的數據可以被寫入數據庫,否則事務應該將其回滾到最初的狀態
隔離性:事務允許多個用戶對同一數據進行併發訪問,而不破壞數據的正確性和完整性,同時並行事務的修改必須與其他並行事務的修改相互獨立
持久性:事務結束後,處理結果必須能持久化
2.數據庫隔離級別
4個,由低到高:
√: 可能出現 ×: 不會出現
髒讀 不可重複讀 幻讀
Read uncommitted√ √ √
Read committed× √ √
Repeatable read× × √
Serializable × × ×
這4個級別可以逐個解決:髒讀、不可重複讀、幻讀這幾類問題
MySQL默認隔離級別: Repeatable Read, 可重複讀級別
http://lib.csdn.net/article/mysql/52873
81.非阻塞算法在併發容器中的實現
典型:
ConcurrentLinkedQueue
ConcurrentLinkedQueue源碼解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
1.Java多線程同步機制 --- 3種類型
1.volatile變量:輕量級多線程同步機制,不會引起上下文切換和線程調度。 提供內存可見性和禁止指令排序, 不保證原子性
2.CAS原子指令: 輕量級多線程同步機制,不會引起上下文切換和線程調度。 同時提供可見性和原子性更新保證
3.內部鎖(synchronized)和顯示鎖(各類Lock): 重量級多線程同步機制, 可能會引起上下文切換和線程調度,同時提供內存可見性和原子性
2.多核處理器系統對併發的支持
1.多處理系統提供了特殊的指令來管理對共享數據的併發訪問,這些指令能實現原子化的讀-改-寫操作
Intel和AMD多處理器系統支持 CAS(比較並交換)指令
2.JDK爲concurrent.atomic包中的原子類提供了compareAndSet()方法,
compareAndSet()方法使用機器級別的原子指令來原子化的更新值,
CAS 和 concurrent包中的原子類,是非阻塞算法實現併發容器的基礎
3.非阻塞算法
1.要想提高併發性,就應該儘量使串行部分達到最大程度的並行
即:最小化串行代碼的粒度是提高併發性能的關鍵 !!!
2.與鎖相比,非阻塞算法在更細粒度的層面(機器級別的原子指令)協同多線程間的競爭。
它使得多個線程在競爭相同資源時不會發生阻塞,它的併發性與鎖相比有了質的提高;同時也大大減少了線程調度的開銷
由於幾乎所有的同步原語都只能對單個變量進行操作,這個限制導致非阻塞算法的設計和實現非常複雜
4.非阻塞算法實現
1.基於非阻塞算法實現的併發容器
ConcurrentLinkedQueue: 基於鏈接節點的的無界線程安全隊列
SynchronousQueue: 無容量的阻塞隊列,使用雙重數據結構來實現非阻塞算法
Exchangeer: 能對元數據進行配對和交互的交換器,使用消除技術來實現非阻塞算法
ConcurrentSkipListMap 一個可以根據key進行排序的可伸縮的併發Map
2.ConcurrentLinkedQueue 非阻塞算法實現的併發安全隊列
1.使用CAS原子指令來處理對數據的併發訪問 --- 非阻塞算法得以實現的基礎
2.head/tail並非總是指向隊列的頭/尾節點,即:允許隊列處於不一致狀態,
這個特性把入隊/出隊時,原本需要一起原子化執行的兩個步驟分離開來,從而縮小了入隊/出隊時需要原子化更新值的範圍到唯一變量 --- 非阻塞算法得以實現的關鍵
3.由於隊列有時會處於不一致狀態,爲此,ConcurrentLinkedQueue使用三個不變式來維護阻塞算法的正確性
4.以批處理方式來更新head/tail,從整體上減少入隊/出隊操作的開銷
5.爲了利於垃圾收集,隊列使用特有的head更新機制;爲了確保從已刪除節點向後遍歷,可到達所有的費刪除節點,隊列使用了特有的向後推進策略
3.3個不變式
基本不變式:
在執行方法之前和之後,隊列必須要保持的不變式:
1.當入隊插入新節點之後,隊列中有一個next域爲null的(最後)節點
2.從head開始遍歷隊列,可以訪問所有item域不爲null的節點
head不變式和可變式:
在執行方法之前和之後,head必須保持的不變式:
1.所有"活着"的節點(指未刪除節點),都不能從head通過調用succ()方法遍歷可達
2.head不能爲null
3.head節點的next域不能引用到自身
在執行方法之前和之後,head的可變式
1.head節點的item域可能爲null,也可能不爲null
2.允許tail滯後於head,即:從head開始遍歷隊列,不一定能到達tail
tail不變式和可變式:
在執行方法之前和之後,tail必須保持的不變式:
1.通過tail調用succ()方法,最後節點總是可達的
2.tail不能爲null
在執行方法之前和之後,tail的可變式:
1.tail節點的item域可能爲null,也可能不爲null
2.允許tail滯後於head,即:從head開始遍歷隊列,不一定能到達tail
3.tail節點的next域可以引用到自身
82.JVM內存模型+GC
1.垃圾回收機制
1.引用計數 對象被引用一次,計數器+1,當不再引用時,引用計數器減一;當引用計數器爲0時,對象即可被回
2.根搜索法(可達性算法) 把對象的引用看做圖結構,由根節點集合出發,不可達的節點即可回收
根節點包含的5種元素:
Java棧中的對象引用
本地方法棧中的對象引用
運行時常量池的對象引用
方法區中靜態屬性的對象引用
所有Class對象
2.常用垃圾回收算法
1.標記清除: 2個階段, 第一階:標記可用對象, 第二階段:清除垃圾對象, 效率低,會產生內存碎片(不連續的內存空間),無法再次分配給較大對象
2.複製 : 用於新生代(適合回收生命週期短的對象),將內存分爲2個區域,新對象都分配在一個區域,回收時將可用對象連續複製到另一個區域,清空當前區域內存
不會產生內存碎片,但對象分配只有一個區域,內存利用率低
3.標記整理: 適用於老年代,類似標記清除,在其基礎上,將可用對象移動到一端連續的內存上,解決了內存碎片問題
4.分代算法: 基於分代特點
年輕代:複製算法
分爲較大的Eden區和2個相等的Survivor區(from、to),比例一般是 8:1:1,
對象分配在Eden區,當GC發生時(新生代GC叫Minor GC),將Eden區與from區中的可用對象複製到To區中,
from區和to區互換名稱,循環方法,直到發生如下2種請,對象進入老年代:
1.From區內的對象一大存活代數閾值(經歷一次Minor GC對象的存活代數+1,經歷GC的次數達到設定值), GC時進入To區,直接移動至老年代
2.在回收Eden和from區後,超出to區可容納範圍,則直接將存活對象移動至老年代
特別注意:
大的對象在分配時,直接進入老年代
老年代:標記整理算法
當老年代滿時,會觸發Full GC(新生代與老年代一起進行GC)
3.常用的垃圾回收器?特點?適合場景? 5類
1.Serial --- 絕對不推薦應用於服務器端!!!
年輕代複製算法、串行回收、Stop the world(GC時停止其他一切工作),適合單核CPU環境, 絕對不推薦應用於服務器端!!!
Serial 提供了老年代回收器 Serial Old,採用標記整理算法,特性與新生代一致, Serial + Serial Old適合客戶端場景
2.ParNew --- 推薦用於服務器場景!
相當於Serial的多線程版本,並回收,年輕代用複製算法和"Stop The World"機制,適合多核CPU、低延遲環境,推薦應用於服務器場景
3.Parallel --- 非常適合於服務器場景!!!
與ParNew類似,複製算法、並行回收、“Stop the world”機制,但是與ParNew不同,Parallel可以控制程序吞吐量大小,也被稱爲吞吐量優先的垃圾收集器
Parallel 也有老年代版本, Parallel Old ,採用標記整理算法
Parallel + Parallel Old 非常適合於服務器場景!!!
4.CMS --- 爲高併發、低延遲而生,採用標記-清除算法,會產生大量內存碎片,慎重使用!!!
與Parallel的高吞吐對應,CMS就是爲高併發、低延時而生的。採用標記-清除算法、並行回收、“Stop the world”
5.G1 --- JDK新加的GC收集器,不使用分代內存策略(年輕代+老年代)!!!,而是將內存分爲多個相等的區域
4.GC優化方案
1.不用顯示調用System.gc()
JVM接受這個消息後,並不是立即做垃圾回收,而只是對幾個垃圾回收算法做了加權,使垃圾回收操作容易發生,或提早發生,或回收較多而已。但即便這樣,很多情況下它會觸發Full GC,也即增加了間歇性停頓的次數
2.儘量減少臨時對象的使用
3.對象不用時,最好顯示置爲null
4.儘量使用StringBuffer代替String累加字符串
5.儘量使用基本類型,代替包裝類
6.儘量少用靜態對象變量
靜態變量屬於全局變量,不會被GC回收,會一直佔用內存
7.分散對象創建或刪除的時間
集中在短時間內創建新對象,特別是大對象,會導致突然需要大量內存,JVM在這種情況,只能進行Full GC,以回收內存或整合內存碎片,從而增加主GC頻率
從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閒空間必然減少,從而大大增加了下一次創建新對象時強制主GC的機會
5.可能導致內存泄露的情況
1.靜態集合類eg:HashMap、Vector等使用,容易出現內存泄露,這些靜態變量的聲明週期和程序一致,所有的對象Object也不能被釋放,他們將一直被集合所引用
2.各類連接:數據庫連接,網絡連接,IO連接,等沒有顯示調用close()關閉,不被GC回收導致內存泄露
3. 監聽器的使用,在釋放對象的同時,沒有相應刪除監聽器的時候,也可能導致內存泄露
83.Jenkins相關:
http://10.10.30.116:8888/jenkins/ 登錄名,密碼:admin admin
說明:
忘記Jenkins的admin密碼,解決:http://blog.csdn.net/qq105319914/article/details/52094463
1.配置文件路徑:/root/.jenkins/
2.初始密碼:/root/.jenkins/secrets/initialAdminPassword
3.安裝參考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
http://blog.csdn.net/tengdazhang770960436/article/details/53842604
4.Jenkins無法下載插件的解決方法:http://blog.csdn.net/russ44/article/details/52266953
常見的無法安裝插件下載地址:
http://updates.jenkins-ci.org/download/plugins/
步驟:
從http://10.10.30.116:8888/jenkins/updateCenter/ 中查看爲安裝成功的插件,進行手動安裝即可
安裝順序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
5.常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
2.Jenkins Blue Ocean使用
1.安裝
1.Jenkins版本在2.7.x以上
2.登錄Jenkins(admin,密碼:admin或111111),系統管理——>管理插件-->可選插件 -->搜索 Blue Ocean
3.點擊安裝後重啓,或直接安裝,安裝完畢後重啓Jenkins服務器
2.使用 參考:https://testerhome.com/topics/6700
1.點擊Open Blue Ocean進入Ocean UI界面
2.點擊 New Pipeline新建項目 或使用舊版界面創建項目
3.推薦使用舊版界面創建項目:
1.點擊新建 --> name: test1 ,選擇Pipeline OK保存
2.創建Pipeline Script腳本 即: Jenkinsfile 最重要!!!
node {
stage('Clone Code') { // for display purposes
// Get some code from a GitHub repository
git 'https://github.com/trautonen/coveralls-maven-plugin.git/'
}
stage('Code Analysis') {
sh "mvn clean"
sh "infer -- mvn compile"
}
stage('Testing') {
sh "mvn test"
junit 'target/surefire-reports/TEST-*.xml'
}
stage('Package') {
sh "'mvn' -Dmaven.test.skip=true package"
archive 'target/*.jar'
}
stage('Deploy') {
echo 'pipeline success'
}
}
3.常見異常
Cannot run program "nohup" (in directory "C
Windows環境,需要把Jenkinsfile文件中的sh改爲bat
4.新建時 沒有構建Maven項目項,安裝Maven項目插件:Maven Integration plugin
5.源碼沒有Git選項,安裝Git插件: Git Plugin
6.
3.常規的Jenkins項目自動化構建 --- 基於Git
0.先進行全局變量的配置
常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
1.構建自由風格的軟件項目
1.新建
2.輸入項目名稱 jay-plat,構建一個自由風格的軟件項目,點擊OK
3.添加項目描述
4.選擇源碼管理: Git
輸入 Repository URL :https://github.com/jayfeihe/jay-plat.git
5.構建-->增加構建步驟-->Execute Windows batch commnad :命令進入要構建項目
cd plat-config-server
mvn clean install
6.構建後操作-->增加構建後操作-->shell腳本,將構建後生產的jar或war放到指定服務器並啓動
7.點擊 Apply --> 點擊保存
8.立即構建
2.構建Maven+Git項目
參考:http://blog.csdn.net/pucao_cug/article/details/52373655
https://m.aliyun.com/yunqi/articles/64970
http://jdkleo.iteye.com/blog/2159844
1.新建
2.輸入項目名稱 server2 ---> 選擇 構建一個maven項目 -->點擊OK
3.General ---> 添加項目描述
4.源碼管理 --> Git
Repositories
Repository URL:https://github.com/jayfeihe/jay-plat.git
5.構建環境
1.Pre Steps 可以添加構建之前執行的命令
2.Build
Root POM : plat-config-server\pom.xml 這裏選擇以plat-config-server下的pom.xml構建項目 !!!
6.構建後操作-->增加構建後操作-->shell腳本,將構建後生產的jar或war放到指定服務器並啓動
7.點擊 Apply --> 點擊保存
8.立即構建
特別注意!!!
構建環境中,Build 如果clone下的Git項目中包含多個Pom子項目,
可通過ROOT POM 來指定具體構建哪一個Maven項目 !!!
3.構建Pipeline項目 , 需要寫基於Groovy的Jenkinsfile腳本
Jenkins Pipeline 參考:
http://www.07net01.com/2016/12/1731789.html
http://www.cnblogs.com/wzy5223/p/5554935.html
84.Redis實現分佈式鎖
*
* 基於Redis分佈式鎖實現的秒殺:http://blog.csdn.net/u010359884/article/details/50310387
*
* 在集羣等多服務器中經常使用到同步處理一下業務,這是普通的事務是滿足不了業務需求,需要分佈式鎖
*
* 分佈式鎖的常用幾種實現:
Redission實現的基於redis的分佈式鎖
* 0.數據庫樂觀鎖實現
* 1.Redis實現 --- 使用redis的setnx()、get()、getset()方法,用於分佈式鎖,解決死鎖問題
* 2.Zookeeper實現
* 參考:http://surlymo.iteye.com/blog/2082684
* http://www.jb51.net/article/103617.htm
* http://www.hollischuang.com/archives/1716?utm_source=tuicool&utm_medium=referral
* 1、實現原理:
基於zookeeper瞬時有序節點實現的分佈式鎖,其主要邏輯如下(該圖來自於IBM網站)。大致思想即爲:每個客戶端對某個功能加鎖時,在zookeeper上的與該功能對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。
2、優點
鎖安全性高,zk可持久化
3、缺點
性能開銷比較高。因爲其需要動態產生、銷燬瞬時節點來實現鎖功能。
4、實現
可以直接採用zookeeper第三方庫curator即可方便地實現分佈式鎖
*
* Redis實現分佈式鎖的原理:
* 1.通過setnx(lock_timeout)實現,如果設置了鎖返回1, 已經有值沒有設置成功返回0
* 2.死鎖問題:通過實踐來判斷是否過期,如果已經過期,獲取到過期時間get(lockKey),然後getset(lock_timeout)判斷是否和get相同,
* 相同則證明已經加鎖成功,因爲可能導致多線程同時執行getset(lock_timeout)方法,這可能導致多線程都只需getset後,對於判斷加鎖成功的線程,
* 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)過期時間,防止多個線程同時疊加時間,導致鎖時效時間翻倍
* 3.針對集羣服務器時間不一致問題,可以調用redis的time()獲取當前時間
85.深入理解JVM
1.對象相關
1.對象的創建:
JVM遇到new指令時,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已被加載、解析和初始化過,
如果沒有,則必須先執行類的加載過程
類加載檢查通過後,虛擬機將爲新生對象分配內存,對象所需內存的大小在類加載完成便可完全確定,爲對象分配空間的任務,等同於把一塊確定大小的內存,從Java堆中劃分出來,把地址賦給引用對象
如果JVM堆是絕對規整的,所有用過的內存都放一邊,沒用過的內存放在另一邊,中間放着一個指針作爲分界點的指示器,
內存分配就僅僅是把指針向空閒空間那邊移動與對象大小相等的距離 ---- 指針碰撞法
如果JVM堆式不規整的,使用的內存和未使用內存相互交錯,則JVM必須維護一個列表,記錄那些內存塊可用,
在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄 ----- 空閒列表法
JVM堆的規整與否,決定了何種分配方式,JVM堆是否規整取決於GC的是否帶有壓縮整理功能!!!
Serial、ParNew收集器,使用複製算法 --- 使用 指針碰撞法 分配對象內存
CMS收集器,使用標記清除算法 --- 使用 空閒列表法 分配對象內存
對象創建存在的問題及解決:
1.線程安全問題,eg:修改一個指針所指向位置, 併發時,可能出現正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存的情況
解決:
方案1。對分配內存空間的動作進行同步處理 --- JVM採用CAS失敗重試的樂觀鎖機制保證操作的原子性
方案2。把內存分配的動作按照線程劃分在不同的空間之中進行,
即:每個線程在Java堆中預選分配一小塊內存,稱爲本地線程分配緩存(TLAB),那個線程要分配內存,就在哪個線程的TLAB上分配,
只有TLAB用完並分配新的TLAB時,才需要同步鎖定。
JVM使用使用TLAB,通過 -XX:+-UseTLAB參數設定
對象內存分配完成後,JVM需要將分配到的內存空間都初始化爲零值
然後JVM對對象進行必要的設置(eg:對象所屬類實例,類的元信息,對象的哈希碼,對象的GC分代年齡等信息,這些信息存在對象頭中)
此時,對象init還沒執行
最後執行new指令,按照程序進行初始化,一個對象纔是創建成功!
2.對象內存佈局
1.對象頭
1.存儲對象自身運行時數據,eg:哈希碼、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向時間戳等
2.類型指針:即對象指向它的類元數據的指針, JVM依賴這個指針來確定對象是哪個類的實例
2.實例數據
1.程序中定義的各類字段屬性
2.從父類繼承的屬性
3.對齊填充
HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,
當時實例數據部分沒有對齊,就需要通過對齊填充來補全
3.對象的訪問定位
引用(棧) ----> 對象(堆),
對象訪問方式的2種實現:
1.句柄訪問
堆中劃分一塊內存作爲句柄池,reference引用中存儲的是對象的句柄地址,
句柄中包含了對象實例數據和類型數據各自的具體地址
以 句柄 爲中介,通過對象的句柄地址,查找對象的真實地址,從而找到該對象 !!!
---> 堆 實例池(對象實例數據)
棧引用 ---> 句柄
---> 方法區 對象類型數據
優點:
引用中存儲的是穩定的句柄地址,對象被移動(GC回收時,會移動對象到新內存地址)時,只會改變句柄中的對象指針,reference引用本身不用修改
2.直接訪問 --- HotSpot的默認對象訪問方式
棧中存放引用,引用中存儲的是對象的真實地址
優點:
速度更快,節省了一次指針定位的時間開銷,因爲對象的訪問在Java中非常頻繁,減小一次指針尋址開銷,可以提供性能
4.對象分配引起的內存問題
OutOfMemoryError
1.Java堆溢出
-Xmx -Xms 設置JVM的最大和最小堆內存
循環創建對象,而保持引用不回收,會導致堆內存溢出, Java Heap Space
2.棧溢出
-Xss 設置JVM的棧大小
1.線程請求的棧深度大於虛擬機所允許的最大深度 --- StackOverflowError --- 一般由遞歸或方法調用層次太深導致!!!
2.JVM在擴展棧時,無法申請到足夠的內存空間 --- OutOfMemoryError
3.方法區和運行時常量池溢出
-XX:PermSize 和 -XX:MaxPermSize 設置方法區大小
方法區存儲 Class相關信息(類名、訪問修飾符、常量池、字段描述、方法描述等),當方法區申請內存無法被滿足時 --- OutOfMemoryError PermGen Space
測試:
String.intern()方法:如果字符串常量池中已經包含一個等於String對象的字符串,則返回池中字符串,
否則,將此String對象包含的字符串添加到常量池中,並返回此String對象的引用
eg:
降低方法區大小,然後循環向常量池添加字符串,可能導致方法區內存溢出
2.GC相關
1.如何確定回收對象
1.引用計數法
給對象添加一個引用計數器,每當有一個地方引用它,計數+1, 引用失效,計數-1,任何時刻計數=0,則表示對象不再被使用,可回收
缺點:
無法解決循環依賴問題
2.可達性算法
通過一系列的 GC Roots 對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲"引用鏈",
當對象到GC Roots沒有任何引用鏈相連時,證明此對象是不可用的
可做GC Roots的對象:
JVM棧中引用的對象
方法區中靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧中引用的對象
3.關於對象引用:
強引用: 類似 Object obj = new Object()的對象,只要強引用還存在,GC用於不會回收掉被引用的對象
軟引用: JDK1.2+提供了SoftReference類來實現軟引用,軟引用關聯着對象,在系統發生內存溢出異常之前,會把這些對象進行二次回收,如果回收軟引用後,還沒有足夠空間,拋出內存溢出異常
弱引用: JDK1.2+提供了WeakReference類實現弱引用,被弱引用關聯的對象,只能生存到下一次垃圾回收發生之前,
GC工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象
虛引用:
2.垃圾收集算法
1.標記-清除
階段一:標記 --- 首先標記出所有需要回收的對象
階段二:清除 --- 標記完成後,統一回收所有被標記的對象
問題:
1.標記和清除效率都不高
2.標記清除後會產生大量不連續的內存碎片,空間碎片過多可能導致程序在分配較大對象時,無法找到足夠內存,而提前引發一次GC收集
2.複製算法 --- 適合新生代
爲了解決效率問題,複製算法將內存按容量劃分爲大小相等的2份,每次只使使用其中的一塊,
當這一塊的內存用完,就將還存活的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉
每次都對整個半區進行內存回收,內存分配時不用考慮內存碎片問題,只要移動堆頂指針,按順序分配內存即可
優點:
實現簡單,運行高效
問題:
將內存縮小了一般
適合: 新生代的GC對象回收
新生代對象,絕大多數生命週期比較短,將新生代分爲 較大的Eden和2個等大較小的from和to區,,
每次使用Eden和其中一個Survivor,當回收時,將Eden和Survivor中還存活的對象一次性複製到另外一個Survivor空間上,
最後清理掉Eden和剛纔使用過的Survivor空間
HotSpot默認 Eden和Survivor比例 8:1:1,即:每次新生代中可用內存空間爲新生代容量的 90%(eden+一個Survivor),只有10%的浪費
3.標記-整理 --- 適合老年代
老年代特點:生命週期長,被回收的少,不適合複製算法
標記過程與算法一相同,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都想一端移動,然後直接清理掉端邊界以外的內存
即:
把未被標記的對象,移動到一端,指針指到臨界端,清除臨界端指針以外的內存空間,避免了內存碎片,同空間浪費,但增加了存活對象的移動開銷
4.分代收集算法
新生代 --- 複製算法 --- 每次GC都有大批對象死去,只有少量存活
老年代 --- 標記整理 --- 對象存活率高,沒有額外空間擔保
3.垃圾收集器 --- 內存回收的具體實現
1.Serial 收集器 --- Client模式下,簡單高效 --- 不能用於服務器環境,GC會停止一切服務!!!
單線程收集器,新生代,GC時,必須暫停其他所有工作線程,直到收集結束
2.ParNew收集器 --- 適合新生代GC
Serial的多線程版本,配合 CMS (老年代收集器) 使用
3.Parallel收集器 --- 新生代收集器,使用複製算法的並行多線程收集器
Parallel目標:達到一個可控制的吞吐量
CMS目標:關注點是儘量縮短垃圾收集時,用戶線程的停頓時間
4.Serial Old --- Serial收集器的老年代版本,單線程,使用標記-整理算法
5.Parallel Old --- Parallel收集器的老年代版本,多線程,使用標記-整理算法, 只能配合新生代是Parallel使用
6.CMS --- 適合老年代,以獲取最短回收停頓時間爲目標的收集器 --- 最適合 服務器端使用!!!
--- 基於"標記清除算法"實現
GC時4個步驟:
1.初始標記
2.併發標記
3.重新標記
4.併發清除
解析:
初始標記和重新標記,需要 "停止一切",
初始標記:標記一下 GC Roots能直接關聯到的對象,速度很快
併發標記:進行 GC Roots 跟蹤的過程
重新標記:爲了修正併發標記期間,因用戶程序繼續運行而導致標記產生變動的那部分對象,這個節點停頓時間一般會比初始標記節點稍長,但遠比並發標記時間短
整個過程,併發標記和併發清除耗時最長, 但這兩個過程都是多線程的,可以與用戶線程一起工作,
總體上看,CMS收集器的內存回收過程和用戶一起併發執行
優點:
併發收集,低停頓,適合老年代!!!
缺點:
標記-清除算法 會產生內存碎片,需要特殊的參數定義:對內存碎片進行合併,以避免大對象直接分配到老年代
GC執行時,會和用戶線程搶CPU,對CPU資源非常敏感
7.G1 --- JDK1.7+提供,面向服務端應用的垃圾收集器!!!
特點:
併發並行,G1能充分利用多CPU,多核環境下的硬件優勢,使用多個CPU來縮短 "停止一切" 的停頓時間
空間整合,整體上看是基於 標記-整理 算法實現的, 但局部上看是基於 複製算法實現的,
不會產生內存碎片,收集後能提供規整的可用內存
注意:
1.Parallel用於新生代 ,老年代必須用Parallel Old
2.ParNew(新生代)+CMS(老年代),吞吐量和性能 > Parallel + Parallel Old
4.內存分配與回收策略
1.對象優先在Eden分配,當Eden區沒有足夠空間進行分配時,觸發一次Minor GC
2.大對象直接進入老年代,可通過參數設置大對象的大小 eg:很長的字符串或數組
3.長期存活的對象進入老年代,每次GC,存活的對象年齡+1,當對象年齡達到閾值,還沒被回收,就進入老年代
4.動態對象年齡判定
如果在Survivor空間中,相同年齡所有對象大小總和大於 Survivor空間的一半,年齡大於或等於概念了的對象可以直接進入老年代,而不必達到年齡設置的閾值
5.空間分配擔保
Minor GC(新生代GC)能回收的內存,儘量不要用Full GC(老年代GC)來做, 性能問題!!!
在發生Minor GC之前, JVM會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,
如果條件成立,則Minor GC可以確保是安全的,
如果不成立,JVM會查看HandlePromotionFailure設置值是否允許擔保失敗,
如果允許,JVM繼續檢查老年代最大可用的連續空間十分大於歷次晉升到老年代對象的平均大小,
如果大於,進行一次Minor GC
如果小於,進行一次Full GC
如果HandlePromotionFailure設置值不允許擔保失敗,則進行一次Full GC
3.JVM類加載機制
1.JVM類加載機制:
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可被虛擬機直接使用的Java類型的過程,叫JVM的類加載機制
Java中,類型的加載、連接、初始化都是在程序運行期間完成的,會稍微增加一些性能開銷,但提高了高度的靈活性,
Java動態擴展特性,就是依賴運行期動態加載和動態連接特定實現的
eg:
面向接口的程序,可以等到運行時再指定其實際實現類!!!
用戶可以通過Java預定義和自定義類加載器,讓一個本地程序可以運行從網絡或其他地方加載的二進制流作爲程序的一部分!!!
1.類加載時機
類的的生命週期
加載 ---> 連接(驗證 --> 準備 --> 解析) ---> 初始化 ---> 使用 ---> 卸載
5種情況立刻對類進行初始化(加載、連接在初始化之前執行)
1.遇到new、getstatic、putstatic、invokestatic時,如果類沒被初始化,則先觸發初始化
即:new關鍵字實例化對象、讀取或設置一個類的靜態字段(final變量,已經在編譯期把結果放入常量池),調用一個類的靜態方法
2.使用java.lang.reflect包的方法對類進行反射調用時,如果類沒被初始化,則先觸發初始化
3.初始化一個類時,其父類未初始化,先初始化其父類
4.JVM啓動時,用戶需要指定一個執行的主類(包含main()方法的類),虛擬機會先初始化這個類
5.使用JDK1.7+動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果,涉及到類的靜態變量或靜態方法時,先對涉及的類進行初始化
解析:
以上5種場景,稱爲對一個類進行主動引用!!!
除此之外,所有引用類的方式都不會觸發初始化,稱爲被動引用!!!
被動引用示例:
1.對於靜態字段,只有直接定義這個字段的類纔會被初始化,!!!
通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化!!!
2.常量會在編譯期存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量類的初始化!!!
eg1:
/**
*被動使用類字段演示一:
*通過子類引用父類的靜態字段,不會導致子類初始化
**/
public class SuperClass{
static{
System.out.println("SuperClass init!");
}
public static int value=123;
}
public class SubClass extends SuperClass{
static{
System.out.println("SubClass init!");
}
} /**
*非主動使用類字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(SubClass.value);
}}
結果:
SuperClass init!
123
分析:
對於靜態字段,只有直接定義這個字段的類纔會被初始化,!!!
通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化!!!
eg2:
/**
*常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
**/
public class ConstClass{
static{
System.out.println("ConstClass init!");
} public static final String HELLOWORLD="hello world";
} /**
*非主動使用類字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(ConstClass.HELLOWORLD);
}}
結果:
hello world
解析:
常量在編譯期會存入調用類的常量池中,本質上並未引用到定義常量的類,因此ConstClass不會被初始化!!!
2.類加載過程
1.加載:
完成3件事
1.通過類全限定名來獲取定義此類的二進制字節流 --- 全限定名獲取類的二進制字節流
2.將這個字節流所代表的靜態存儲結果轉化爲方法區的運行時數據結構 --- 把字節流,轉化爲方法區運行時數據結構
3.在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口 --- 生成類的Class對象
2.連接:
1.驗證:
確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並不會危害虛擬機自身的安全
1.文件格式驗證
2.元數據驗證
3.字節碼驗證
4.符號引用驗證
2.準備:
1.爲static類變量分配內存(存儲在方法區),並設初始零值,賦真實值實在初始化時,而實例變量則在對象創建時隨對象一起分配在Java堆中
2.爲final變量分配內存(方法區)並賦真實值
3.解析:
把常量池內的符號引用替換爲直接引用的過程
符號引用:以一組符號來描述所引用的目標,符號可以是任何形式的字面量,引用的目標並不一定已經加載到內存中
直接引用:可以是直接指向目標的指針,相對偏移量或一定能間接定位到目標的句柄。直接引用的目標一定以及在內存中存在
3.初始化:
類加載過程中,除了在加載階段用戶可以通過自定義類加載器參與外,其他動作都是由JVM主導和控制的,
初始化階段,才真正開始執行類中定義的Java代碼
1.爲static類變量賦初始值(準備階段賦零值,初始化階段賦代碼中定義的真實值)
2.初始化類變量和其他資源,主要是執行類構造器<cinit>()方法的過程
關於<cinit>()方法
1.<cinit>()主要由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合併產生的,
按照語句在源碼中出現的順序,靜態語句塊中只能訪問定義在其之前的變量,定義在其後的靜態語句塊變量,可以賦值,但不能訪問
2.<cinit>()方法不需要顯示調用父類構造器,JVM會保證在子類的<cinit>方法執行前,父類的<cinit>方法執行完畢,因此JVM中第一個執行的<cinit>()方法是Object
3.父類中定義的靜態語句(塊)先於子類中定義的靜態語句(塊)執行,
4.代碼執行順序
父靜態塊 <-- 子靜態塊 <-- 父普通代碼塊 <-- 父構造器 <-- 子普通代碼塊 <-- 子構造器
1.父類靜態成員和靜態初始化快,按在代碼中出現的順序依次執行。
2.子類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行。
3. 父類的實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
4.執行父類的構造方法。
5.子類實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
6.執行子類的構造方法。
5.類加載器
用於實現類的加載,對任意一個類,都需要由它的類加載器和這個類本身,一同確立其在JVM中的唯一性。
即:
比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義!!!
即使2個類來源於同一個Class文件,被同一個JVM加載,只要加載它們的類加載器不同,這兩個類就必定不相等!!!
這裏的相等,包括代表類Class對象的:
equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果 和 instanceof 做對象所屬關係的判定
1.雙親委派機制
啓動類加載器Bootstrap ClassLoader(C++實現),是JVM自身的一部分
其他類加載器,由Java實現,獨立於虛擬機外部,繼承自java.lang.ClassLoader
雙親委派機制工作過程:
如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,
每一個層次的類加載器都是如此,因此所有的類加載請求最終都應該傳送到頂層的啓動類加載器中,
只有當父加載器反饋自己無法完成這個加載請求時(它的搜索範圍中沒有找到所需的類),子加載器纔會嘗試自己去加載
啓動類加載器:加載 <JAVA_HOME>\lib\rt.jar裏所有的類, C++實現,不是ClassLoader子類
擴展類加載器:加載 <JAVA_HOME>\lib\ext目錄中的類
應用程序類加載器:加載 類路徑ClassPath上指定的類庫和目錄中的class --- 如果沒有自定義加載器,這個是應用程序默認的加載器!!!
類加載器層次關係:
啓動類加載器
Bootstrap ClassLoader
|
|
擴展類加載器
Extension ClassLoader
|
|
應用程序類加載器
Application ClassLoader
| |
| |
自定義類加載器1 自定義類加載器2
4.Java內存模型與線程
1.Java內存模型主要目標是定義程序中各個變量的訪問規則,
即:在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。
注意:
這裏的變量包括實例字段、靜態字段和構成數組對象的元素,
不包括局部變量和方法參數(它們是線程私有的,不會被共享,不存在競爭問題)
Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,
線程的工作內存中保存了該線程使用變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀取主內存中的變量
不同的線程之間也無法直接訪問對方工作內存中變量,線程間變量值的傳遞,需要通過主內存來完成。
一般:
主內存指的是堆內存,
工作內存優先存儲於寄存器或高速緩存總,因爲程序運行時主要訪問讀寫的是工作內存
2.內存間交互操作
主內存與工作內存交互協議:即:一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存指令的實現細節
Java內存模型定義了8種操作:
lock(鎖定): 作用於主內存的變量,把一個變量標識爲一條線程獨佔的狀態
unlock(解鎖): 作用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定
read(讀取): 作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存,以便隨後的load動作使用
load(載入): 作用於工作內存的變,它把read操作從主內存中得到的變量值放入工作內存的變量副本中
use(使用): 作用於工作內存的變量,它把工作變量內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時,執行這個操作
assign(賦值): 作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存變量,每當虛擬機遇到一個給變量賦值的字節碼指令是,執行此操作
store(存儲): 作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨後的write操作使用
write(寫入): 作用於主內存的變量,它把store操作從工作內存中得到的變量值,放入主內存變量中
解析:
如果要把一個變量從主內存複製到工作內存,就要順序執行read和load操作,
如果要把一個變量從工作內存複製到主內存,就要順序執行store和write操作,
特別注意:
Java內存模型只要求,以上2對操作必須按順序執行,而沒有保證是連續執行,
即:read和load,store和write直接是可插入其他指令的,
eg:
對主內存中變量a、b進行訪問時,一種可能出現順序是 read a、read b、load b、load a
注意:
Java內存模型規定了在執行以上8種操作時,必須滿足如下規則:
1.不允許read和load、store和write操作之一單獨出現,即:不允許一個變量從主內存讀去了但工作內存不接受,或從工作內存發起回寫而主內存不接受的情況出現
2.不允許一個線程丟棄它最近的assign操作,即:變量在工作內存中改變之後,必須把變化同步會主內存
3.不允許一個線程無原因的(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存
4.一個變量只能在主內存中 "誕生",不允許在工作內存中直接使用一個未被初始化(read和load)的變量,即:對一個變量實施use、store操作之前,必須先執行過了assign和load操作
5.一個變量在同一時刻只允許一條線程對其進行Lock操作,但lock操作可以被同一條線程重複執行,多次執行後,只有執行相同次數的unlock操作,變量纔會被解鎖
6.如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assgin操作,初始化變量的值
7.變量沒被lock,不允許對其執行unlock操作
8.對一個變量執行unlock之前,必須先把此變量同步會主內存中(執行store、write操作)
3.對volatile變量的特殊規則
1.保證可見性
對所有線程可見,一條線程修改了此變量,新值對其他線程是立即可知的,
強制所有線程,操作該變量時,重新重主內存中獲取,操作完之後,立即同步到主內存
2.禁止指令重排序
3.不保證原子性!!!
valatile用途:
1.用volatile控制併發
2.結合CAS實現的原子性,實現樂觀鎖
4.原子性、可見性、有序性
1.原子性保證
1.synchronized 原理:通過字節碼指令 monitorenter和monitorexit來保證線程安全
2.Lock 對鎖定部分的代碼,執行同步操作,同一時刻只允許一個線程操作鎖定的代碼
3.原子類AutoInteger
2.可見性 volatile
3.有序性 volatile、synchronized、先行發生原則
5.先行發生原則
判斷數據是否存在競爭、線程是否安全的主要依據,依靠這個原則,可以通過幾條規則,一攬子地解決併發環境下兩個操作之間是否可能存在衝突的所有問題!
定義:
先行發生是Java內存模型中定義的兩項操作之間的偏序關係,如果說操作A先行發生於操作B,
即:發生操作B之前,操作A產生的影響能被操作B觀察到 -- "影響" 包括 修改了內存中共享變量的值、發送了消息、調了方法等
5.線程安全
1.實現方法
1.synchronized互斥同步
synchronized關鍵字經過編譯後,會在同步塊的前後分表形成monitorentrer和monitorexit兩個字節碼指令,調用操作系統的mutex互斥鎖實現!!!
根據JVM虛擬機要求,在執行monitorenter指令是,首先要嘗試獲取對象的鎖,如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象的鎖,把鎖的計數器+1;
相應的在執行monitorexit指令時,將鎖計數器 -1,當計數器爲0時,鎖被釋放,如果獲取對象鎖失敗,則當前線程就要阻塞等待,知道對象鎖被另外一個線程釋放爲止
synchronzied是非公平鎖
2.Lock --- ReentrantLock可重入鎖
ReentrantLock增加了高級特性:
1.等待可中斷
當持有鎖的線程長期不釋放鎖時,正在等待的線程可以選擇放棄等待,該做其他事情,可中斷特性對處理執行時間非常長的同步塊很有幫助
2.可實現公平鎖
公平鎖;多個線程在等待同一個鎖時,必須按照申請鎖的時間順序依次獲得鎖,
ReentrantLock默認也是非公平的,但可通過構造器設置其爲公平鎖
3.鎖可以綁定多個Condition條件
一個ReentrantLock可以綁定多個Condition條件
而synchronized中,鎖對象的wait()、notify()、notifyAll()方法可以實現一個隱含條件
3.非阻塞同步
阻塞式同步,悲觀鎖 --- 互斥同步,主要問題是進行線程阻塞和喚醒所帶來的性能問題
總是認爲會出問題,無論共享數據是否真的會出現競爭,都要加鎖
非阻塞同步,樂觀鎖 --- 基於衝突檢測的樂觀併發策略
通常:先執行操作,如果沒有其他線程爭用共享數據,就操作成功,
如果共享數據有爭用,產生了衝突,就採取補償機制(不斷重試,直到成功爲止)。
樂觀併發策略的許多實現並不需要把線程掛起 --- 非阻塞同步
底層實現:
CAS ==> 依賴硬件指令,來保證原子性:
4.鎖優化
自旋鎖 JDK6後默認開啓
互斥同步時,對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操作都需要轉入內核態完成,這些操作給系統的併發性能帶來了很大壓力。
許多應用,共享數據的鎖狀態只會持續很短時間,爲了這段時間去掛起和恢復線程並不值得。
如果物理機有一個以上的處理器,能讓2個或以上的線程同時執行,就可以讓後面請求鎖的那個線程"稍等一下",但不放器處理器的執行時間,
看看持有鎖的線程是否很快就會釋放,爲了讓線程等待,只需要讓線程執行一個忙循環(自旋) --- 自旋鎖
自旋等待本身雖然避免了線程切換的開銷,但要佔用處理時間,鎖佔用時間短時,自旋等待效果好,如果所佔用時間長,自旋的線程只會白白消耗CPU資源
輕量級鎖
本意是在沒有多線程競爭前提下,減少傳統的重量級鎖適用操作系統互斥而產生的性能消耗,
偏向鎖
86.MySQL事務隔離級別和Spring的事務傳播機制?(同程面試)
事務的ACID特性:
原子性(要麼都做,要麼都不做)、
一致性(事務執行結果,必須是使數據庫從一個一致性狀態變到另一個一致性狀態)、
隔離性(事務之間不能干擾)、
永久性(事務一旦提交,數據的改變是永久的)
MySQL的4種隔離級別
--- 用來限定事務內外的哪些改變是可見的,哪些是不可見的。
--- 低級別的隔離級一般支持更高的併發處理,並擁有更低的系統開銷
Read Uncommited --- 讀取未提交內容
所有事務都可以看到其他未提交事務的執行結果。
本隔離級別很少用於實際應用,
讀取未提交的數據 --- 髒讀
Read Commited --- 讀取提交內容
大多數數據的默認隔離級別, 但非MySQL默認的!!!、
一個事務只能看見已經提交事務所做的改變,
這種隔離級別,支持不可重複讀,因爲同一事務的其他實例在該實例處理期間可能會有新的commit,所有同一select可能返回不同結果
Repeated Read --- 可重讀
MySQL的默認事務隔離級別!!!
它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。
幻讀問題:幻讀指當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的 "幻影" 行。
InnoDB通過多版本併發控制機制解決了該問題!!!
Serizliable --- 可串行化
最高的隔離級別,它通過強事務排序,使之不可能相互衝突,從而解決幻讀問題,
即:它在每個讀的數據行上加上共享鎖!!!
該隔離級別,可能導致大量的超時現象和鎖競爭!!!
4種隔離級別是通過採取不同的鎖類型來實現的,若讀完的是同一個數據,就容易發生以下問題:
髒讀:
某個事物已更新一份數據,另一個事務在此時讀取了同一份數據,但由於某些原因,前一個事務回滾了,則後一個事務所讀取的數據是髒數據!!!
不可重複讀:
在一個事務的兩次查詢之中數據不一致,可能是兩次查詢過程中,插入了一個事務更新操作
幻讀:
在一個事務的兩次查詢彙總數據筆數不一致,
eg:一個事務查詢了幾列數據,而另一個事務卻在此時插入了新的幾列數據,
先去的事務在接下來的查詢中,就會發現有借了數據是它之前所沒有的
4種隔離級別與可能產生的問題如下:
隔離解別 髒讀不可重複讀 幻讀
Read UncommittedY Y Y
Read CommittedN Y Y
Repeatable(default)N N Y
Serializable N N N
Spring事務傳播機制
事務傳播行爲類型 說明
PROPAGATION_REQUIRED如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是 最常見的選擇。
PROPAGATION_SUPPORTS支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY使用當前的事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED 類似的操作。
87.Spring中的bean,在初始化之後或銷燬之前執行指定功能,幾種方式? (同程面試)
3種方式:
1.通過註解 @PostConstruct 和 @PreDestory 方法,實現初始化和銷燬bean之前的操作
eg:
public class DataInitializer{
@PostConstruct
public void initMethod() throws Exception {
System.out.println("initMethod 被執行");
}
@PreDestroy
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被執行");
}
}
2.在xml中定義init-method和destory-method方法
1.Bean中定義方法
public class DataInitializer{
public void initMethod() throws Exception {
System.out.println("initMethod 被執行");
}
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被執行");
}
}
2.xml中對bean配置初始化init-method和銷燬前destory-method執行的方法
<bean id="dataInitializer" class="com.jay.demo.DataInitializer"
init-method="initMethod" destory-method="destroyMethod"/>
3.通過Bean實現InitializingBean和DisposableBean接口
public class DataInitializer implements InitializerBean, DisposableBean{
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Bean初始化後,afterPropertiesSet被執行");
}
@Override
public void destory() throws Exception{
System.out.println("Bean被銷燬之前, destory被執行");
}
}
分析:
1.方式1和方式2,本質是一個,只是一個註解方式實現,一個xml配置方式實現
2.Bean實例化執行的順序:
初始化:
Constructor ---> @PostConstruct ---> InitializingBean(afterPropertiesSet方法) ---> init-method(xml配置)
銷燬:
@PreDestory ---> DisposableBean(destory方法) ---> destory-method(xml配置)
執行順序源碼分析:
3.實現InitializingBean接口是直接調用afterPropertiesSet方法,比通過反射調用init-method指定方法效率相對來說要高點,但 init-method方式,消除了對Spring的依賴!
4.如果調用afterPropertiesSet()方法時出錯,則不調用init-method指定的方法
5.Spring容器中Bean實例完整的生命週期:
開始 -->創建實例
--> 注入依賴關係
--> 調用afterPropertiesSet()方法
--> 調用init-method方法
--> 對外提供服務
--> 調用destory()方法
--> 調用destory-method指定的方法
--> 結束
注意:
當Bean實現ApplicationAware、BeanNameAware接口後,Spring容器會在該Bean初始化完成後,
即:init-method指定方法(如果有)執行之後,再來回調setApplicationContext(ApplicationContext context)和setBeanName(String beanName)方法
88.在項目啓動前執行預處理方法的幾種方式?
1.Web項目Servlet啓動、執行、銷燬的全過程
1.讀取配置信息
啓動Web項目時,容器(Tomcat)會讀取配置文件(web.xml)中的<listener/>和<context-param/>標籤
2.創建監聽類
由容器創建<listener/>監聽類實例,用於監聽ServletContext、HttpSession的聲明週期及書序變更
3.創建上下文
由容器創建ServletContext上下文實例,這時監聽類實例會調用其contextInitialized(ServletContextEvent args)方法,並傳入讀取的<context-parm/>鍵值對,
在該方法中可以讀取、操作ServletContext鍵值對
eg:
ServletContext = ServletContextEvent.getServletContext();
Value = ServletContext.getInitParameter(Key);
4.容器調用繼承HttpServlet接口的類的構造方法,創建Servlet
5.創建ServletConfig
容器創建ServletConfig對象(包含Servlet初始化信息),並將ServletConfig對象與ServletContext對象關聯
6.初始化Servlet
容器調用Servlet對象的初始化init(ServletConfig config)方法,並傳入ServletConfig參數初始化Servlet
7.接收請求
當容器接收到Servlet請求時,容器創建ServletRequest和ServletResponse對象,然後調用service()方法,並傳入參數,進行處理
8.響應請求
Service()方法通過ServletRequest對象獲得請求信息,並處理該請求,再通過ServletResponse對象生成響應結果
9.銷燬Servlet
當Web應用被終止時,Servlet容器會先調用Web應用中所有Servlet對象的destory()方法,然後再銷燬Servlet對象。
此外容器還會銷燬與Servlet對象關聯的ServletConfig對象。在destroy()方法的實現中,可以釋放servlet所佔用的資源。如關閉文件輸入輸出流,關閉與數據庫的連接
1.在項目啓動時,執行某個方法的5種方式
1.實現Servlet監聽器接口--- ServletContextListener
eg:
1.定義監聽器
public class InitListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent context) {
}
@Override
public void contextInitialized(ServletContextEvent context) {
// 上下文初始化執行
System.out.println("================>[ServletContextListener]自動加載啓動開始.");
SpringUtil.getInstance().setContext( WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()));
}
}
2.web.xml中配置該監聽器
<listener>
<listener-class>com.test.init.InitListener</listener-class>
</listener>
2.實現Servlet的過濾器接口--- Filter
1.自定義過濾器,重寫 init()方法
2.在web.xml中配置過濾器
3.編寫Servlet,在web.xml中配置容器啓動後執行即可 --- HttpServlet
1.自定義Servlet,重寫 init()方法
2.web.xml中配置Servlet
4.使用Spring IOC管理Bean,可以指定init-method,在bean加載成後,立即執行某個方法
5.使用Spring IOC管理Bean,可以實現Spring Bean後置處理器接口 --- BeanFactoryPostProcessor --- 表示該Bean加載完成後,執行一些自定義事件
public class KeyWordInit implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
System.out.println("================>[BeanFactoryPostProcessor]自動加載啓動開始.");
ShopService shopService = factory.getBean("shopService", ShopService.class);
List<map<string, object="">> shopList = shopService.findAllShop();
System.out.println("================>" + shopList);
System.out.println("================>[BeanFactoryPostProcessor]自動加載啓動結束.");
}
}
5種方式的執行順序:
4===>5===>1===>2===>3
即:
指定init-method的Bean開始執行。
接着實現spring的Bean後置處理器開始執行
然後是Servlet的監聽器執行
再接下來是Servlet的過濾器執行
最後纔是Servlet執行
特別注意:
Spring提供的項目啓動時執行的2種方法:
1.ApplicationListener<ContextRefreshedEvent> --- Spring容器初始化完成後,執行 onApplicationEvent()方法
可以使用Spring的 ApplicationListener,也可以完成項目啓動時,Spring容器初始化完成後,執行方法!!!
參考:
http://blog.csdn.net/ilovejava_2010/article/details/7953419
@Service
public class StartGateServiceData implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在web項目中(spring mvc),系統會存在兩個容器,一個是root application context
// ,另一個就是我們自己的 projectName-servlet context(作爲root application context的子容器)。
// 這種情況下,就會造成onApplicationEvent方法被執行兩次。爲了避免這個問題,我們可以只在root
// application context初始化完成後調用邏輯代碼,其他的容器的初始化完成,則不做任何處理。
if (event.getApplicationContext().getParent() == null) {
//需要執行的邏輯代碼,當spring容器初始化完成後就會執行該方法。
}
}
}
2.自定義類,實現InitializingBean接口,然後交由Spring容器管理
eg:
需求:
權限管理系統,在項目啓動時,把用戶權限和資源信息加載到內存
分析:
給web容器添加一個Listener類,在容器啓動的時候執行Listener的“初始化”方法,在這個初始化方法中執行查詢數據庫的所有操作,然後將數據庫中的信息緩存起來
問題:
查詢DB的Service和dao都是Spring IOC控制,Listener類只是在系統啓動時會執行初始化方法,
但此時Service還沒被Spring管理(Spring容器還沒創建),即:Service和Dao無法訪問數據庫
解決:
自定義類,實現InitializingBean接口,然後交由Spring管理
Spring容器啓動後,加載該類,自動執行其中的方法
參考:http://hbiao68.iteye.com/blog/2026210
89.線程安全的集合類
1.Vector、HashTable
2.Collections.synchronizedXxx(List、Set、Map)
3.併發包中
CopyOnWriteArrayList --- 其中的set、add、remove等方法,都使用了ReentrantLock來加鎖和解鎖,
當增加元素時,使用Arrays.copyOf()來拷貝副本,在副本上增加元素,然後改變原引用指向副本
CopyOnWriteArraySet --- 使用了
ConcurrentHashMap --- 允許多個修改操作併發進行,關鍵在於使用了鎖分離技術。
它使用多個鎖來控制對hash表的不同部分(Segment)進行修改,
每個段其實就是一個小的hashtable,它們都有自己的鎖(由Segment繼承ReentrantLock實現),
只要多個修改操作發生在不同的Segment段上,它們就能併發的進行
JDK1.8後,HashMap和ConcurrentHashMap的每個Segment都是通過紅黑樹實現!!!
各類BlockingQueue(ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue(基於優先級隊列實現)、SynchronousQueue)
ConcurrentLinkedQueue
ConcurrentSkipListMap
實現參考:http://www.2cto.com/kf/201212/175026.html
ConcurrentSkipListMap 繼承 AbstractMap 實現ConcurrentMap接口
1.SkipList 跳錶
Skip List是一種隨機化的數據結構,基於並聯的鏈表,其效率可以比擬二叉查找樹(O(log n)),
跳躍列表是對有序的鏈表增加上附加的前進鏈接,增加是以隨機化方式進行的,所以在列表中的查找可以快速的跳過部分列表,
所有操作都是以對數隨機化的時間進行。
Skip List可以很好解決有序鏈表查找特定值的困難
Skip List(跳錶)是一種可以代替平衡樹的數據結構,默認按key值升序。
SkipList讓已排序的數據分佈在多層鏈表中,以0-1隨機數決定一個數據的向上攀升與否,通過 "空間來換取時間"的一個算法,
在每個節點中增加了向前的指針,在插入、刪除、查找時可忽略一些不可能設計到的節點,從而提高了效率
2.ConcurrentSkipListMap
提供了一種線程安全的併發訪問的排序映射表。
內部是SkipList(跳錶)結構實現,理論上能在O(log n)時間內完成查找、插入、刪除操作。
調用ConcurrentSkipListMap的size時,由於多線程可以同時對映射表進行操作,所以映射表需要遍歷整個鏈表才能返回元素個數
3.ConcurrentSkipListMap有幾個ConcurrentHashMap不可比擬的優點:
1.ConcurrentSkipListMap的key是有序的
2.ConcurrentSkipListMap支持更高的併發,存取時間是 O(log N),和線程數幾乎無關,
即:在數據量一定的情況下,併發的線程越多,ConcurrentSkipListMap越能體現出其優勢
4.使用建議:
1.非多線程下,如果要保證有序,儘量使用TreeMap, 對於併發性相對較低的程序,可用Collections.synchronizedSortedMap()將TreeMap進行線程安全同步。
2.對於多線程高併發程序,如果需要對Map的key進行排序時,應當使用ConcurrentSkipListMap,能夠提供更高的併發度
90.關於幾個常用的Map
Map接口的4個實現類
HashMap:繼承自Dictionary類,根據hashCode存儲數據,訪問速度快,無序(遍歷時,取得數據的順序是完全隨機的),最多隻允許一條記錄的key爲null,允許多個value爲null,線程不安全
HashTable:繼承自Dictionary類,不允許記錄的key或value爲null,線程安全,寫入慢
LinkedHashMap:HashMap的子類,保存了記錄插入順序,用Iterator遍歷時,按插入Map順序取值
TreeMap:實現SortMap接口,可根據Key進行自定義排序,通過比較器Comparator或key實現comparable接口來指定順序
HashMap中hash衝突的解決:
拉鍊法 --- 系統總是將新添加的 Entry 對象放入 table 數組的 bucketIndex 索引處——如果 bucketIndex 索引處已經有了一個 Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象(產生一個 Entry 鏈),如果 bucketIndex 索引處沒有 Entry 對象,也就是上面程序代碼的 e 變量是 null,也就是新放入的 Entry 對象指向 null,也就是沒有產生 Entry 鏈
hash衝突的其他解決方法:
1.開放地址法 --- 線程探測法
即:當發生地址衝突時,安裝某種方法,繼續探測哈希表中的其他存儲單元,直到找到空位置爲止,將該元素放入該槽中
91.JVM常用的分析命令
1.jstat
--- 查看JVM的堆棧信息,能夠查看eden、Survivor、old、perm等內存的capacity、utility信息,對系統是否有內存泄露以及參數設置是否合理有不錯的意義
eg:
jstat -gc pid顯示gc信息、查看gc次數及時間
jstat -gccapacity pid顯示VM內存中三代(young、old、perm)對象的使用和佔用大小
jstat -gcutil pid統計gc信息
jstat -gcnew pid年輕代對象信息
jstat -class pid 顯示加載class的數量,及所佔空間等信息
2.jstack
--- 查看JVM當前的thread dump,可以看到當前JVM中的線程狀況,對於查找阻塞的線程有幫助
--- 用於打印給定的Java進程ID或遠程調用服務的Java堆棧信息
3.jmap
--- 查看JVM當前的heap dump堆內存信息,可以看出當前JVM中各種對象的數量,所佔空間等
項目中遇到的Bug及解決?
1.線程池問題
newCachedThreadPool(),吞吐量大,阻塞隊列使用SynchronousBlockQueue吳榮林的阻塞隊列,put必須等待take,同樣take必須等待put,
適合執行耗時較短的多線程,線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程
如果瞬間進入的線程過多,而且線程耗時長,將會產生上千個線程,導致內存溢出,程序崩潰
2.Future問題,
Future + Callable可以定義新的線程,只有future.get()時,纔會等待線程執行接收,如果沒有調用get()方式,當前線程不會阻塞
如果有多個Future + Callable線程,爲了應用多線程提高併發效率,可以把提交後返回的Future放到一個List<Futrue>中,
遍歷該list,同時調用future.get(), 可以保證每個線程執行完後,才執行主程序中的代碼
3.Spring事務問題,註解式事務不生效 參考 :59
4.MyBatis原理簡述:
MyBatis程序根據XML配置文件創建SqlSessionFactory,SqlSessionFactory再根據配置(來源:配置文件和Java註解)獲取一個SqlSession。
SqlSession包含了執行SQL所需的所有方法,可以通過SqlSession實例直接運行映射的SQL語句,完成對數據的增刪改查和事務提交,用完後關閉SqlSession
http://www.importnew.com/22056.html
http://marlonyao.iteye.com/blog/344876
http://cache.baiducontent.com/c?m=9f65cb4a8c8507ed4fece763104687270e54f72864879b5468d4e419ce3b46454762e0b82c3510738983233915ea141cbcff2102471453b08cb98b5daec885295f9f564267688c5613a30edfbd5151c337e150fed96af0bb806ac0ea81c4de2444bb52120d84e7fa291762cc78f1642692d78e3f15&p=9a769a4786cc41af59a6e6285c07cf&newp=8067c64ad4934eaf58eccb3861648b231610db2151d6d0106b82c825d7331b001c3bbfb423241200d7c07a6c00ac4e56ecf73d78350025a3dda5c91d9fb4c57479&user=baidu&fm=sc&query=%BB%A5%C1%AA%CD%F8%BD%F0%C8%DAjava%C3%E6%CA%D4%CC%E2&qid=d6cd91620001a7e2&p1=1
http://blog.csdn.net/luanlouis/article/details/40422941
http://ms.csdn.net/geek/79519
http://blog.csdn.net/u010154380/article/details/53557507
http://blog.csdn.net/qq_35124535/article/details/64129964
http://blog.csdn.net/pistolove/article/details/46753275
Redis併發注意點:http://qsalg.com/?p=423
RPC原理:http://www.cnblogs.com/LBSer/p/4853234.html
Java相關原理和深入學習:http://wiki.jikexueyuan.com/list/java/
微服務的一種開源實現方式——dubbo+zookeeper http://blog.csdn.net/zhdd99/article/details/52263609
RPC框架:
http://www.cnblogs.com/luxiaoxun/p/5272384.html
https://my.oschina.net/huangyong/blog/361751
JDK集合框架原理:http://yikun.github.io/tags/Java/
牛逼的博客:http://www.cnblogs.com/LBSer/p/4753112.html
編程狂人週刊:http://www.tuicool.com/mags
碼農週刊:http://weekly.manong.io/issues/
http://blog.csdn.net/zhangliangzi/article/details/52526125
成爲一個Java的架構師要學習哪些知識? 搜索知乎
soa和微服務架構的區別?搜索知乎
美團點評技術: http://tech.meituan.com/
http://blog.csdn.net/zhangliangzi/article/details/50995326
Java精華:
http://blog.csdn.net/kobejayandy/article/category/1216487
Java併發集合的實現原理:http://www.cnblogs.com/luxiaoxun/p/4638748.html
Java併發原理:http://blog.csdn.net/column/details/14531.html
數據庫索引原理和B+樹算法分析!!!數據庫索引的實現(B+樹介紹、和B樹、R樹區別)
分佈式的冪等性和分佈式CAP原理!!!
多讀書:
JVM公衆號推薦:你假笨
《core Java》多線程部分 《effective java》《深入理解Java虛擬機》《Java併發編程實戰》
《Java多線程編程核心技術》《實戰java高併發程序設計》《Java併發編程實戰》《jcip》《深入理解Java虛擬機:JVM高級特性與最佳實踐》《深入分析Java Web技術內幕》《Spring源碼深度解析》
《大型網站技術架構 核心原理與案例分析》《大型網站系統與Java中間件實踐》《高性能MySQL》
《重構 改善既有代碼的設計》《企業應用架構模式》《從Paxos到ZooKeeper 分佈式一致性原理與實踐》
《分佈式系統常用技術及案例分析》《精通Spring 4.x》
《高性能網站構建實戰》《實用負載均衡技術:網站性能優化攻略》
書籍推薦:http://blog.jobbole.com/106093/
http://www.importnew.com/7099.html
阿里面試:http://www.importnew.com/22056.html
http://www.importnew.com/22637.html
http://yemengying.com/2016/06/05/interview/
面試必看:https://github.com/kdn251/interviews/blob/master/README-zh-cn.md
https://github.com/it-interview/easy-job
http://taoxiaoran.top/pages/tags.html#Java
https://github.com/hadyang/interview
數據結構:
動畫版展示數據結構: http://zh.visualgo.net/
架構師技能:http://www.zhihu.com/question/29031276
AOP必看博客,架構分析+優化
http://blog.csdn.net/xvshu/article/details/46288953
http://blog.csdn.net/xvshu/article/category/2110821/2
http://blog.csdn.net/chenleixing/article/details/47099725
http://www.cnblogs.com/java-zhao/category/776210.html
http://www.iteye.com/topic/1122859
http://www.cnblogs.com/davidwang456/p/4213652.html
http://blog.csdn.net/z69183787/article/category/2175689/2
http://www.cnblogs.com/java-zhao/p/5106189.html
http://blog.csdn.net/xyw591238/article/category/6083265
https://github.com/jcalaz/jcalaBlog
Java併發容器源碼分析:http://blog.csdn.net/Architect0719/article/category/6193805
書籍推薦:
http://blog.csdn.net/u013256816/article/details/52091850
讀書:
深入理解計算機系統、Java併發編程實戰、深入理解Java虛擬機第二版、tcp/ip詳解 卷一、二、三,Java數據結構與算法
幾個流行Java框架:
Spring MVC/Spring Boot
Spark
Dropwizard
Ninja framework
ZK
Ratpack 用於構建現代化HTTP程序的Java庫
Jodd
jHipster
分佈式協調框架:zookeeper etcd
Java 應用監控 https://github.com/stagemonitor/stagemonitor
http://wsmajunfeng.iteye.com/blog/1744587
7個監控項目: http://www.codeceo.com/article/7-monitor-tools.html
Swagger學習:
http://blog.csdn.net/u010827436/article/details/44417637
Java博客推薦:http://www.tuicool.com/articles/jUrQ7r6
http://www.cnblogs.com/swiftma/category/816241.html
http://www.cnblogs.com/skywang12345/category/489072.html
Java常見類底層實現分析:http://blog.csdn.net/column/details/chenssy-javaimpr.html
開發者頭條、簡書、碼農週刊、推酷、http://www.importnew.com/ 、極客頭條、stackoverflow、java world
深入Java:http://www.codeceo.com/article/tag/java 、http://hugnew.com/?cat=17 、 http://www.hollischuang.com/archives/1001 、
http://www.ibm.com/developerworks/cn/java/ 、 http://www.programcreek.com/ 、http://www.open-open.com/lib/tag/Spring 、
http://blog.csdn.net/pkueecser/article/details/50670601 、猿天地 http://cxytiandi.com/blog 、技術乾貨:http://www.primeton.com/pr/tech.php
國外技術趨勢網站:
https://www.infoq.com
https://seroter.wordpress.com/category/aws/
http://www.theserverside.com/
https://dzone.com
https://www.javacodegeeks.com/2015/12/profile-successful-java-developer-2016.html?spm=5176.100239.blogcont54071.5.YXZzqC
http://ifeve.com/tech-related-sites/
Chrome插件:
掘金:http://gold.xitu.io/extension/
Postman:
需要熟練使用版本控制工具 Git(閱讀:《Git 權威指南》),以及項目構建工具 Maven(閱讀:《Maven實戰》)。另外,在這個階段可以嘗試 TDD 開發。
進階(2-6 個月)
目標:獨立負責某個服務端項目。
技能:
掌握 web 開發最佳實踐,掌握 Restful API 設計,理解 Spring 原理。推薦閱讀《Spring 揭祕》。掌握項目分層、子模塊劃分。推薦閱讀:《J2EE 核心模式》。
掌握 web 架構設計。包括 Http 反向代理,數據緩存,負載均衡,水平擴展和垂直擴展。推薦閱讀:《分佈式Java應用:基礎與實踐》。
掌握關係型數據庫。包括設計 MySQL 表結構,根據業務特點分表分庫,基於執行計劃的 SQL 分析優化,以及數據庫容量規劃。推薦閱讀:《MySQL 必知必會》、《高性能 MySQL》。
瞭解 NoSQL。我們大規模使用 Hadoop、HBase、Hive,同時部分項目使用 Redis、Storm。你需要學會這些工具最基本的使用。
學習 web 安全知識。瞭解 web 前端安全問題。設計安全 web 服務,包括加解密、防僞造、防重放攻擊等。
掌握 Http(推薦閱讀:《圖解 Http》、《Http 權威指南》)、Thrift 等協議。
掌握服務容量規劃,性能調優,可靠性保證,以及故障處理。學習容量規劃和性能調優知識,梳理業務監控點,熟練使用我們的監控報警系統。推薦閱讀:《深入理解 Java 虛擬機》。
其他。設計模式:從項目中學習,有時間可以看看《深入淺出設計模式》、《JDK 裏的設計模式》。學習Java Socket 編程與多線程知識,可以看看《Java 併發編程實戰》,並翻翻併發編程網的文章。
深入(6 個月-)
目標:分佈式系統和中間件開發。
構建知識體系:《大型網站系統與 Java 中間件實踐》、《大型網站技術架構:核心原理與案例分析》。
原理與設計:《大規模存儲式系統》、《UNIX 網絡編程 卷1:套接字聯網 API》、《How Tomcat Works》。
學習開源項目:Apache Thrift、Zipkin、Netty、Rose、Jade、淘寶 RPC 系統 Dubbo 等。分析項目中的設計思路。比如,同樣是RPC框架,Finagle 和 Dubbo 有什麼異同。
其他。根據參與的項目加深學習吧。比如,如果需要寫 DSL,可以讀一下《領域特定語言》,對 Redis 感興趣推薦讀一下:《Redis 設計與實現》。有兩本書,無論做什麼項目,都推薦讀:《Unix 編程藝術》、《UNIX 環境高級編程(第3版)》。
HashMap、TreeMap、數據庫索引 原理,及常見的平衡二叉樹,紅黑樹原理和實現,redis的幾種數據結構原理和優缺點(Hash結構的ziplist和quicklist原理),Hash算法原理和實現
https://github.com/spotify/apollo
http://blog.csdn.net/column/details/java-vitual-machine.html
http://blog.csdn.net/column/details/yrp-java-algorithm.html?&page=2
http://blog.csdn.net/column/details/datastructureinjava.html
http://blog.csdn.net/yannanying/article/details/46956355
http://blog.csdn.net/column/details/datastructure-phn.html
http://blog.csdn.net/column/details/zhonghua.html
http://blog.csdn.net/column/details/datastructure.html
https://github.com/shekhargulati/52-technologies-in-2016
十二個程序員必備的優質資源推薦: http://blog.csdn.net/proginn/article/details/51614131
美團點評技術團隊:http://tech.meituan.com/
阿里中間件團隊:http://jm.taobao.org/
BAT 技術團隊博客:http://blog.csdn.net/tengdazhang770960436/article/details/49963983
技術博客推薦:http://www.cnblogs.com/newpanderking/p/4366174.html
NB的技術社區:https://yq.aliyun.com/tags/type_blog-tagid_41/?spm=5176.100239.rightarea.8.4qenGu
Spring-session源碼解析:https://yq.aliyun.com/articles/57425?spm=5176.100239.blogrightarea58510.22.DTpSqZ
Java上線用到的項目:
JMeter 測試
FindBugs SparkJava
JProfiler 約束內存泄漏和修復線程的問題。
Takipi
消息中間件:
Nats --- 速度超快 每秒能處理千萬級別消息,佔用CPU少,但不支持離線
Kafka --- 速度快,每秒處理百萬級消息,能存儲,適合大數據
推薦需要看的幾本書:
《輕量級微服務架構(上冊)》
<<Web Scalability for Startup Engineers--互聯網創業核心技術:構建可伸縮的web應用>>
《Spring源碼深度解析》《大型網站技術架構 核心原理與案例分析》《大型網站系統與Java中間件實踐》《Effective Java中文版》《HotSpot實戰》
《從Paxos到ZooKeeper 分佈式一致性原理與實踐》《深入分析Java Web技術內幕》《java多線程編程核心技術》《實戰Java高併發程序設計》
《深入Java虛擬機第2版》《重構 改善既有代碼的設計》 《高性能MySQL第3版》 《Java編程思想第4版》 《HTTP權威指南》 《精通正則表達式必知必會》
《Java解惑》 《Java併發編程實踐》 《鳥哥的Linux私房菜》《How Tomcat Works(中英文版)》 《Maven權威指南》
《Java併發編程實戰》
要求:
其次掌握的技能樹主要有三個方面:
第一個是基礎,比如對集合類,併發包,IO/NIO,JVM,內存模型,泛型,異常,反射,等有深入瞭解,最好是看過源碼瞭解底層的設計。比如一般面試都會問ConcurrentHashMap,CopyOnWrite,線程池,CAS,AQS,虛擬機優化等知識點,因爲這些對互聯網的企業是絕對重要的。而且一般人這關都過不了,還發鬧騷說這些沒什麼用,爲什麼要面試。舉一例子,在使用線程池時,因爲使用了無界隊列,在遠程服務異常情況下導致內層飆升,怎麼去解決?你要是連線程池都不清楚,你怎麼去玩?再舉一例,由於對ThreadLocal理解出錯,使用它做線程安全的控制,導致沒能實現真的線程安全,你怪我哦?所以作爲一個拿兩萬的JAVA程序員這點基礎是必須的。
第二你需要有全面的互聯網技術相關知識。從底層說起,你起碼得深入瞭解mysql,redis,mongodb,nginx,tomcat,rpc,jms等方面的知識。你要問需要瞭解到什麼程度,我可以給你說個大慨。首先對於MySQL,你要知道常見的參數設置,存儲引擎怎麼去選擇,還需要了解常見的索引引擎,知道怎麼去選擇。知道怎麼去設計表,怎麼優化sql,怎麼根據執行計劃去調優。高級的你需要去做分庫分表的設計和優化,一般互聯網企業的數據庫都是讀寫分離,還會垂直與水平拆分,所以這個也有經驗的成分在裏面。然後redis,mongodb都是需要了解原理,需要會調整參數的,而nginx和tomcat幾乎都是JAVA互聯網方面必配,其實很阿里的技術棧選擇有點關係。至於rpc相關的就多的去,必須各種網絡協議,序列化技術,SOA等等,你要有一個深入的理解。現在應用比較廣的rpc框架,在國內就是dubbo了,可以自行搜索。至於jms相關的起碼得了解原理吧,一般情況下不是專門開發中間件系統和支撐系統的不需要了解太多細節,國內企業常用的主要是activeMQ和kafka。你能對我說的都研究的比較深入,阿里p6我覺得是沒問題的,當然這個還需要看你的架構能力方面的面試表現了。
第三就是編程能力,編程思想,算法能力,架構能力的考量。首先2W程序員對算法的要求我覺得還是比較低,再高級也最多紅黑樹吧,但是排序和查詢的基本算法得會。編程思想是必須的,問你個AOP和IOC你起碼的清清楚楚,設計模式不說每種都用過,但是也能深入理解個十四五種。編程能力這個我覺得不好去評價,但是拿一個2000W用戶根據姓名年齡排序這種題目也能信手拈來。最後就是架構能力,這種不是說要你設計個多牛逼多高併發的系統,起碼讓你做一個秒殺系統,防重請求的設計能快速搞定而沒有坑吧。
#深入理解Java虛擬機第2版
#Java併發編程實戰
#MongoDB權威指南
#Netty權威指南
Netty Mina框架源碼
2016新興互聯網公司前300:http://www.askci.com/news/hlw/20160425/941447185.shtml
面試:
1.數據傳入的安全性解決方案?認證 SSL HTTPS 原理(eg:遊戲中數據傳輸給服務器,如何保證數據安全和完整,防止外掛?)
2.如何使用多線程處理同一個大的任務?
3.蹲坑算法(數據量大的時候,根據內存空間的有序性,爲每個數找各自對應的內存空間地址)
4.如何防止內存被擊穿,最大併發限制,降級策略?(eg:遊戲服務器,併發最多5000,超過就回擊穿服務器內存,如果玩家>5000,如何處理?)
百萬級訪問量網站架構:http://www.biaodianfu.com/thinking-before-building-site.html
http://www.wtoutiao.com/p/12aqbAi.html
初期架構一般比較簡單,web負載均衡+數據庫主從+緩存+分佈式存儲+隊列。
大方向上也確實就這幾樣東西,細節上也無數文章都重複過了,按照將來會有N多WEB,N多主從關係,N多緩存,N多xxx設計就行,基本方案都是現成的,
只是您比其他人厲害之處就在於設計上考慮到緩存失效時的雪崩效應、主從同步的數據一致性和時間差、隊列的穩定性和失敗後的重試策略、文件存儲的效率和備份方式等等意外情況。
緩存總有一天會失效,數據庫複製總有一天會斷掉,隊列總有一天會寫不進去,電源總有一天會燒壞。根據墨菲定律,如果不考慮這些,網站早晚會成爲茶几
需要加強學習的東西:
網絡: Netty,mina,NIO,REST,OAuth
Web:Tapestry,DWR,GWT,WebX
搜索: ElasticSearch,Solr
緩存/DB: mongoDB、HBASE,Cassandra,Redis
中間件:RPC,Dubbo,Thrift,Zookeeper,ActiveMQ, Kafka
技能: Shell編程、性能調優、MAT、救火、故障排查
跨語言: shell、Go、NodeJS
大數據: hadoop、hbase、storm、hive、pig、spark
其他框架:Spring Boot、Spring Cloud、 Disruptor(Disruptor是一個用於在線程間通信的高效低延時的消息組件,它像個增強的隊列)、Guava、Trove(高性能集合框架)
多線程推薦:
http://www.cnblogs.com/dolphin0520/category/602384.html
http://blog.csdn.net/qilixiang012/article/category/2857487
http://blog.csdn.net/column/details/java-dxc.html
1.Eclipse安裝Activiti插件 http://activiti.org/designer/update/
2.Spring MVC 輸出頁面亂碼 在mvc.xml中配置 StringHttpMessageConverter 編碼格式
<mvc:message-converters>
<beans:bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.StringHttpMessageConverter">
<beans:property name="supportedMediaTypes">
<beans:list>
<beans:value>text/plain;charset=UTF-8</beans:value>
<beans:value>text/html;charset=UTF-8</beans:value>
</beans:list>
</beans:property>
</beans:bean>
<beans:bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></beans:bean>
<beans:bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></beans:bean>
</mvc:message-converters>
3.Spring MVC註解式統一異常處理:
--- 可根據異常,返回數據或指定頁面
BaseController中添加方法處理異常
@ExceptionHandler(RuntimeException.class)
public @ResponseBody ResponseVo runtimeExceptionHandler(RuntimeException ex) {
ResponseVo responseVo = new ResponseVo();
responseVo.setSuccess(false);
responseVo.setMsg(ex.getMessage());
responseVo.setData(ex);
return responseVo;
}
4.幾個最常用的Eclipse快捷鍵
1. ctrl+shift+r:打開資源,在workspace中快速按文件名查找
2. ctrl+o:快速outline,查看代碼結構,顯示類中方法和屬性,能快速定位類的方法和屬性
3. ctrl+e:快速轉換編輯器
4. ctrl+2,L:爲本地變量賦值
5. alt+shift+r:重命名
6. Alt+左右方向鍵
我們經常會遇到看代碼時Ctrl+左鍵,層層跟蹤,然後迷失在代碼中的情況,
這時只需要按“Alt+左方向鍵”就可以退回到上次閱讀的位置,
同理,按“Alt+右方向鍵”會前進到剛纔退回的閱讀位置
7.ctrl+shift+x和ctrl+shift+y:英文字母大小寫的轉換
8.ctrl+shift+f:格式化代碼
9.ctrl+m:當前編輯頁面窗口最大/小化
10.ctrl+shift+o:自動引入包和刪除無用包
11.Ctrl+T 快速顯示當前類的繼承結構
12.Ctrl+W 關閉當前窗口
13.Alt+Shift+R 重命名
14.Alt+Shift+M 抽取方法
15.【Ct rl+K】、【Ct rl++Shift +K】 快速向下和向上查找選定的內容,從此不再需要用鼠標單擊查找對話框
16.Ctrl+Shift+G 查找類、方法和屬性的引用
17.Alt+Shift+w 查找當前文件所在項目中的路徑
18.Ctrl+Shift+w 關閉所有文件
最實用的:
【Alt + ← 】 查看代碼時,返回上次查看位置 --- 後退上次代碼記錄
【Alt + → 】 查看代碼時,跟蹤,回到下次瀏覽位置
點中類名+F4 查看類的繼承關係
5.常見的內存移除的3種情況
1.JVM Heap(堆)溢出 java.lang.OutOfMemoryError:Java heap space
JVM在啓動的時候會自動設置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。
Heap的大小是Young Generation 和Tenured Generaion 之和。在JVM中如果98%的時間是用於GC,且可用的Heap size 不足2%的時候將拋出此異常信息
解決方法:
設置JVM Heap(堆)大小
即:-Xmn -Xms -Xmx等選項,以及 年輕代與年老代的比例 ratio等參數
2.PermGen space溢出 java.lang.OutOfMemoryError:PermGen space
永久帶溢出 --- 一般發生在程序啓動階段
永久帶被JVM存放Class和Meta信息,如果載入很多Class,可能出現PermGen space溢出
解決方法:
通過-XX:PermSize和-XX:MaxPermSize設置永久代大小即可
3.棧溢出 java.lang.StackOverflowError : Thread Stack space
可能原因:遞歸層次太多,導致棧溢出
解決方法:
1.修改程序
2.通過 -Xss 設置每個線程的Stack大小
Server容器啓動時,需要設置的幾個JVM參數:
-Xms:java Heap 堆初始大小, 默認是物理內存的1/64。
-Xmx:java Heap 堆最大值,不可超過物理內存。
-Xmn:young generation的heap堆大小,一般設置爲Xmx的3、4分之一 。增大年輕代後,將會減小年老代大小,可以根據監控合理設置。
-Xss:每個線程的Stack大小,而最佳值應該是128K,默認值好像是512k。
-XX:PermSize:設定內存的永久保存區初始大小,缺省值爲64M。
-XX:MaxPermSize:設定內存的永久保存區最大大小,缺省值爲64M。
-XX:SurvivorRatio:Eden區與Survivor區的大小比值,設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10
-XX:+UseParallelGC:F年輕代使用併發收集,而年老代仍舊使用串行收集.
-XX:+UseParNewGC:設置年輕代爲並行收集,JDK5.0以上,JVM會根據系統配置自行設置,所無需再設置此值。
-XX:ParallelGCThreads:並行收集器的線程數,值最好配置與處理器數目相等 同樣適用於CMS。
-XX:+UseParallelOldGC:年老代垃圾收集方式爲並行收集(Parallel Compacting)。
-XX:MaxGCPauseMillis:每次年輕代垃圾回收的最長時間(最大暫停時間),如果無法滿足此時間,JVM會自動調整年輕代大小,以滿足此值。
-XX:+ScavengeBeforeFullGC:Full GC前調用YGC,默認是true。
實例如:JAVA_OPTS=”-Xms4g -Xmx4g -Xmn1024m -XX:PermSize=320M -XX:MaxPermSize=320m -XX:SurvivorRatio=6″
6.Service或Dao層獲取request和IP方法?
1.獲取IP InetAddress.getLocalHost().getHostAddress()
2.獲取request對象 http://my.oschina.net/u/2007041/blog/420956
7.JDK6在Linux下的安裝
第一:用linux 的命令運行它: sh jdk-6u2-linux-i586-rpm.bin
第二:按多次回車後出現
Do you agree to the above license terms? [yes or no]
輸入yes
第三:編輯環境變量
$gedit ~/.bashrc
加入如下五行:
JAVA_HOME=/usr/java/jdk1.6.0_02
JAVA_BIN=/usr/java/jdk1.6.0_02/bin
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME JAVA_BIN PATH CLASSPATH
第四步是必須的,不然它總是調用FC6自帶的jdk1.4
第四:創建鏈接
#cd /usr/bin
#ln -s -f /usr/local/jdk1.5.0_05/jre/bin/java
#ln -s -f /usr/local/jdk1.5.0_05/bin/javac
或 環境變量配置 vi /etc/profile
#for java
export JAVA_HOME=/home/hetiewei/software/java/jdk1.8.0_40
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
#for go
export GOROOT=/home/hetiewei/go/soft/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/hetiewei/go/soft/go/pkg
#for node
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
PATH=/usr/local/ssl/bin:/sbin/:$PATH:/usr/sbin
export PATH
#for hadoop
export HADOOP_HOME=/home/hetiewei/software/bigdata/hadoop/hadoop-2.6.4
export PATH=.:$HADOOP_HOME/bin:$PATH
source /etc/profile
8.Tomcat啓動時,在initialing Spring root context 卡死:
解決:
1.查看數據庫連接
2.清除Tomcat下的work目錄
9.Maven install時出現編碼異常解決方法:
mvn clean install -Dmaven.javadoc.skip=true
maven 安裝時跳過 測試用例:
mvn clean install -Dmaven.test.skip=true
10.Spring和Mybatis整合時無法讀取properties的處理方案:
方法一:
修改<property name="sqlSessionFactory" ref="sqlSessionFactory"/>爲<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
原理:使用sqlSessionFactoryBeanName注入,不會立即初始化sqlSessionFactory, 所以不會引發提前初始化問題。
方法二:
直接刪掉<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
注意:在沒有配置這一行時,必須配置一個以sqlSessionFactory命名的org.mybatis.spring.SqlSessionFactoryBean。
11.token,Session的區別?
服務器在生成表單的時候同時生成一個CSRF token,插入到表單的一個hidden field裏面,並且把這個token記錄在服務器端,通常是用戶的Session數據裏面。
客戶端啥都不用幹,照常提交表單。當表單被提交的時候,服務端檢查一個表單裏面的token跟自己之前記錄下來的是否匹配,匹配才繼續處理。
CSRF劫持的請求也會帶上網站的cookie的,所以光驗證session並不能避免CSRF
token的關鍵是在於你在發送請求的時候一定要保證你是讀取了這個頁面的。。 而不是憑空就發送請求
對於HTTP協議來說,就是url,header,body
對於WEB頁面來說,就是url,form/post body,cookie
好了,現在有的就是這麼多東西,要怎麼用呢?
首先是第一個問題,HTTP請求是無狀態的,我怎麼知道誰是誰?
解:讓用戶每次訪問的時候告訴你它叫什麼,怎麼告訴?url,form/post body,cookie
然後是第二個問題,用戶訪問的時候說他自己是張三,他騙你怎麼辦?
解:在服務器端保存張三的信息,給他一個id,讓他下次用id訪問。id保存在url,form/post body,cookie中。這叫做session
現在是第三個問題,用戶提交了一筆訂單,你怎麼保證他是在你的訂單頁面提交的?(referer可能是一個辦法)
解:在你訂單頁面中放入一個加密的信息,只有真正打開了訂單頁才能知道,提交的時候將這個信息返回回來。這個東西,可以被叫做token
Token實現防止表單重複提交?
表單重複提交的兩種情形:
1.通過瀏覽器回退功能,回到原來頁面重複提交表單,服務器端應避免用戶重複註冊
2.提交完成後,單擊瀏覽器的“刷新”按鈕,瀏覽器會彈出對話框,詢問是否重新提交數據。單擊“是”,瀏覽器會重新提交數據。
如何防止?
每次請求都生產一個token標識
1.表單提交後,先匹配(使用Aop做)token,判斷當前用戶會話中token令牌值與當前請求參數的token令牌值是否一致
2.每次請求都創建一個新的token令牌,將其保存在當前會話(session)範圍內
3.token在服務器端匹配後,就把session中的toke令牌值刪除
12.Java線程安全的本質:線程中並沒有存放任何對象數據,而是在執行時,去主內存(堆)中去同步數據,所有的對象數據都存在JVM的堆中,因此需要對資源進行共享鎖!!!
堆 --- JVM的核心數據存儲區 --- 線程共享的主內存
堆中爲JVM的所有對象分配了內存空間用以存儲和維護變量值等
棧 --- 線程私有的內存區,由值棧(線程棧)組成,存放8中基本數據類型和對象引用
每個線程都會生成一個自有的線程棧,線程棧中用存儲了該線程的基本數據常量,變量值,以及對象長變量的引用
每個線程執行時,根據代碼順序,壓棧 值棧(棧內存)
對象變量在線程執行時的過程:!!! --- 由JVM內存模型決定
1.線程根據棧中的引用去堆上同步該對象數據下來,然後在線程自己的內存中進行操作
2.操作之後再將線程棧撒花姑娘的運算結果同步到堆(主內存)中
3.多線程時,因爲每個線程都操作自己從主內存(JVM堆)中同步過來的數據,如果不加鎖,會導致線程安全問題(數據提交到主內存時不一致)
13.堆 --- JVM中所有對象的內存空間 分爲: Young Gen, Old Gen
Young Gen 又分爲:Eden區和兩個大小相同的Survivor區(from 和 to)
Eden和Survivor默認比例 8:1 由 -XX:SurvivorRation設置
堆大小 -Xmx -Xms 設置
Young Gen -Xmn 設置
-XX:NewSize和-XX:MaxNewSize
用於設置年輕代的大小,建議設爲整個堆大小的1/3或者1/4,兩個值設爲一樣大。
Minor GC --- 發生在新生代的垃圾回收,Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快,
年輕代的GC使用複製算法(將內存分爲兩塊,每次只用其中一塊,當這塊內存用完,就將還活着的對象複製到另外一塊上面,複製算法不會產生內存碎片)
Full GC --- 發生在年老代的GC, Full GC比較慢,儘量避免
新創建對象都會被分配到Eden區(一些大對象特殊處理),當Eden區滿則進行Minor GC,
這些對象經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區, 對象在Survivor區中每熬過一次Minor GC,年齡增加1歲,
當年齡增加到一定程度後(默認15歲),會被移動到年老代中,
當年老代滿時,經常Full GC
線程Stack 每個線程獨有的操作數棧局部變量表方法入口 -Xss 設置
方法區 -XX:PermSize和-XX:MaxPermSize設置
JVM中的類加載機制: http://www.cnblogs.com/ITtangtang/p/3978102.html
14.Go,MongoDB,redis,node.js在Linux下的安全和配置
1.go
下載tar.gz後,
解壓:tar -zxvf go1.6.2.linux-amd64.tar.gz
環境變量:
vi /etc/profile
在profile中添加一下內容:
GOPATH和GOROOT修改爲你的路徑即可!!!
export GOROOT=/home/forward/tools/go
export PATH=$GOROOT/bin:$PATH
export GOPATH=/home/forward/tools/gopkg
刷新環境變量:
source /etc/profile
驗證:
go version
編譯Go程序:
go build xx.go
直接運行Go程序:
go run xx.go
2.mongodb
解壓:
tar -zxvf mongodb-linux-i686-3.2.6.tgz
環境變量:
--- (只當前用戶有效)
export PATH=<mongodb-install-directory>/bin:$PATH
或
--- (全局有效)
vi /etc/profile
在profile中添加一下內容:
export PATH=<mongodb-install-directory>/bin:$PATH
刷新環境變量:
source /etc/profile
3.node.js
下載解壓:
wget --no-check-certificate https://nodejs.org/dist/v6.2.0/node-v6.2.0-linux-x64.tar.gz
環境變量:
NODE_HOME 是node.js安全目錄
export NODE_HOME=/home/hetiewei/node/software/node-v6.2.0-linux-x64
export PATH=$PATH:$NODE_HOME/bin
export NODE_PATH=$NODE_HOME/lib/node_modules
刷新環境變量: 讓環境變量生效
source /etc/profile
驗證:
命令行輸入:node -v,查看node.js的版本
4.redis
參考官方源碼安裝說明
Redis Cluster集羣的搭建與實踐 http://lib.csdn.net/article/redis/60796
15.通過Spring在工具類中獲取HttpServletRequest對象:
/**
* 獲取當前Request
* @return
*/
private HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return requestAttributes.getRequest();
}
16.Spring mvc在redirect請求中傳遞數據 http://shmilyaw-hotmail-com.iteye.com/blog/2246344
3種方式:1.Session 2.url template 3. flash attribute
17.logback與ActiveMQ的slf4j jar包衝突解決??? --- MQ使用指定版本, 不使用用activemq-all
<!-- Spring 整合ActiveMQ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- activeMQ begin -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-core</artifactId>
<version>5.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-pool</artifactId>
<version>5.7.0</version>
</dependency>
18.logback配置打印MyBatis執行的sql?
在logback.xml中配置 : dao所在的包名
<logger name="com.tuniu.plat.config.dao" level="DEBUG"/>
19.Spring MVC接收不到前端的參數???
試試: data:JSON.stringify(formData)
20.File文件目錄中文無法解析的問題:
String path = Config.class.getClassLoader().getResource("").toURI().getPath();
21.Spring MVC 轉發和重定向的傳參問題?
1.轉發 forward
1. this.getServletContext().getRequestDispatcher("/rentHouse.htm?method=display").forward(request,response);
return null;
2. return new ModelAndView("forward:/xxx.htm", map);
或
ModelAndView mv = new ModelAndView("forward:/xxx.htm");
mv.addAtrribute("param", value)
return mv;
2.重定向 redirect
1.無參數
return new ModelAndView("redirect:/toList");
return "redirect:/ toList ";
2.有參數
1.手動拼url
new ModelAndView("redirect:/toList?param1="+value1+"¶m2="+value2);
這樣有個弊端,就是傳中文可能會有亂碼問題。
2.用RedirectAttributes
redirectAttributes.addFlashAttribute("message", "保存用戶成功!");//使用addFlashAttribute,參數不會出現在url地址欄中
public String save(@ModelAttribute("form") Bean form,RedirectAttributes attr)
throws Exception {
String code = service.save(form);
if(code.equals("000")){
attr.addFlashAttribute("name", form.getName());
attr.addFlashAttribute("success", "添加成功!");
return "redirect:/index";
}else{
attr.addAttribute("projectName", form.getProjectName());
attr.addAttribute("enviroment", form.getEnviroment());
attr.addFlashAttribute("msg", "添加出錯!錯誤碼爲:"+rsp.getCode().getCode()+",錯誤爲:"+rsp.getCode().getName());
return "redirect:/maintenance/toAddConfigCenter";
}
}
注意:
1.使用RedirectAttributes的addAttribute方法傳遞參數會跟隨在URL後面,如上代碼即爲http:/index.action?a=a
2.使用addFlashAttribute不會跟隨在URL後面,會把該參數值暫時保存於session,待重定向url獲取該參數後從session中移除,
這裏的redirect必須是方法映射路徑,jsp無效。你會發現redirect後的jsp頁面中b只會出現一次,刷新後b再也不會出現了,
這驗證了上面說的,b被訪問後就會從session中移除。對於重複提交可以使用此來完成.
22.QQ 網頁上的登陸模塊(全程HTTP/GET請求) --- 前端數據的安全
QQ 在登陸時,對用戶輸入的密碼加密的JS代碼爲:
function getEncryption(password, uin, vcode, isMd5) {
var str1 = hexchar2bin(isMd5 ? password : md5(password));
var str2 = md5(str1 + uin);
var str3 = md5(str2 + vcode.toUpperCase());
return str3
}
白話就是: md5(md5(md5(密碼) + 用戶的QQ號) + 驗證碼)
驗證碼是一次性的, 所以,在你在網絡層拿到本次的請求之後,無法做 重放攻擊, 因爲驗證碼是不正確的.
23.git常用命令:
git init 將當前目錄初始化爲git倉庫
git status 查看倉庫狀態
git add xx 添加文件,該文件等待提交
git add -A 或 git add . 添加當前目錄下所有文件
git commit -m "備註" 提價到本地git倉庫
git remote add origin https://github.com/jayfeihe/xx.git 爲本地倉庫指定遠程倉庫
git remote rm origin 斷開遠程倉庫
git remote -v 查看遠程倉庫
git pull origin master 從遠程倉庫拉取文件
git push origin master 向遠程倉庫推送已提交內容
1,如何在提交代碼前看看我的代碼中不同文件的狀態?
git status
2,如何把別人的代碼拉取下來?
git clone url
url:一般都是在github上的倉庫地址
執行這個命令後,倉庫就會被下載到你指定的目錄
3,如何把新的文件加入到git的索引中?
git add file1 file2 file3
這麼增加很多文件的話一定很煩,那麼請使用git add . 把所有文件加入到git索引中
git索引:代表了你的文件已經被git管理
4,如何看我將要提交到遠程倉庫的文件?
git diff --cached
5,如何給我的提交增加備註說明?
git commit -m “xxxx說明”這個命令是提交代碼必須的
6,如何通過圖形化的界面查看該項目的所有歷史提交記錄?
gitk
7,如何查看項目的日誌?也就是你的提交記錄
git log
8,如何合併git add 和git commit 命令的效果?
git commit -a -m“xxx說明”
注意,這會把所有文件add到git索引中,可能你會有不想被git管理的文件,所以你需要事先通過忽略文件來控制。
9,如何創建一個分支?
git branch a
a就是新分支,然後使用git checkout a來切換到a分支,創建分支的意義是,你可以在自己的分支下開發,在開發完成後和主版本master合併,尤其在團隊中尤爲重要
10,如何合併分支到主分支?
git merge a master
11,如何刪除已經合併的分支?
git branch -D a
12,如何暫時保存我們的工作記錄,去看一個例如修復版本bug的事情?
git stash “xxxxx” 暫時記錄你的工作狀態
進行你的修復工作
git commit -a -m “xxx提交你的修復”
git stash apply 回到你的工作
13,如何搞定遠程分支和本地主版本的合併?
git branch --track [遠程分支的名稱] origin/[遠程分支的名稱]
例如:git branch --track a origin/a
git pull a
pull:這個命令同時做了下載遠程a分支,併合併到本地master的動作。如果有衝突是會合並失敗的,但是不會造成下載a分支失敗。
14,如何根據關鍵字搜索其在代碼中出現的位置?
git grep -n [key]
15,如果我想重置我的版本如何做?
git reset --hard HEAD
這個命令會把你做過的所有未提交(git commit -m)的內容清空
16,如果我只想重置一個文件怎麼做?
git checkout --file
這時只會check出一個未修改過的文件
17,如何修復一個已經提交文件中的錯誤?
雖然有2種做法,創建一個新提交和checkout出老提交併修復,但是建議通過創建新提交去做。因爲git對於歷史內容被改動會出現不能合併的情況
24.ActiveMQ整合Spring,監聽隊列
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd ">
<!-- ActiveMQ連接工廠 -->
<bean id="amqConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL"
value="failover:(tcp://mq.master.jd.tuniu-cie.org:61616)" />
</bean>
<!-- <bean id="productDetailListener" class="com.tuniu.plat.service.mq.ProductDetailListener"/> -->
<!--方式一:只監聽一個隊列 -->
<!-- 監聽器隊列 -->
<!--
<bean id="productDetailDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg index="0" value="${product_mq_queue}" />
-->
</bean>
<!-- 消息監聽容器(Queue),配置連接工廠,監聽的隊列,監聽器是:mq的產品消息監聽器 -->
<!--
<bean id="jmsContainer"
class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="amqConnectionFactory" />
<property name="destination" ref="productDetailDestination" />
<property name="messageListener" ref="productDetailListener" />
</bean>
-->
<!--方式二:監聽多個隊列 -->
<jms:listener-container destination-type="queue" container-type="default" connection-factory="amqConnectionFactory" acknowledge="auto">
<jms:listener destination="${product_mq_queue}" ref="productDetailListener"/>
</jms:listener-container>
<!-- Spring JmsTemplate 的消息生產者 start -->
<!-- 定義JmsTemplate的Queue類型 -->
<bean id="jmsQueueTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- 非pub/sub模型(發佈/訂閱),即隊列模式 -->
<property name="pubSubDomain" value="false" />
</bean>
<!-- 定義JmsTemplate的Topic類型 -->
<bean id="jmsTopicTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 這個connectionFactory對應的是我們定義的Spring提供的那個ConnectionFactory對象 -->
<constructor-arg ref="amqConnectionFactory" />
<!-- pub/sub模型(發佈/訂閱) -->
<property name="pubSubDomain" value="true" />
</bean>
<!--Spring JmsTemplate 的消息生產者 end -->
</beans>
25.JSP中無法解析後端傳遞的數據(jstl,el表達式不生效)?
1.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
</web-app>
2.jsp頁面
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page isELIgnored="false" %>
26.關於LinkedBlockQueue
阻塞的線程安全隊列,底層採用鏈表實現,LinkedBlockingQueue不接受null
添加元素: 都是向隊尾添加元素
put 向隊尾添加元素,如果隊列滿了會發生阻塞,一直等待空間,以加入元素
add 添加元素時,超出隊列長度會直接拋異常
offer 添加元素時,如果隊列已滿,直接返回false
移除元素: 都是從隊頭移除元素
poll 隊列爲空,返回null
remove 隊列爲空,拋出NoSuchElementException異常
take 隊列爲空,發送阻塞,等到有元素
BlockingQueue阻塞隊列的4個實現類:
ArrayBlockingQueue:規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的
LinkedBlockingQueue:大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.其所含的對象是以FIFO(先入先出)順序排序的 ,不允許放null
PriotityBlockingQueue:類似於LinkedBlockQueue,但其所含對象的排序不是FIFO,而是依據對象的自然排序順序或者是構造函數的Comparator決定的順序
SynchronousQueue:對其的操作必須是放和取交替完成的
LinkedBlockingQueue的數據吞吐量要大於ArrayBlockingQueue,但在線程數量很大時其性能的可預見性低於ArrayBlockingQueue
27.MyBatis批量更新參考:
http://my.oschina.net/zouqun/blog/405424
http://blog.csdn.net/tolcf/article/details/39213217
28.Spring 中配置的@Aspect 不起作用:
1.添加註解:@Component
2.在Spring MVC的配置文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>
29.Spring Boot 遇到的問題?
Spring Boot Mapper無法注入:啓動類上添加註解 @MapperScan("com.jay.spring.boot.demo10.multidb.dao")
30.Maven指定打包環境:http://haohaoxuexi.iteye.com/blog/1900568
3個地方可以指定profile環境:
web.xml
maven的setting.xml --- 默認是:dev
項目的pom.xml
eg:pom.xml
<profiles>
<profile>
<id>development</id>
<properties>
<profile.path>config/dev</profile.path>
</properties>
</profile>
<profile>
<id>sit</id>
<properties>
<profile.path>config/sit</profile.path>
</properties>
</profile>
<profile>
<id>product</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.path>config/prd</profile.path>
</properties>
</profile>
</profiles>
1.通過下面配置指定默認環境:
<activation>
<activeByDefault>true</activeByDefault>
</activation>
或
2.通過命令指定環境:(說明:-P profile_id)
mvn package –P product
31.Spring中的幾個Listener監聽器和類 http://www.cnblogs.com/damowang/p/4305153.html
1.ServletContextListener接口 --- Web容器啓動時執行,此時Bean還未初始化,不能在裏面獲取依賴的bean
--- 適合做一些容器初始化工作
Why ServletContextListener接口無法獲取Spring中定義的Bean?
eg:
public class ConfigListener implements ServletContextListener {
@Autowired
private ConfigService configService;
@Override
public void contextInitialized(ServletContextEvent sce) {
configService.initConfig(); //這裏會報空指針異常,無法獲取注入的configService
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
要理解這個問題,首先要區分Listener的生命週期和spring管理的bean的生命週期
(1)Listener的生命週期是由servlet容器(例如tomcat)管理的,項目啓動時上例中的ConfigListener是由servlet容器實例化並調用其contextInitialized方法,而servlet容器並不認得@Autowired註解,因此導致ConfigService實例注入失敗。
(2)而spring容器中的bean的生命週期是由spring容器管理的。
(3)即:此時Spring管理的Bean還未初始化完成
修改:
@Override
public void contextInitialized(ServletContextEvent sce) {
ConfigService configService = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()).getBean(ConfigService.class);
configService.initConfig();
}
特別注意:
以上代碼有一個前提,那就是servlet容器在實例化ConfigListener並調用其方法之前,要確保spring容器已經初始化完畢!而spring容器的初始化也是由Listener(ContextLoaderListener)完成,因此只需在web.xml中先配置初始化spring容器的Listener,然後在配置自己的Listener,配置如下
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>example.ConfigListener</listener-class>
</listener>
2.ApplicationListener<T>接口 --- Spring容器初始化完成,Bean加載完成後執行, 可獲取所依賴的Bean,
---- 適合完成一些項目的初始化工作(eg:加載或清空過期緩存,執行初始化sql等)
T 表示事件:
ApplicationEvent,每次請求都會執行
ContextRefreshedEvent, Spring容器啓動後執行
參考:http://www.cnblogs.com/rollenholt/p/3612440.html
3.項目加載完畢後,執行一些初始化工作,eg:數據庫初始化或查詢數據,緩存初始化等 --- 3 種 方式
1.實現BeanPostProcessor接口
接口有兩個方法
(1)postProcessBeforeInitialization方法,在spring中定義的bean初始化前調用這個方法;
(2)postProcessAfterInitialization方法,在spring中定義的bean初始化後調用這個方法;
2.實現ApplicationListener接口
實現其onApplicationEvent()方法
Spring容器初始化完成,Bean加載完成後執行, 可獲取所依賴的Bean
特別注意:
ApplicationListener接口中的onApplicationEvent被調用多次的問題?
解決:
onApplicationEvent事件參數,添加final 即:final ContextRefreshedEvent event
eg:
@Component
public class NatsInitListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private NatsService natsService;
/**
* 容器啓動時,添加Nats的消息監聽
* @param contextRefreshedEvent
*/
@Override
public void onApplicationEvent(final ContextRefreshedEvent contextRefreshedEvent) {
try {
natsService.getMsgAsync("foo");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.實現InitializingBean接口
實現afterPropertiesSet()方法
項目在加載完畢後立刻執行afterPropertiesSet 方法 ,並且可以使用spring 注入好的bean
32.Map的遍歷
entrySet()和 keySet() 方式, 前者快,後者滿
entrySet() 是map的一個節點,是橫向的,生成鍵和映射關係的視圖 不需要再get一次。所以效率明顯快
keySet() 是map中所有鍵的集合,是縱向的,先獲取出map的key,然後再通過key,get出value
keySet是鍵的集合,Set裏面的類型即key的類型
entrySet是 鍵-值 對的集合,Set裏面的類型是Map.Entry
Map<String, String> maps = new HashMap<String, String>();
//方法一: 用entrySet()
Iterator<Entry<String,String>> it = maps.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,String> m = it.next();
String key = m.getKey();
String value= m.getValue();
}
// 方法二:jdk1.5支持,用entrySet()和For-Each循環()
for (Map.Entry<String, String> m : maps.entrySet()) {
String key = m.getKey();
String value= m.getValue();
}
// 方法三:用keySet()
Iterator<String> it2 = maps.keySet().iterator();
while (it2.hasNext()){
String key = it2.next();
String value= maps.get(key);
}
// 方法四:jdk1.5支持,用keySet()和For-Each循環
for(String m: maps.keySet()){
String key = m;
String value= maps.get(m);
}
33.解決CORS跨域問題的3種方式?
1.Tomcat配置
1.下載cors-filter-1.7.jar,java-property-utils-1.9.jar這兩個庫文件,放到lib目錄下
2.項目的web.xml中添加如下配置
<filter>
<filter-name>CORS</filter-name>
<filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>
<init-param>
<param-name>cors.allowOrigin</param-name>
<param-value>*</param-value>
</init-param>
<init-param>
<param-name>cors.supportedMethods</param-name>
<param-value>GET, POST, HEAD, PUT, DELETE</param-value>
</init-param>
<init-param>
<param-name>cors.supportedHeaders</param-name>
<param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
</init-param>
<init-param>
<param-name>cors.exposedHeaders</param-name>
<param-value>Set-Cookie</param-value>
</init-param>
<init-param>
<param-name>cors.supportsCredentials</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CORS</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2.攔截器設置響應頭 --- Spring MVC 4.2以下版本推薦方式
<!-- API 接口跨域配置 -->
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="*"
allowed-methods="POST, GET, OPTIONS, DELETE, PUT"
allowed-headers="Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
allow-credentials="true" />
</mvc:cors>
3.使用Spring MVC 4.2+以上版本的
在對應的接口上添加註解: @CrossOrigin(origins="http://xxx", maxAge = 3600)
34.HttpSessionListener和HttpSessionAttributeListener接口的用法?
1.HttpSessionListener --- 2個方法
sessionCreated() --- 新建一個會話時觸發,可以說是客戶端第一次和服務器交互時觸發
sessionDestroyed() --- 銷燬會話時, 按鈕觸發進行銷燬或配置定時銷燬會話(關閉瀏覽器時不會銷燬)
2.HttpSessionAttributeListener --- 3個方法
attributeAdded() --- 在session中添加對象時觸發此操作,即:調用setAttribute這個方法時候會觸發
attributeRemoved() --- 修改、刪除session中添加對象時觸發,即:調用 removeAttribute這個方法時候會觸發
attributeReplaced() --- 在Session屬性被重新設置時觸發
eg:
統計在線會話數的功能,並讓超時的自動銷燬
web.xml
<listener>
<listener-class>
org.xiosu.listener.onlineListener (實現session監聽器接口的類的名字,包也要寫上)
</listener-class>
</listener>
<!--默認的會話超時時間間隔,以分鐘爲單位 -->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
OnlineListener.java
public class onlineListener implements HttpSessionListener,
HttpSessionAttributeListener {
// 參數
ServletContext sc;
ArrayList list = new ArrayList();
// 新建一個session時觸發此操作
public void sessionCreated(HttpSessionEvent se) {
sc = se.getSession().getServletContext();
System.out.println("新建一個session");
}
// 銷燬一個session時觸發此操作
public void sessionDestroyed(HttpSessionEvent se) {
System.out.println("銷燬一個session");
if (!list.isEmpty()) {
list.remove((String) se.getSession().getAttribute("userName"));
sc.setAttribute("list", list);
}
}
// 在session中添加對象時觸發此操作,在list中添加一個對象
public void attributeAdded(HttpSessionBindingEvent sbe) {
list.add((String) sbe.getValue());
System.out.println(sbe.getValue());
sc.setAttribute("list", list);
}
// 修改、刪除session中添加對象時觸發此操作
public void attributeRemoved(HttpSessionBindingEvent arg0) {
System.out.println("5555555");
}
public void attributeReplaced(HttpSessionBindingEvent arg0) {
System.out.println("77777777");
}
}
eg:會話計數器
https://github.com/appfuse/appfuse/blob/master/web/common/src/main/java/org/appfuse/webapp/listener/UserCounterListener.java
35.4種提交表單的方式 http://www.aikaiyuan.com/6324.html
base64與byte[]相互轉換:
import sun.misc.BASE64Decoder;//將base64轉換爲byte[]
import sun.misc.BASE64Encoder;//轉byet[]換爲base64
eg:
// 定義一個BASE64Encoder
BASE64Encoder encode = new BASE64Encoder();
// 將byte[]轉換爲base64
String base64 = encode.encode("五筆字型電子計算機".getBytes());
// 輸出base64
System.out.println(base64);
// 新建一個BASE64Decoder
BASE64Decoder decode = new BASE64Decoder();
// 將base64轉換爲byte[]
byte[] b = decode.decodeBuffer(base64);
// 輸出轉換後的byte[]
System.out.println(new String(b));
36.失敗重試
public class Test1{
public static void main(String[] args) {
new Test1().comparePrice(6, 3);
}
/*
* 3次嘗試,如果有一次成功,則不再嘗試,如果有異常,會重試3次
*
*/
public String comparePrice(int m, int n) {
int i=3;
while(i-->0){
System.out.println("第"+(3-i)+"次");
try {
int a = m/n;
return "success";
} catch (Exception e) {
System.out.println("出現異常");
}
}
return "error";
}
}
37.關於有返回值的多線程應用問題:
Future --- Callable
FutureTask --- Callable
eg;
對list遍歷,每個item都要請求一次網絡,可通過Future--Callable,用線程池方式,list遍歷中多線程調用
注意:
1.線程池可以JDK自帶的,也可用Spring的線程池
2.先通過線程池提交Callable任務,然後將返回的Future或FutureTask存放到一個List集合中
3.遍歷Future任務,通過調用future.get()得到每個線程的返回值,將多個線程的返回值組裝即可
public List<JdProductWare> getSkuWareDetail(List<JdProductWare> list) throws JdException {
//創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
List<JdProductWare> result = new ArrayList<>();
List<Future<JdProductWare>> furuteList = new ArrayList<Future<JdProductWare>>();
try{
//遍歷List,每個item都用一個線程執行,同時將線程的Future存入一個List集合
for(JdProductWare jdProductWare:list){
Future<JdProductWare> future = executorService.submit(new SkuDetailTask(url,accesstoken, appkey, appsecret, jdProductWare));
furuteList.add(future);
}
//遍歷Future集合,得到每個線程的返回值,並將返回值存入指定結果集
for(Future<JdProductWare> future:furuteList){
JdProductWare ware = future.get();
result.add(ware);
}
//銷燬線程池
executorService.shutdown();
}catch(Exception e){
LOG.info("獲取Sku的商品title和sku子標題出錯,【{}】", e.getMessage());
}
return result;
}
eg:
/*
* 景點操作控制器
*/
@Controller
@RequestMapping("/menpiao/")
public class MenPiaoScenicController {
private final static Logger LOG = LoggerFactory.getLogger(MenPiaoScenicController.class);
@Autowired
private MenPiaoSecnicService secnicService;
/*
* 推送京東景點信息
*/
@RequestMapping(value = "/send/{holidayId}", method = RequestMethod.GET)
public MenPiaoScenicInfo sendScenicToJd(@PathVariable("holidayId") String holidayId) throws JdException {
MenPiaoScenicInfo info = secnicService.sendSenicInfo(holidayId);
return info;
}
/*
* 使用異步多線程方式推送京東景點信息 holidayIds:多個holidayId 用 , 分隔
*/
@RequestMapping(value = "/send/batch/{holidayIds}", method = RequestMethod.GET)
public List<MenPiaoScenicInfo> sendScenicBatch(@PathVariable("holidayIds") String holidayIds) {
String ids[] = holidayIds != null ? holidayIds.split(",") : null;
List<MenPiaoScenicInfo> list = new ArrayList<>();
List<Future<MenPiaoScenicInfo>> futureList = new ArrayList<>();
try{
// 創建線程池
ExecutorService executorService = Executors.newFixedThreadPool(50);
for (String id : ids) {
futureList.add(executorService.submit(new sendScenicTask(secnicService, id)));
}
//遍歷Future集合,得到每個線程的返回值,並將返回值存入指定結果集
for(Future<MenPiaoScenicInfo> future:futureList){
MenPiaoScenicInfo info = future.get();
list.add(info);
}
//銷燬線程池
executorService.shutdown();
}catch(Exception e){
LOG.info("獲取Sku的商品title和sku子標題出錯,【{}】", e.getMessage());
}
return list;
}
}
class sendScenicTask implements Callable<MenPiaoScenicInfo> {
private MenPiaoSecnicService scenicService;
private String holidayId;
public sendScenicTask() {
super();
}
public sendScenicTask(MenPiaoSecnicService scenicService, String holidayId) {
super();
this.scenicService = scenicService;
this.holidayId = holidayId;
}
public MenPiaoSecnicService getScenicService() {
return scenicService;
}
public void setScenicService(MenPiaoSecnicService scenicService) {
this.scenicService = scenicService;
}
public String getHolidayId() {
return holidayId;
}
public void setHolidayId(String holidayId) {
this.holidayId = holidayId;
}
@Override
public MenPiaoScenicInfo call() throws Exception {
return scenicService.sendSenicInfo(holidayId);
}
}
38.MyBatis傳入多個參數(太多了可以封裝成Vo,少於3個的情況),3種方式?
1.Map傳遞
Dao層:
TicketOrderCoupon findOrderCoupon(Map<String, Long> map);
Mapper.xml:
<select id="findOrderCoupon" resultMap="BaseResultMap" parameterType="java.util.Map">
select
<include refid="Base_Column_List" />
from ticket_order_coupon
where sku_id = #{skuId,jdbcType=BIGINT}
and order_id = #{orderId,jdbcType=BIGINT}
</select>
Service層:
Map<String, Long> map = new HashMap<String, Long>();
map.put("skuId", item.getOutSkuId());
map.put("orderId", item.getOutOrderId());
TicketOrderCoupon orderCoupon = orderCouponMapper.findOrderCoupon(map);
2. 參數佔位符
DAO層:
Public User selectUser(String name,String area);
Mapper.xml:
<select id="selectUser" resultMap="BaseResultMap">
select * from user_user_t where user_name = #{0} and user_area=#{1}
</select>
其中:
#{0}代表接收的是dao層中的第一個參數,#{1}代表dao層中第二參數,更多參數一致往後加即可
3.參數標識
Dao層:
List<TicketOrderLog> selectLogList(@Param("type") String type, @Param("status") Integer status);
Mapper.xml:
<select id="selectLogList" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM ticket_order_log
WHERE
<![CDATA[ retry < 3 ]]>
and status = #{status,jdbcType=TINYINT}
<if test="type != null">
and type = #{type,jdbcType=VARCHAR}
</if>
</select>
注意:
@Param註解 org.apache.ibatis.annotations.Param
39.MyBatis批量操作和緩存,表關聯
批量操作參考:http://blog.csdn.net/u012562943/article/details/50425747
緩存參考:http://blog.csdn.net/u012562943/article/details/50403163
表關聯:http://blog.csdn.net/u012562943/article/details/50403144
40.Redis配置主從複製 + Spring Boot 整合Redis集羣
主Master:redis.conf
port 6379
maxmemory-policy noeviction
appendonly yes
appendfilename "appendonly.aof"
requirepass mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
從Slave1:redis.conf
port 6380
slaveof 127.0.0.1 6379 #主redis的IP port(注意:用IP不要用localhost)
slave-read-only no #slave是否只讀
masterauth mypass
#cluster-enabled yes
#cluster-config-file nodes.conf
#cluster-node-timeout 5000
#appendonly yes
Spring Boot 連接Redis集羣
1.Ubuntu下創建redis集羣
1.源碼安裝redis,設置redis.conf中的cluster-enabled yes ,並拷貝3份分別爲redis1, redis2, redis3
# redis.conf
bind node1 # for node1
cluster-enabled yes
cluster-node-timeout 5000
2.啓動3個redis節點:
./redis-server ../redis.conf --port 6379
./redis-server ../redis.conf --port 6380
./redis-server ../redis.conf --port 6381
說明:
1.調試信息最重要的一行:
No cluster configuration found, I'm a1eec932d923b55e23a5fe6a488ed7a97e27c826
這表示我們的redis服務器正在運行在cluster mode
3.連接啓動的3個節點
node1 6379
node1 6380
node1 6381
說明:
它們都處於失聯狀態,我們現在開始配置將它們彼此連接起來,Redis有一個連接節點的工具稱爲redis-trib.rb.
它是一個ruby文件,需要 redis gem被安裝
1.安裝ruby
sudo apt-get install ruby
sudo apt-get install rubygems #如果可以執行gem命令,則不需要此步驟
2.安裝redis
gem install redis
3.執行創建集羣命令
./redis-trib.rb create 127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
每個節點負責數據的1/3,鍵入 'yes'
4.連接其中的一個節點,查看redis集羣狀態
src/redis-cli -h node2 cluster nodes
./redis-cli -p 6379
>cluster info
5.關於Redis集羣節點的管理:
參考:blog.51yip.com/nosql/1726.html
2.Spring Boot + Redis集羣
1.添加pom依賴
<!-- Spring data redis, 通過配置使用Redis集羣 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.添加配置文件
application.yml 或 application.properties中添加redis集羣的配置
spring:
redis:
cluster:
nodes:
- 127.0.0.1:6379
- 127.0.0.1:6380
- 127.0.0.1:6381
3.注入RedisTemplate,操作redis集羣
@Autowire
private RedisTemplate redisTemplate;
4.啓動Spring Boot項目,驗證數據
41.關於MyBatis的接口註解實現:
1.insert方法,返回操作對象保存後的id --- 即:這裏的 User 在執行save(user)方法後,user中的id會被自動賦值
/* 下面的註解,可以在操作完save方法後,得到保存後的id
* @SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
*/
@Insert("insert into micro_user(name, username, password, salt) values(#{name},#{username}, #{password}, #{salt})")
@SelectKey(before=false,keyProperty="id",resultType=Long.class,statementType=StatementType.STATEMENT,statement="SELECT LAST_INSERT_ID() AS id")
int save(User user);
2.註解版批量保存
/*
* 批量添加 --- 註解版
*/
@InsertProvider(type=UserMapperProvider.class, method = "batchInsert")
Integer batchSave(List<User> list);
public static class UserMapperProvider{
public String batchInsert(Map<String, List<User>> map) {
List<User> list = map.get("list");
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.append("insert into micro_user(name, username, password, salt) values ");
MessageFormat messageFormat = new MessageFormat("(#'{'list[{0}].name},#'{'list[{0}].username},#'{'list[{0}].password},#'{'list[{0}].salt})");
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(messageFormat.format(new Integer[]{i}));
stringBuilder.append(",");
}
stringBuilder.setLength(stringBuilder.length() - 1);
return stringBuilder.toString();
}
}
3.註解版查詢
@Select("select * from micro_user where del_flag = 0")
List<User> findAll();
@Results(
{
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name"),
@Result(column = "username", property = "username"),
@Result(column = "password", property = "password"),
@Result(column = "salt", property = "salt"),
@Result(column = "create_time", property = "createTime"),
@Result(column = "login_time", property = "loginTime")
})
@Select("select * from micro_user where username = #{username}")
User findByUsername(String username);
42.Jenkins相關:
http://10.10.30.116:8888/jenkins/ 登錄名,密碼:admin admin
1.配置文件路徑:/root/.jenkins/
2.初始密碼:/root/.jenkins/secrets/initialAdminPassword
3.安裝參考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
jenkins+github持續集成:
參考:
http://www.jianshu.com/p/22b7860b4e81
http://www.jianshu.com/p/b2ed4d23a3a9
4.Jenkins無法下載插件的解決方法:http://blog.csdn.net/russ44/article/details/52266953
常見的無法安裝插件下載地址:
http://updates.jenkins-ci.org/download/plugins/
步驟:
從http://10.10.30.116:8888/jenkins/updateCenter/ 中查看爲安裝成功的插件,進行手動安裝即可
安裝順序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
8.deploy
9.email-ext
5.常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
6.Jenkins構建svn項目
1.General : 填寫項目名稱和描述
2.源碼管理:
選 Subversion
Modules:
Repository URL:http://boy.tuniu.com/svn/JDB/PPLA/test/jd-plat
Credentials: 填svn的賬號密碼
3.構建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals: clean package -Dmaven.test.skip=true (先clean再打包)
7.Jenkins構建git項目(github) --- 配置參考:http://10.10.30.116:8888/jenkins/job/cloudDemo1/
1.General : 填寫項目名稱和描述
2.源碼管理:
選Git
Repositories:
Repository URL:https://github.com/jayfeihe/spring-cloud-demo.git
Credentials: 填Github的賬號密碼
Branches : 填寫分支(默認:*/master)
源碼庫瀏覽器: githubweb
URL: https://github.com/jayfeihe/spring-cloud-demo/
3.構建觸發器: Build when a change is pushed to GitHub
4.構建:
Invoke top-level Maven targets
Maven Version: maven-3.3.9 (Maven配置)
Goals: clean package -Dmaven.test.skip=true (先clean再打包)
點擊高級:
pom: workspace/CloudShopEurekaServer (如果git下有多個項目,可以選擇基於哪一個項目下的pom構建)
8.配置Tomcat登錄名和密碼 Tomcat --- > conf --> tomcat-users.xml
添加登錄名和密碼: deploy tomcat
<role rolename="tomcat"/>
<role rolename="role1"/>
<role rolename="manager-gui" />
<role rolename="manager-script" />
<role rolename="manager-status" />
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="role1" password="tomcat" roles="role1"/>
<user username="deploy" password="tomcat" roles="manager-gui,manager-script,manager-status" />
42.WebStorm開發Node.js啓用代碼提示:
File -> Settings... -> Languages&Frameworks -> Node.js and NPM 頁
Code Assistatant啓用Node.js庫的代碼提示即可(點擊Enable按鈕)
43.WebStorm/Idea 與 Eclipse 快捷鍵對比大全
http://blog.csdn.net/quincylk/article/details/18256697
默認配置-Eclipse的常用快捷鍵對照表
查找/代替
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+shift+N ctrl+shift+R 通過文件名快速查找工程內的文件(必記)
ctrl+shift+alt+Nctrl+shift+alt+N 通過一個字符快速查找位置(必記)
ctrl+F ctrl+F 在文件內快速查找代碼
F3 ctrl+K查找下一個
shift+F3 ctrl+shift+K 查找上一個
ctrl+R ctrl+F 文件內代碼替換
ctrl+shift+R 指定目錄內代碼批量替換
ctrl+shift+F ctrl+H 指定目錄內代碼批量查找
界面操作
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+shift+A ctrl+shift+A 快速查找並使用編輯器所有功能(必記)
alt+[0-9] alt+[0-9] 快速拆合功能界面模塊
ctrl+shift+F12ctrl+shift+F12 最大區域顯示代碼(會隱藏其他的功能界面模塊)
alt+shift+F alt+shift+F 將當前文件加入收藏夾
ctrl+alt+s ctrl+alt+s 打開配置窗口
ctrl+tab ctrl+tab 切換代碼選項卡(還要進行此選擇,效率差些)
alt+←/→ alt+←/→ 切換代碼選項卡
ctrl+F4 ctrl+F4 關閉當前代碼選項卡
代碼編輯
Webstorm快捷鍵 Eclipse快捷鍵 說明
ctrl+D ctrl+shift+↑ 複製當前行
ctrl+W alt+shift+↑ 選中單詞
ctrl+←/→
ctrl+←/→
以單詞作爲邊界跳光標位置
alt+insert alt+insert 新建一個文件或其他
ctrl+alt+L ctrl+alt+L 格式化代碼
shift+tab/tab shift+tab/tab 減少/擴大縮進(可以在代碼中減少行縮進)
ctrl+Y ctrl+D 刪除一行
shift+enter shift+enter 重新開始一行(無論光標在哪個位置)
導航
Webstorm快捷鍵 Eclipse快捷鍵 說明
esc esc進入代碼編輯區域
alt+F1 alt+F1 查找代碼在其他界面模塊的位置,頗爲有用
ctrl+G ctrl+L 到指定行的代碼
ctrl+]/[ ctrl+]/[ 光標到代碼塊的前面或後面
alt+up/down ctrl+shift+up/down 上一個/下一個方法
44.新的架構模式:
前後端分離: 參考:http://developer.51cto.com/art/201404/435984.htm
前端:負責View和Controller層
後端:負責Model層、業務處理、數據等
前後端的接口
前端: (Front UI View展示) (Node中間層 Model + Controller處理) (Restful、SOAP)
展示(Angular、React)<--- 數據獲取(Node) <--- 後端數據接口
後端:
數據接口 <--- 業務邏輯 <--- 緩存+DB
45.Filebeat5+Kafka+ELK Docker搭建日誌系統: http://www.jianshu.com/p/9dfac37885cb
Docker筆記:http://www.jianshu.com/users/110149e3a887/latest_articles
46.分隔List集合,按指定大小,將集合分成多個
/**
* 常用工具類
* @author hetiewei(賀鐵偉)
*
*/
public class JayCommonUtil {
/**
* 按指定大小,分隔集合,將集合按規定個數分爲n個部分
*
* @param list
* @param len
* @return
*/
public static List<List<?>> splitList(List<?> list, int len) {
if (list == null || list.size() == 0 || len < 1) {
return null;
}
List<List<?>> result = new ArrayList<List<?>>();
int size = list.size();
int count = (size + len - 1) / len;
for (int i = 0; i < count; i++) {
List<?> subList = list.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
result.add(subList);
}
return result;
}
}
47.Spring Data MongoDB註解
@Id - 文檔的唯一標識,在mongodb中爲ObjectId,它是唯一的,通過時間戳+機器標識+進程ID+自增計數器(確保同一秒內產生的Id不會衝突)構成。
@Document - 把一個java類聲明爲mongodb的文檔,可以通過collection參數指定這個類對應的文檔。
@DBRef - 聲明類似於關係數據庫的關聯關係。ps:暫不支持級聯的保存功能,當你在本實例中修改了DERef對象裏面的值時,單獨保存本實例並不能保存DERef引用的對象,它要另外保存,如下面例子的Person和Account。
@Indexed - 聲明該字段需要索引,建索引可以大大的提高查詢效率。
@CompoundIndex - 複合索引的聲明,建複合索引可以有效地提高多字段的查詢效率。
@GeoSpatialIndexed - 聲明該字段爲地理信息的索引。
@Transient - 映射忽略的字段,該字段不會保存到mongodb。
@PersistenceConstructor - 聲明構造函數,作用是把從數據庫取出的數據實例化爲對象。該構造函數傳入的值爲從DBObject中取出的數據
eg:
@Document(collection="person")
@CompoundIndexes({
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
})
public class Person<T extends Address> {
@Id
private String id;
@Indexed(unique = true)
private Integer ssn;
private String firstName;
@Indexed
private String lastName;
private Integer age;
@Transient
private Integer accountTotal;
@DBRef
private List<Account> accounts;
private T address;
public Person(Integer ssn) {
this.ssn = ssn;
}
@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
}
48. MongoDB根據已有數據文檔,批量造大數據,(指數級成倍增長)
原理:
1.將已有文件放到遊標
2.遍歷遊標得到文檔數組
3.批量插入文檔數組
> var cur = db.ticket_order_detail.find({},{_id:0,id:1,outOrderId:1,fullName:1,phone:1,address:1,orderRemark:1,venderRemark:1,bossRemark:1});
> var data =[];
> for(var i=0;i<cur.size();i++){ data.push(cur[i]); }
> db.ticket_order_detail.insert(data);
MongoDB數據備份與還原:
一、集合備份
mongoexport -h 集合所在服務器地址 -d 數據庫 -c 集合 -o 本地文件存儲位置
注:文件格式可選json、cvs等
官方參數說明:mongoexport --help
二、集合還原
mongoimport --host 目標IP --port 目標端口 --db 目標數據庫 --collection 目標集合 --file 備份文件所在位置
官方參數說明:mongoimport --help
49.MongoDB內存使用過大的問題,如果不設置默認會佔用系統總內存的50%~80%,
內存問題官方參考:https://docs.mongodb.com/manual/faq/storage/
設置 --wiredTigerCacheSizeGB 參數可防止佔用過多內存
eg:最多佔用2G內存
mongod -dbpath=../db --wiredTigerCacheSizeGB 2
說明:
1.如果是在Docker容器中運行,該參數不能超過容器分配的內存,建議:1或2G
解決MongoDB佔用內存過duo的幾個方法:!!!
1.啓動時,調整佔用內存大小(默認佔用主機內存的50%)
mongod -dbpath=../db --wiredTigerCacheSizeGB 1
2.壓縮集合,減少內存佔用
//進入集合所在的db
use db
//執行命令,壓縮指定集合
db.runCommand({compact:'collection_name'})
3.Linux下執行切換日誌命令 --- MongoDB內存有很大一部分跟日誌有關
use admin
db.runCommand("logRotate");
通過配置文件方式啓動MongoDB
./mongod --config /data/mongodb3/mongo.conf
monggo.conf內容:
storageEngine = wiredTiger
wiredTigerCacheSizeGB = 2
syncdelay = 30
wiredTigerCollectionBlockCompressor = snappy
port=38019
dbpath=/data/mongodb30/db
oplogSize=2048
logpath=/data/mongodb30/logs/mongodb.log
logappend=true
fork=true
rest=true
journal = true
解析:
storageEngine 是設置存儲引擎;wiredTigerCacheSizeGB 是設置mongodb存儲引擎所用的內容,默認爲系統內存的50%;
syncdelay 是設置從內存同步到硬盤的時間間隔,默認爲60秒,可以設置的少一些,在mongodb故障時,丟失的日誌會少一些;
wiredTigerCollectionBlockCompressor 是設定壓縮策略 snappy 是一種壓縮速度非常快的壓縮策略
50.使用MongoExpress操作MongoDB數據庫 --- Web頁面操作MongoDB
1.安裝mongo-express
npm install -g mongo-express
2.以管理員賬號啓動
mongo-express -a -u username -p password
連接到指定的數據庫
mongo-express -u username -p password -d database
連接到遠程庫
mongo-express -u username -p password -d database -H mongoDBHost -P mongoDBPort
3.操作數據庫
localhost:8081
4.Docker使用
$ docker run -it --rm -p 8081:8081 --link YOUR_MONGODB_CONTAINER:mongo mongo-express
51.使用elasticsearch-HQ 管理ElasticSearch
1.啓動ElasticSearch,並開啓跨域支持 --- elasticsearch.yml中http模塊下配置:
#是否支持跨域,默認false
http.cors.enabled: true
#支持跨域的路徑
http.cors.allow-origin: http://localhost
2.將elasticsearch-HQ解壓到指定路徑:eg: D:\new_tech\ELK\royrusso-elasticsearch-HQ-6a0f138
3.使用Nginx訪問elasticsearch-HQ,配置Nginx如下:
location / {
#訪問的靜態資源根路徑
root D:/new_tech/ELK/royrusso-elasticsearch-HQ-6a0f138;
#默認頁面
index index.html index.htm;
#允許跨域訪問
add_header 'Access-Control-Allow-Origin' '*';
}
4.訪問 http://localhost/即可
5.連接到指定的ElasticSearch,按提示,在左上角中填寫 http://localhost:9200,連接
52.Spring MVC 配置各種模板解析引擎
http://blog.csdn.net/zhuzhoulin/article/details/52371530
53.Spring Boot中處理靜態資源(自定義資源映射)
1.Spring Boot默認使用resources下的靜態資源進行映射
2.自定義資源映射的2種方式:
1.實現類繼承 WebMvcConfigurerAdapter 並重寫方法 addResourceHandlers
2.
eg:
以 /myres/* 映射到 classpath:/myres/* 爲例的代碼處理爲
方式1:
@Configuration
public class MyWebAppConfigurer
extends WebMvcConfigurerAdapter {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
super.addResourceHandlers(registry);
}
}
解析:
1.addResourceLocations 的參數是動參,可以這樣寫 addResourceLocations(“classpath:/img1/”, “classpath:/img2/”, “classpath:/img3/”);
2.如果我們要指定一個絕對路徑的文件夾(如 D:/data/api_files ),則只需要使用 addResourceLocations 指定即可
3.可以直接使用addResourceLocations 指定磁盤絕對路徑,同樣可以配置多個位置,注意路徑寫法需要加上file:
registry.addResourceHandler("/api_files/**").addResourceLocations("file:D:/data/api_files");
方式2:
#Spring MVC對靜態文件的配置
spring.resources.static-locations=classpath:/static/
54.Spring Boot 中,添加入參和出參解析器
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {
/*
* 配置簡易的View, 默認頁面
* /, /index都指向 index頁面
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("/index");
registry.addViewController("/index").setViewName("/index");
registry.addViewController("/login").setViewName("/user/login");
}
/**
* 配置mvc請求入參解析器
* @param argumentResolvers
*/
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
//添加入參Base64解碼
argumentResolvers.add(new JayRequestBase64Resolver());
//添加入參解密
// argumentResolvers.add(new JayRequestDecryptResovler());
}
/**
* 配置mvc參數返回解析器
* @param returnValueHandlers
*/
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
super.addReturnValueHandlers(returnValueHandlers);
//添加出參Base64編碼
returnValueHandlers.add(new JayResponseBase64Resovler());
//添加出參加密
// returnValueHandlers.add(new JayResponseEncryptResovler());
}
}
55.Java中創建對象的5種方式
1、用new語句創建對象,這是最常見的創建對象的方法。
2、通過工廠方法返回對象,如:String str = String.valueOf(23);
3、運用反射手段,調用java.lang.Class或者java.lang.reflect.Constructor類的newInstance()實例方法。如:Object obj = Class.forName("java.lang.Object").newInstance();
4、調用對象的clone()方法。 implements Cloneable
5、通過I/O流(包括反序列化),如運用反序列化手段,調用java.io.ObjectInputStream對象的 readObject()方法。
56.MyBatis返回數字類型結果:
將resultMap改爲resultType=Java對應的數字類型即可
eg:
<select id="selectByHolidayId" resultType="java.lang.Integer"
parameterType="java.lang.Integer">
select count(id) from t_menpiao_product_info
where holiday_id=#{holidayId,jdbcType=INTEGER}
</select>
57.JDK動態代理和cglib字節碼技術代理的區別?
1.JDK動態代理:
1.靜態代理 --- 代理對象和目標對象實現了相同的接口,目標對象作爲代理對象的一個屬性,
具體接口實現中,可以調用目標對象相應方法前後加上其他業務處理邏輯
2.JDK動態代理只能針對實現了接口的類生成代理
2.CGLIB代理 --- 通過字節碼技術,爲目標對象生成一個與其功能一樣的子類
1.針對類實現代理
2.主要是對指定的類生產一個子類,覆蓋其中的所有方法
3.被代理類或方法不能聲明爲final
3.區別:
1.JDK動態代理只能對實現了接口的類生成代理, 動態代理只能對於接口進行代理
2.cglib針對類實現代理,主要是對指定的類生成一個子類,覆蓋中的方法,因爲是繼承,所以該類或方法最好不要聲明成final ,final可以阻止繼承和多態
3.Spring實現中,如果有接口,默認使用JDK動態代理,如果目標對象沒有實現接口,使用cglib代理,
如果目標對象實現了接口,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
4.動態代理的應用
AOP(Aspect-OrientedProgramming,面向切面編程),AOP包括切面(aspect)、通知(advice)、連接點(joinpoint),實現方式就是通過對目標對象的代理在連接點前後加入通知,完成統一的切面操作。
實現AOP的技術,主要分爲兩大類:
一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;
二是採用靜態織入的方式,引入特定的語法創建“方面”,從而使得編譯器可以在編譯期間織入有關“方面”的代碼。
Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。
默認的策略是如果目標類是接口,則使用JDK動態代理技術,如果目標對象沒有實現接口,則默認會採用CGLIB代理。
如果目標對象實現了接口,可以強制使用CGLIB實現代理(添加CGLIB庫,並在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。
5.參考:http://www.cnblogs.com/linghu-java/p/5714769.html
58.Spring boot + redis 實現消息隊列 --- 可用於分佈式系統
PK Spring的事件監聽器 --- 適合單系統(非分佈式)
1.依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.redis作爲消息隊列的配置
@Configuration
public class RedisConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 定義redis作爲消息隊列時的容器
* @param connectionFactory
* @param listenerAdapter
* @return
*/
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic("queue1"));
return container;
}
//定義一個隊列消息監聽器,指定監聽器類和監聽方法
@Bean
MessageListenerAdapter listenerAdapter(Receiver receiver){
return new MessageListenerAdapter(receiver, "receiveMessage");
}
//定義一個監聽器對象
@Bean
Receiver receiver(CountDownLatch latch){
return new Receiver(latch);
}
@Bean
CountDownLatch latch() {
return new CountDownLatch(1);
}
}
3.消息監聽器
/**
* 在任何一個基於消息的應用中,都有消息發佈者和消息接收者(或者稱爲消息訂閱者)。
* 創建消息的接收者,我們只需一個普通POJO,在POJO中定義一個接收消息的方法即可
* Created by hetiewei on 2017/3/9.
*/
/*
該Receiver類被註冊爲一個消息監聽者,處理消息的方法可以任意命名,
給Receiver的構造函數通過@AutoWired標註注入了一個CountDownLatch實例,當接收到消息時,調用countDown()方法
*/
public class Receiver {
private Logger logger = LoggerFactory.getLogger(getClass());
private CountDownLatch latch;
@Autowired
public Receiver(CountDownLatch latch){
this.latch = latch;
}
public void receiveMessage(String message){
logger.info("Received <"+message+">");
latch.countDown();
}
}
4.發送消息
@RestController
@RequestMapping("/redis/queue")
public class RedisQueueController {
@Autowired
private CountDownLatch latch;
@Autowired
private StringRedisTemplate template;
/**
* 發送消息
* @param msg
* @return
*/
@GetMapping("/send/{msg}")
public String sendMsg(@PathVariable("msg") String msg) throws InterruptedException {
template.convertAndSend("queue1", msg);
latch.await();
return msg;
}
@GetMapping("/batch/send")
public String batchSend() throws InterruptedException {
for (int i=0;i<1000;i++){
template.convertAndSend("queue1", "msg"+i);
latch.await();
}
return "success";
}
}
59.關於Spring 聲明式事務的原理
參考:http://yemengying.com/2016/11/14/something-about-spring-transaction/
Spring的聲明式事務:
1.JavaConfig方法 --- 在需要管理事務的類或方法上添加 @Transactional註解,然後在配置類上添加 @EnableTransactionManagement註解
2.Xml方式 --- 添加 <tx:annotation-driven />
Spring會利用Aop在相關方法調用的前後進行事務管理
問題:
public class JayServiceImpl implements JayService {
public void A(List<Giraffe> giraffes) {
for (Giraffe giraffe : giraffes) {
B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
說明:
Service中A方法調用B方法,方法A沒有事務管理,方法B採用聲明式事務,通過在方法上聲明 @Transactional註解來做事務管理
問題:
Junit 測試方法 A 的時候發現方法 B 的事務並沒有開啓, 而直接調用方法 B 事務是正常開啓的???
// 沒有開啓事務
@Test
public void testA() {
giraffeService.A();
}
// 正常開啓事務
@Test
public void testB() {
giraffeService.B();
}
}
原理分析:
Spring在加載目標Bean時,會爲聲明瞭@Transactional的Bean創建一個代理類,而目標類本身並不能感知到代理類的存在,
調用通過Spring上下文注入的Bean的方法,而不是直接調用目標類的方法
即:
先調用代理類的方法,代理類再調用目標類的方法
Calling Code
--call--> Proxy --->foo()
---> Pojo --> pojo.foo()
對於加了@Transactional註解的方法,在調用代理類方法時,會先通過攔截器 TransactionInterceptor開啓事務,
然後再調用目標類的方法,最後在調用結束後, TransactionInterceptor會提交或回滾事務
問題解析:
對於第一段的代碼,我在方法 A 中調用方法 B,實際上是通過“this”的引用,也就是直接調用了目標類的方法,而非通過 Spring 上下文獲得的代理類,所以。。。事務是不會開啓滴
解決方法:
通過實現ApplicationContextAware接口獲得 Spring 的上下文,(或自動注入Context對象),然後獲得目標類的代理類,通過代理類的對象,調用方法 B,即可
public class GiraffeServiceImpl implements GiraffeService,ApplicationContextAware{
@Setter
private ApplicationContext applicationContext;
public void A(List<Giraffe> giraffes) {
GiraffeService service = applicationContext.getBean(GiraffeService.class);
for (Giraffe giraffe : giraffes) {
service.B(giraffe);
}
}
@Transactional("transactionManager")
public void B(Giraffe giraffe) {
// Step 1: update something
// Step 2: insert something
// Step 3: update something
}
}
60.Java類加載機制
裝載 ---> 鏈接(驗證 --> 準備 --> 解析) ---> 初始化
1.JVM類加載機制:
裝載:
1.找到該類型的class文件,產生一個該類型的class文件二進制數據流(ClassLoader需要實現的loadClassData()方法)
2.解析該二進制數據流爲方法區內的數據結構
3.創建一個該類型的java.lang.Class實例
最終:通過defineClass()創建一個Java類型對象(Class對象)
找到二進制字節碼,並加載到JVM中
JVM通過類全限定名(包名.類名) + 類加載器 完成類的加載,生成類對應的Class對象
鏈接:
驗證:
負責對二進制字節碼進行校驗、類信息是否符合JVM規範,有沒有安全問題、對class文件長度和類型進行檢查
參考:http://www.importnew.com/17105.html
準備:
初始化類中靜態變量、並將其初始化爲默認值 --- 只初始化靜態變量默認值 !!!,給其類變量賦值發生在初始化階段!!!
對於final類型的變量,準備階段直接賦初始值
該內存分配發生在方法區
解析:
解析類中調用的接口、類、字段、方法的符號引用,把虛擬機常量池中的符號引用轉換爲直接引用
初始化:
1.對static類變量指定初始值!!!(2種方式:一種是通過類變量的初始化語句,一種是靜態初始化語句)
2.一個類的初始化需要先初始化其父類,並遞歸初始化其祖先類
2.JVM必須在每個類或接口主動使用時進行初始化:
主動使用的情況:
1.創建類的實例(無論是new、還是反射、克隆、序列化創建的)
2.使用某個類的靜態方法
3.訪問某個類或即可的靜態字段
4.調用Java API中的某些反射方法
5.初始化某個類的子類(先初始化其父類)
6.啓動某個標明爲啓動類的類(含main()方法)
主動使用會導致類的初始化,其超類均將在該類的初始化之前被初始化,但通過子類訪問父類的靜態字段或方法時,對於子類(或子接口、接口的實現類)來說,這種訪問就是被動訪問,或者說訪問了該類(接口)中的不在該類(接口)中聲明的靜態成員
3.創建對象時,類中各成員的執行順序:
父靜態塊 <-- 子靜態塊 <-- 父普通代碼塊 <-- 父構造器 <-- 子普通代碼塊 <-- 子構造器
1.父類靜態成員和靜態初始化快,按在代碼中出現的順序依次執行。
2.子類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行。
3. 父類的實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
4.執行父類的構造方法。
5.子類實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
6.執行子類的構造方法。
eg:
public class Test {
public static void main(String[] args) {
Son s = new Son();
}
}
class Parent{
{
System.out.println("parent中的初始化塊");
}
static{
System.out.println("parent中static初始化塊");
}
public Parent(){
System.out.println("parent構造方法");
}
}
class Son extends Parent{
{
System.out.println("son中的初始化塊");
}
static{
System.out.println("son中的static初始化塊");
}
public Son(){
System.out.println("son構造方法");
}
}
結果:
parent中static初始化塊
son中的static初始化塊
parent中的初始化塊
parent構造方法
son中的初始化塊
son構造方法
61.MySQL性能優化
參考:
MySQL性能優化總結:http://www.cnblogs.com/luxiaoxun/p/4694144.html
http://blog.chinaunix.net/uid-29435603-id-4275475.html
1.存儲引擎選擇
參考:http://www.jb51.net/article/38178.htm
MyISAM:
不支持事務處理,爲每個表創建3個文件,分別存儲不同內容
支持表級鎖,表的寫操作會阻塞其他用戶對同一個表的讀和寫操作,併發度低
1.myISAM表的讀操作,不會阻塞其他用戶對同一個表的讀請求,但會阻塞對同一個表的寫請求。
2.myISAM表的寫操作,會阻塞其他用戶對同一個表的讀和寫操作。
3.myISAM表的讀、寫操作之間、以及寫操作之間是串行的
eg:
tb_Demo表,那麼就會生成以下三個文件:
1.tb_demo.frm,存儲表定義;
2.tb_demo.MYD,存儲數據;
3.tb_demo.MYI,存儲索引
適合場景:
1.選擇密集型表 --- MyISAM引擎在篩選大量數據時非常迅速 --- 查詢快
2.插入密集型表 --- 併發插入特性允許同時選擇和插入數據,適合管理:郵件或Web服務器日誌數據
總結:
1.適合做count的計算 (注意:不含where條件的統計,因爲MyISAM會記錄表的行數)
2.插入不頻繁,查詢非常頻繁
3.沒有事務需求
InnoDB: 默認引擎
支持事務處理
引入了行級鎖(併發高)和外鍵約束
不支持全文索引
適合場景:
1.更新密集型表 --- 特別適合處理多重併發的更新請求
2.事務
3.自動災難恢復 --- InnoDB表能夠自動從災難中恢復
4.外鍵約束 --- MySQL支持外鍵的存儲引擎只有InnoDB
5.支持自動增加列 Auto_INCREMNET屬性
6.InnoDB是爲處理巨大數據量時的最大性能設計
總結:
可靠性要求高,需要事務支持,並有較高的併發讀取頻率,適合InnoDB
行鎖機制必然決定了寫入時的更多性能開銷,而它的強項在於多線程的併發處理
表更新和查詢都相當的頻繁,並且表鎖定的機會比較大的情況指定數據引擎的創建
細節和具體實現的差別:
1.InnoDB不支持FULLTEXT類型的索引。
2.InnoDB 中不保存表的具體行數,也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可。注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的。
3.對於AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯合索引。
4.DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除。
5.LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導入數據後再改成InnoDB表,但是對於使用的額外的InnoDB特性(例如外鍵)的表不適用。
另外,InnoDB表的行鎖也不是絕對的,如果在執行一個SQL語句時MySQL不能確定要掃描的範圍,InnoDB表同樣會鎖全表,例如update table set num=1 where name like “%aaa%”
任何一種表都不是萬能的,只用恰當的針對業務類型來選擇合適的表類型,才能最大的發揮MySQL的性能優勢。
存儲引擎選擇依據?
是否需要支持事務;
是否需要使用熱備;
崩潰恢復:能否接受崩潰;
是否需要外鍵支持;
是否需要全文索引
經常使用什麼樣的查詢模式
數據量大小
eg:
需要事務和外鍵約束 -- InnoDB
需要全文索引 -- MyISAM
數據量大,傾向於InnoDB,因爲它支持事務處理和故障恢復,InnoDB可以利用事務日誌進行數據恢復,這會比較快。而MyISAM可能會需要幾個小時甚至幾天來幹這些事,InnoDB只需要幾分鐘
操作數據表的習慣,也會影響性能
eg:
COUNT() 在 MyISAM 表中會非常快,而在InnoDB 表下可能會很痛苦(
因爲InnoDB不保存表的行數,即:執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可,
注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的)
主鍵查詢在InnoDB下非常快,但如果主鍵太長也會導致性能問題
大批的inserts語句在MyISAM下回快一些,但updates語句在InnoDB下更快(尤其在併發量大的時候)
提示InnoDB性能的方法:
InnoDB支持事務,存儲過程,視圖和行級鎖,在高併發下,表現比MyISAM強很多
影響性能的配置:
innodb_flush_log_at_trx_commit 這個選項,如果設置爲1的話,那麼每次插入數據的時候都會自動提交,導致性能急劇下降,應該是跟刷新日誌有關係,設置爲0效率能夠看到明顯提升
當然,同 樣你可以SQL中提交“SET AUTOCOMMIT = 0”來設置達到好的性能
設置innodb_buffer_pool_size能夠提升InnoDB的性能
設置查詢緩存
2.配置文件my.ini參數優化
1.max_connections --- 最大併發連接數,允許的同時客戶連接數, 默認100, 建議根據需求設定,eg:1024
2.query_cache_size=0 --- 查詢緩存,用於緩存select 查詢結果,如果有許多返回相同查詢結果的SELECT查詢,並且很少改變表,可以設置query_cache_size大於0,可以極大改善查詢效率。而如果表數據頻繁變化,就不要使用這個,會適得其反
3.table_cache=256
4.thread_cache_size --- 緩存的最大線程數
5.sort_buffer --- 每個需要進行排序的線程分配該大小的一個緩衝區
6.wait_timeout --- 默認是28800秒,也就是說一個connection空閒超過8個小時,Mysql將自動斷開該connection,通俗的講就是一個連接在8小時內沒有活動,就會自動斷開該連接。 不要設置太長,建議 7200
7.default-storage-engine=INNODB # 創建新表時將使用的默認存儲引擎
配置示例,2G內存,針對站多,抗壓型的設置,最佳:
table_cache=1024 物理內存越大,設置就越大.默認爲2402,調到512-1024最佳
innodb_additional_mem_pool_size=4M 默認爲2M
innodb_flush_log_at_trx_commit=1
(設置爲0就是等到innodb_log_buffer_size列隊滿後再統一儲存,默認爲1)
innodb_log_buffer_size=2M 默認爲1M
innodb_thread_concurrency=8 你的服務器CPU有幾個就設置爲幾,建議用默認一般爲8
key_buffer_size=256M 默認爲218 調到128最佳
tmp_table_size=64M 默認爲16M 調到64-256最掛
read_buffer_size=4M 默認爲64K
read_rnd_buffer_size=16M 默認爲256K
sort_buffer_size=32M 默認爲256K
max_connections=1024 默認爲1210
thread_cache_size=120 默認爲60
query_cache_size=64M
一般:
table_cache=512
innodb_additional_mem_pool_size=8M
innodb_flush_log_at_trx_commit=0
innodb_log_buffer_size=4M
innodb_thread_concurrency=8
key_buffer_size=128M
tmp_table_size=128M
read_buffer_size=4M
read_rnd_buffer_size=16M
sort_buffer_size=32M
max_connections=1024
更多參考:
http://www.cnblogs.com/adolfmc/p/6056392.html
3.Query查詢優化
1.explain sql 查看執行效率,定位優化對象的性能瓶頸
2.永遠用小結果驅動大的結果集
3.儘可能在索引中完成排序
4.只取出自己需要的column,而不是*
5.使用最有效的過濾條件
6.用表連接代替子查詢
7.當只要一行數據時,使用limit 1
8.爲搜索字段建立索引
9.千萬不要ORDER BY RAND(),避免select *
10.儘可能使用NOT NULL
11.開啓查詢緩存,併爲查詢緩存優化查詢語句
eg:
select username from user where add_time >= now()
注意:
1.這樣的語句不會使用查詢緩存,
2.像NOW()和RAND()或是其它的諸如此類的SQL函數都不會開啓查詢緩存,因爲這些函數的返回是會不定的易變的。所以,你所需要的就是用一個變量來代替MySQL的函數,從而開啓緩存
3.修改, 對now()進行處理,只取年月日 yyyy-MM-dd,變爲一個不衣變的值
62.Java常見的鎖類型有哪些?請簡述其特點。
1、synchronized對象同步鎖:synchronized是對對象加鎖,可作用於對象、方法(相當於對this對象加鎖)、靜態方法(相當於對Class實例對象加鎖,鎖住的該類的所有對象)以保證併發環境的線程安全。同一時刻只有一個線程可以獲得鎖。
其底層實現是通過使用對象監視器Monitor,每個對象都有一個監視器,當線程試圖獲取Synchronized鎖定的對象時,就會去請求對象監視器(Monitor.Enter()方法),如果監視器空閒,則請求成功,會獲取執行鎖定代碼的權利;如果監視器已被其他線程持有,線程進入同步隊列等待。
2、Lock同步鎖:與synchronized功能類似,可從Lock與synchronized區別進行分析:
1、Lock可以通過tryLock()方法非阻塞地獲取鎖而。如果獲取了鎖即立刻返回true,否則立刻返回false。這個方法還有加上定時等待的重載方法tryLock(long time, TimeUnit unit)方法,在定時期間內,如果獲取了鎖立刻返回true,否則在定時結束後返回false。在定時等待期間可以被中斷,拋出InterruptException異常。而Synchronized在獲得鎖的過程中是不可被中斷的。
2、Lock可以通過lockInterrupt()方法可中斷的獲取鎖,與lock()方法不同的是等待時可以響應中斷,拋出InterruptException異常。
3、Synchronized是隱式的加鎖解鎖,而Lock必須顯示的加鎖解鎖,而且解鎖應放到finnally中,保證一定會被解鎖,而Synchronized在出現異常時也會自動解鎖。但也因爲這樣,Lock更加靈活。
4、Synchronized是JVM層面上的設計,對對象加鎖,基於對象監視器。Lock是代碼實現的。
3、可重入鎖:ReentrantLock與Synchronized都是可重入鎖。可重入意味着,獲得鎖的線程可遞歸的再次獲取鎖。當所有鎖釋放後,其他線程纔可以獲取鎖。
4、公平鎖與非公平鎖:“公平性”是指是否等待最久的線程就會獲得資源。如果獲得鎖的順序是順序的,那麼就是公平的。不公平鎖一般效率高於公平鎖。ReentrantLock可以通過構造函數參數控制鎖是否公平。
5、ReentrantReadWriteLock讀寫鎖:是一種非排它鎖, 一般的鎖都是排他鎖,就是同一時刻只有一個線程可以訪問,比如Synchronized和Lock。讀寫鎖就多個線程可以同時獲取讀鎖讀資源,當有寫操作的時候,獲取寫鎖,寫操作之後的讀寫操作都將被阻塞,直到寫鎖釋放。讀寫鎖適合寫操作較多的場景,效率較高。
6、樂觀鎖與悲觀鎖:在Java中的實際應用類並不多,大多用在數據庫鎖上,可參看:http://blog.csdn.net/sdyy321/article/details/6183412
7、死鎖:是當兩個線程互相等待獲取對方的對象監視器時就會發生死鎖。一旦出現死鎖,整個程序既不會出現異常也不會有提示,但所有線程都處於阻塞狀態。死鎖一般出現於多個同步監視器的情況。
63.volatile與automicInteger是什麼?如何使用?
在併發環境中有三個因素需要慎重考量,原子性、可見性、有序性。
voatile 保證了有序性(防止指令衝排序)和變量的內存可見性(每次都強制取主存數據),每次取到volatile變量一定是最新的
volatile主要用於解決可見性,它修飾變量,相當於對當前語句前後加上了“內存柵欄”。使當前代碼之前的代碼不會被重排到當前代碼之後,當前代碼之後的指令不會被重排到當前代碼之前,一定程度保證了有序性。而volatile最主要的作用是使修改volatile修飾的變量值時會使所有線程中的緩存失效,並強制寫入公共主存,保證了各個線程的一致。可以看做是輕量級的Synchronized。詳情可參看:http://www.cnblogs.com/dolphin0520/p/3920373.html。
automicXXX主要用於解決原子性,有一個很經典的問題:i++是原子性的操作碼?答案是不是,它其實是兩步操作,一步是取i的值,一步是++。在取值之後如果有另外的線程去修改這個值,那麼當前線程的i值就是舊數據,會影響最後的運算結果。使用automicXXX就可以非阻塞、保證原子性的對數據進行增減操作。詳情可參看:http://ifeve.com/java-atomic/
volatile原理:
1.volatile可以保證線程可見性,且提供了一定的有序性,但無法保證原子性。
1.保證可見性,不保證原子性
2.禁止指令重排序
2.JVM底層,volatile採用 "內存屏障" 來實現
可見性實現:
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值
synchronize和鎖都可以保證可見性。
線程本身並不直接與主存進行數據交換,而是通過線程的工作內存來完成相應的操作 --- 線程間數據不可見的根本原因!!!
volatile實現可見性,直接從這方面入手,
1.修改volatile變量時,會強制將修改後的值刷新到主內存中
2.修改volatile變量後,會導致其他線程工作內存中對應的變量值失效,再讀取該變量值時,要重新從主內存中讀取
有序性實現:
關於重排序:
編譯器重排序:不改變單線程語義的前提下,可以重新安排語句的執行順序
處理器重排序:不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序
指令重排序對單線程無影響,但會影響多線程的正確性,
JVM如何禁止重排序?
happens-before 原則:保證程序的有序性
參考:http://www.cnblogs.com/chenssy/p/6393321.html
注:在此列舉的只是Java多線程最基礎的知識,也是面試官最常問到的,先打牢基礎,再去探討底層原理或者高級用法,除了這十個問題,在此再推薦一些其他的資料:
JVM底層又是如何實現synchronized的:http://www.open-open.com/lib/view/open1352431526366.html
Java線程池詳解:http://blog.csdn.net/zhangliangzi/article/details/52389766
Java線程池深度解析:http://www.cnblogs.com/dolphin0520/p/3932921.html
ConcurrentHashMap原理分析:http://www.cnblogs.com/ITtangtang/p/3948786.html
Java阻塞隊列詳解:http://ifeve.com/java-blocking-queue/
64.MyBatis中timestamp時間類型,在Spring MVC出參時無法轉爲正確的時間類型?
在xml中配置: javaType爲 java.sql.Timestamp
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" javaType="java.sql.Timestamp"/>
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" javaType="java.sql.Timestamp"/>
65.Datatable自定義搜索
//自定義搜索,每次只能根據一個維度進行搜索(按渠道或產品類型)
$("#channel_select,#brand_select").change(function(){
var tsval = $(this).val()
table.search(tsval, false, false).draw();
});
66.synchronized和Lock的底層實現原理?
參考:http://www.open-open.com/lib/view/open1352431526366.html
鎖原理:http://blog.csdn.net/Luxia_24/article/details/52403033
synchronized 在軟件層面依賴JVM
Lock 在硬件層面依賴特殊的CPU指令 --- CAS + JNI調用CPU指令來實現
synchronized 可以吧任何一個非 null 對象作爲 "鎖",
作用於方法上時,鎖住的是對象實例this,
作用於靜態方法,鎖住的是對象對應的Class實例,因爲Class數據存儲在永久帶,因此靜態方法鎖相當於該類的全局鎖,
作用於某個對象實例,鎖住的是對應的代碼塊
HotSpot JVM中,鎖 --- 對象監視器(對象來監視線程的互斥) --- synchronized的實現原理
對象監視器,設置幾種狀態來區分請求的線程:
Contention Set: 所有請求鎖的線程,被首先放置到該競爭隊列 --- 先進後出的虛擬隊列,會被線程併發訪問
Entry Set:等待獲取鎖的線程(來自Contention Set)排隊隊列 --- 等待獲取對象鎖運行
Wait Set:獲取鎖後,調用wait()方法,被阻塞的線程隊列 --- 等待再次獲取對象鎖
OnDeck: 任何時刻最多只能有一個線程正在競爭鎖,該線程稱爲OnDeck
Owner: 獲得鎖的線程稱爲Owner
!Owner: 釋放鎖的線程
說明:
1.Entry Set 和Contention Set 同屬等待隊列,
2.Contention Set會被線程併發訪問,爲了降低對Contention Set隊尾的爭用(爲了減少加入與取出兩個線程對於contentionList的競爭),而建立Entry Set,如果Entry Set爲空,則從Contention Set隊尾取出節點
3.Owner線程在unlock時,會從Contention Set中遷移線程到Entry Set,並會指定Entry Set中的某個線程(一般爲Head)爲Read(OnDeck)線程
4.Owner線程並不是把鎖傳遞給OnDeck線程,只是把競爭鎖的權利交給OnDeck,OnDeck線程需要重新競爭鎖
5.OnDeck線程獲得鎖喉變爲Owner線程,無法獲得鎖的線程依然留在Entry Set中
6.如果Owner線程被wait()方法阻塞,則轉移後WaitSet中,如果某個時刻被notify/notifyAll喚醒,則再次轉移到EntrySet
線程的互斥,其實是線程對同一個對象的監視器monitor的操作:
每個對象都有一個監視器(monitor)!,當monitor被佔就會處於鎖定狀態,線程執行monitorentry指令時嘗試獲取monitor的所有權
1.如果monitor的進入數爲0,則線程進入monitor,然後將進入數設置爲1,線程即爲monitor的所有者
2.如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數 +1 --- 鎖可重入 --- ReentrantLock 和synchronized 都是 可重入鎖
3.其他線程已經佔用了monitor,則該線程進入阻塞狀態,知道monitor的進入數爲0,再嘗試獲取monitor的所有權
4.線程調用一次unlock()釋放鎖,monitor的進入數就 -1 (只有monitor進入數爲0,才能被其他線程搶佔)
5.一個線程獲取多少次鎖,就必須釋放多少次鎖,對於synchronized內置鎖 ,每一次進入和離開synchronized方法(代碼塊),就是一個完整的鎖獲取和釋放
sleep()不會釋放鎖,等待指定時間後繼續運行
wait()會釋放鎖,進入對象監視器的 Wait Set隊列,等待被喚醒,被喚醒後,需要重新獲取鎖
wait()和notify/notifyAll必須成對出現,而且必須放在synchronized中,
yield() 不會釋放鎖, 只是讓當前線程讓出CPU佔用權
Synchronized底層優化 --- 偏向鎖、輕量級鎖
參考:http://www.cnblogs.com/paddix/p/5405678.html
http://www.jianshu.com/p/5dbb07c8d5d5
Synchronized效率低的原因?
Synchronized是通過對象內部的對象監視器鎖(monitor)來實現的,monitor本質是依賴於底層的操作系統的Mutex Lock(互斥鎖)來實現,
操作系統實現線程間切換需要從用戶態轉到內核態(JVM轉到操作系統內核),這個成本非常高,狀態轉換需要相對比較長的時間,這就是爲什麼Synchronized效率低的原因
Synchronized底層優化:
1.鎖的4種狀態:
無鎖狀態、偏向鎖、輕量級鎖、重量級鎖
隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖(鎖升級只能從低到高升級,不會出現鎖的降級)
JDK1.6默認開啓偏向鎖和輕量級鎖,通過-XX:-UseBiasedLocking來禁用偏向鎖
鎖的狀態保存在對象的頭文件中
重量級鎖 --- 依賴於操作系統Mutex Lock所實現的鎖, 需要從JVM轉到操作系統內核,進行互斥操作
輕量級鎖 --- 並不是用來代替重量級鎖,本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗
輕量級鎖目的:
爲了在線程交替執行同步塊時提高性能 !!!
輕量級鎖適用場景:
線程交替執行同步塊的情況 ---- 鎖競爭不激烈的情況!
如果存在同一時間訪問同一個鎖,就會導致輕量級鎖升級爲重量級鎖
偏向鎖 --- 爲了在無多線程競爭的情況下,儘量減少不必要的輕量級鎖執行路徑
一旦線程第一次獲得了監視對象,之後讓監視對象 "偏向"這個線程,在該線程重複獲取鎖時,避免CAS操作
即:
設置一個變量,如果發現是true,無需再走加鎖、解鎖的流程!
偏向鎖目的:
解決無競爭下的鎖性能問題,在只有一個線程執行同步塊時,進一步提高性能
總結:
ynchronized的底層實現主要依靠Lock-Free的隊列,基本思路是自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量 !!!
67.Spring如何解決Bean的循環依賴? --- 只支持Singleton作用域的, setter方式的循環依賴!!!
參考:https://my.oschina.net/yibuliushen/blog/737640
http://blog.csdn.net/caomiao2006/article/details/46511123
Spring容器循環依賴包括構造器循環依賴和setter循環依賴
如果是構造器循環依賴,Spring容器將無法啓動,報循環依賴異常BeanCurrentlInCreationException
解決方式:
將構造器注入方式改爲屬性注入方式 --- setter
Spring 支持setter方法注入屬性方式的循環依賴
Spring中將循環依賴的處理分3中情況:
1.構造器循環依賴 --- 原理, Spring 不支持構造器方式的循環依賴
通過構造器注入構成的循環依賴是無法解決的,只能在容器啓動時拋出BeanCurrentlInCreationException異常 --- 表示循環依賴
Spring容器將每一個正在創建的Bean的標識符(id)放到 "當前創建bean池" 中,bean標識符在創建過程中將一直保持在這個池中,
如果在創建bean過程中,發現自己已經在 "當前創建bean池"裏時,將拋出 BeanCurrentlInCreationException異常,表示循環依賴,
而對於創建完畢的bean將從 "當前創建bean池"中清除掉
eg:
如在創建TestA類時,構造器需要TestB類,那將去創建TestB,在創建TestB類時又發現需要TestC類,則又去創建TestC,
最終在創建TestC時發現又需要TestA,從而形成一個環,沒辦法創建 --- 循環依賴,拋出 BeanCurrentlInCreationException異常
配置文件;
<bean id="testA" class="com.bean.TestA">
<constructor-arg index="0" ref="testB"/>
</bean>
<bean id="testB" class="com.bean.TestB">
<constructor-arg index="0" ref="testC"/>
</bean>
<bean id="testC" class="com.bean.TestC">
<constructor-arg index="0" ref="testA"/>
</bean>
測試用例:
@Test(expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("test.xml");
} catch (Exception e) {
//因爲要在創建testC時拋出;
Throwable ee1 = e.getCause().getCause().getCause();
throw e1;
}
}
分析:
Spring容器創建"testA"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testB",並將"testA"標識符放到"當前創建bean池"。
Spring容器創建"testB"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testC",並將"testB"標識符放到"當前創建bean池"。
Spring容器創建"testC"bean,首先去"當前創建bean池"查找是否當前bean正在創建,如果沒發現,則繼續準備其需要的構造器參數"testA",並將"testC"標識符放到"當前創建Bean池"。
到此爲止Spring容器要去創建"testA"bean,發現該bean標識符在"當前創建bean池"中,因爲表示循環依賴,拋出BeanCurrentlyInCreationException。
說明:
Spring中bean默認是單例的,對於singleton作用於的Bean,可通過setAllowCircularReferences(false)來禁用循環引用
2.Setter循環依賴 --- 原理, Spring支持setter方式注入屬性的循環依賴!
setter注入方式構成的循環依賴,通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(eg:setter注入)的bean來完成的,
而且只能解決Singleton單例作用域的bean循環依賴,通過提前暴露一個單例工廠方法,從而使其他bean能引用到該bean(注意:此時僅僅只是生了一個bean,該bean還未調用其他方法,如setter注入)
對單例Bean循環依賴的處理:通過遞歸方法,找出當前Bean的所有依賴Bean,然後提前緩存起來
原理:
創建Bean A時,先通過無參構造器創建一個A實例,此時屬性都是空的,但對象引用已經創建創建出來,然後把Bean A的引用提前暴露出來,
然後setter B屬性時,創建B對象,此時同樣通過無參構造器,構造一個B對象的引用,並將B對象引用暴露出來。
接着B執行setter方法,去池中找到A(因爲此時,A已經暴露出來,有指向該對象的引用了),這樣依賴B就構造完成,也初始化完成,然後A接着初始化完成,
循環依賴就這麼解決了!!!
總結:
先創建對象引用,再通過setter()方式,給屬性賦值,層層創建對象 !!!
Bean A初始化時,先對其依賴B進行初始化,同時,通過默認無參構造器,生成自己的引用,而不調用其setter()方法,
當B對象創建時,如果還依賴C,則也通過無參構造器,生成B的引用,
C對象創建時,如果引用了A,則去對象池中查到A的引用,然後調用setter()方式,注入A,完成C對象的創建
C創建完成後,B使用setter()方式,注入C,完成B對象創建,
B對象場景完成後,A使用setter()方式,注入B,完成A對象創建,
最終,完成setter()方式的循環依賴!
如果循環依賴的都是單例對象(都是通過setter方式注入屬性的),那麼這個肯定沒問題,放心使用即可!!!
如果一個是單例,一個是原型,那麼一定要保證單例對象能提前暴露出來,纔可以正常注入屬性!!!
3.prototype範圍的依賴處理
對於"prototype"作用域bean,Spring容器無法完成依賴注入,因爲Spring容器不進行緩存"prototype"作用域的bean,因此無法提前暴露一個創建中的bean
這個spring也無能爲力,因爲是原型對象,A創建的時候不會提前暴露出來,所以,每次都是要創建,創建的時候,發現有相同的對象正在創建,同樣報錯,循環依賴錯誤
4.Spring創建Bean的源碼解釋:
1.創建Bean的入口
AbstractBeanFactory-->doGetBean()
Object sharedInstance = getSingleton(beanName); //從緩存中查找,或者如果當前創建池中有並且已經暴露出來了,就返回這個對象
2.創建單例Bean方法
DefaultSingletonBeanRegistry-->getSingleton(String beanName, ObjectFactory<?> singletonFactory)
3.創建真正對象
AbstractAutowireCapableBeanFactory-->doCreateBean
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
} 注意這一步很關鍵,是調用構造方法創建一個實例對象,如果這個構造方法有參數,而且就是循環依賴的參數,那麼這個對象就無法創建了,
因爲到這裏對象沒有創建,也沒有暴露當前對象,如果是無參的構造方法,那麼就可以,先創建一個對象,儘管所有的屬性都爲空
68.Spring事務管理的原理?
參考:http://www.codeceo.com/article/spring-transactions.html
聲明式事務管理,在Service之上或Service的方法之上,添加 @Transactional註解
@Transactional如何工作?
Spring在啓動時,會去解析生成相關的Bean,這是會查看擁有相關注解的類和方法,
並且爲這些類和方法生成代理,並根據 @Transactional的相關參數進行相關配置注入,
這樣就在代理中把相關的事務處理掉了(開啓正常提交事務,異常回滾事務)
真正的數據庫層,事務提交和回滾是通過binlog和redo log實現的
Spring事務管理機制實現原理:
參考:
http://www.jianshu.com/p/4312162b1458
http://www.cnblogs.com/duanxz/p/3750845.html
http://www.92to.com/bangong/2016/11-05/12533010.html
在調用一個需要事務的組件時,管理器首先判斷當前調用(即:當前線程)有沒有事務,如果沒有事務則啓動一個事務,並把事務與當前線程綁定,
Spring使用TransactionSynchronizationManager的bindResource方法將當前線程與一個事務綁定,採用的方式就是ThreadLocal,
參考:DataSourceTransactionManager的啓動事務用的代碼 doBegin()
通過動態代理或AOP方式,對所有需要事務管理的Bean進行加載,生成代理對象,並根據配置在invoke()方法中對當前調用的方法名進行判定,
並在method.invoke()方法前後爲其加上合適的事務管理代碼,根據method.invoke()執行結果,正常提交事務,異常回滾事務
實現了EntityManager接口的持久化上下文代理,包含3個組成部分:
1.EntityManager Proxy本身
2.事務的切面
3.事務管理器
遇到過的問題:
參考:59,爲何聲明式事務沒有生效?
69.Spring如何處理高併發?高併發下,如何保證性能?
1.單例模式 + ThreadLocal
單例模式大大節省了對象的創建和銷燬,有利於性能提高,ThreadLocal用來保證線程安全性
Spring單例模式下,用ThreadLocal來切換不同線程直接的參數,用ThreadLocal是爲了保證線程安全,實際上,ThreadLocal的key就是當前線程的Thread實例
單例模式下,Spring把每個線程可能存在線程安全問題的參數值放進了ThreadLocal,雖然是一個實例,但在不同線程下的數據是相互隔離的,
因爲運行時創建和銷燬的bean大大減少了,所以大多數場景下,這種方式對內存資源的消耗較少,並且併發越高,優勢越明顯
2.ThreadLocal
相比同步機制,ThreadLocal則從另一個角度來解決多線程的併發訪問。ThreadLocal會爲每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。
因爲每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,
在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal !!!
3.Spring MVC在併發訪問時,是否會存在線程安全問題?
參考:http://blog.csdn.net/csluanbin/article/details/50930138
http://blog.csdn.net/a236209186/article/details/61460211
Struts2是基於類的攔截(每次處理請求,都會實例化一個對象Action,不會有線程安全問題)
Spring MVC 是基於方法的攔截,粒度更細,而Spring的Controller默認是Singleton的,即:每個request請求,系統都會用同一個Controller去處理,
Spring MVC和Servlet都是方法級別的線程安全,如果單例的Controller或Servlet中存在實例變量,都是線程不安全的,而Struts2確實是線程安全的
優點:
不用每次創建Controller,減少了對象創建和銷燬
缺點:
Controller是單例的,Controller裏面的變量線程不安全
解決方案:
1.在Controller中使用ThreadLocal變量,把不安全的變量封裝進ThreadLocal,使用ThreadLocal來保存類變量,將類變量保存在線程的變量域中,讓不同的請求隔離開來
2.聲明Controller爲原型 scope="prototype",每個請求都創建新的Controller
3.Controller中不使用實例變量
Spring MVC 如何保證request對象線程安全?
參考:http://blog.csdn.net/csluanbin/article/details/50930138
InvocationHandler接口:這是springmvc保證request對象線程安全的核心。
通過實現該接口,開發者能夠在Java對象方法執行時進行干預,搭配Threadlocal就能夠實現線程安全
問題:判斷一下程序是否線程安全?
@Controller
public class UserController{
@Autowired
private HttpSession session
@RequestMapping(xxxxxxx)
public void getUser{
session.get ...
session.set...
....
}
}
結論:
該程序是線程安全的
解析:
項目啓動和運行時,Controller對象中的HttpSession並不是HttpSession實例,而是一個代理,
是org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler代理了HttpSession ,可通過這個代碼求證:System.out.println(Proxy.getInvocationHandler(session));
只要當你真正調用HttpSession中的非java.lang.Object方法時纔會真真去調用被代理的HttpSession裏面的方法
說一下session.get ...過程:首先從對象工廠從Threadlocal中取得HttpSession實例,然後通過反射調用該實例的set方法
特別注意:
1.Spring 應該是在請求進來的時候ThreadLocal.set(Session),然後在請求的生命週期中都是一個Thread ,執行完後ThreadLocal.remove(Session)
2.一個請求使用一個ThreadLocal,綁定對應的HttpSession,所以是線程安全的
3.對於 "注入" 到Controller中的單例對象, 都是由Spring統一管理的,Spring對注入Controller的對象使用了ThreadLocal + 代理機制,保證了線程安全
4.但是,對於在Controller中直接定義的實例變量,是線程不安全的!!!
eg:
@RestController
@RequestMapping("/test1")
public class ControllerTest1 {
private int i = 0;
@GetMapping("/count")
public void test1(){
System.out.println(i++);
}
//驗證Controller中的實例變量,線程不安全
@GetMapping("/t1")
public void test3(){
ExecutorService service = Executors.newFixedThreadPool(100);
for (int i=0;i<1000;i++) {
service.execute(new Runnable() {
@Override
public void run() {
HttpUtils.sendGet("http://localhost:8080/test1/count", null);
}
});
}
}
}
調用:http://localhost:8080/test1/t1 方法,使用多線程對i進行操作,發現i的結果不是999,證明Controller中的實例變量是線程不安全的!
結論:
1.對於單例的Controller,Service中定義的實例變量,都不是線程安全的!!!
2.儘量避免在Controller和Service中定義多線程共享的實例變量
3.Spring使用ThreadLocal + InvocationHandler(動態代理)提供了高併發訪問的性能
4.對於Controller和Service中的實例變量,多線程訪問時,需要加鎖處理 或 設置 scope = "prototype"爲每個請求創一個對象
5.對於 @Autowire注入的HttpServletRequest和HttpSession,Spring進行了特殊處理,不會有線程安全問題
70.ConcurrentLinkedQueue與BlockingQueue
ConcurrentLinkedQueue源碼解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
2類線程安全的隊列:
1.阻塞隊列 --- 阻塞算法 --- 隊列使用一個鎖(入隊和出隊用同一把鎖)或兩個鎖(入隊和出隊用不同的鎖)等方式實現
2.同步隊列 --- 非阻塞算法 --- 使用循環CAS方式實現
LinkedBlockingQueue 線程安全的阻塞隊列,實現了BlockingQueue接口,BlockingQueue繼承自java.util.Queue接口,
並在接口基礎上增加了take()和put()方法, 這2個方法正式隊列操作的阻塞版本
先進先出,可以指定容量,默認最大是Integer.MAX_VALUE;其中主要用到put和take方法,put方法在隊列滿的時候會阻塞直到有隊列成員被消費,take方法在隊列空的時候會阻塞,直到有隊列成員被放進來!!!
put() 向隊列中放數據 take() 從隊列中取數據
ConcurrentLinkedQueue 是Queue的一個安全實現.Queue中元素按FIFO原則進行排序.採用CAS操作,來保證元素的一致性。
非阻塞方式實現的無界線程安全隊列 !!!
offer()添加元素, poll()獲取元素 isEmpty()判斷隊列是否爲空 (特別注意,不用size(),效率低,會遍歷隊列,儘量要避免用size而改用isEmpty())
採用CAS操作,允許多個線程併發執行,並不會因爲你加鎖而阻塞線程,使得併發性能更好!!!
使用:http://www.cnblogs.com/dmir/p/4907515.html
http://blog.csdn.net/sunxianghuang/article/details/52046150
ConcurrentLinkedQueue源碼解析:http://ifeve.com/concurrentlinkedqueue/
總結:
多數生產消費模型的首選數據結構就是隊列(先進先出)。Java提供的線程安全的Queue可以分爲阻塞隊列和非阻塞隊列,
其中阻塞隊列的典型例子是BlockingQueue,非阻塞隊列的典型例子是ConcurrentLinkedQueue,在實際應用中要根據實際需要選用阻塞隊列或者非阻塞隊列
Java中的7種阻塞隊列
阻塞隊列: --- 阻塞的是線程操作(拿和取元素)
常用於生產者和消費者場景,是生產者用來存放元素、消費者用來獲取元素的容器
put()阻塞:隊列滿時,阻塞插入元素的線程,直到隊列不滿
take()阻塞:隊列空時,阻塞獲取元素的線程,直到隊列不空
ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。 生產者和消費者直接傳遞數據,不對數據作緩存,生產者和消費者通過在隊列裏排隊的方式來阻塞和喚醒 --- 速度快
線程數少時,使用SynchronousQueue 速度更快!!!
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列
關於PriorityBlockingQueue優先級隊列:
傳統隊列都是FIFO,優先級隊列,通過Comparator或Comparable接口,根據元素的比較排序,確定出隊列的順序(優先級:即在compare()中指定的順序)
1.PriorityQueue是基於優先堆的一個無界隊列,元素默認按自認排序或通過提供的Comparator比較器在實例化隊列時排序
2.優先級隊列不允許空值(無法排序),而且不支持不可比較的對象
eg:
對於自定義類,優先級隊列要求,1:類實現Comparable接口 或 2:通過Comparator對對象排序,並且在排序時會按照優先級處理其中的元素。
3.優先級隊列的頭,是基於自然排序或Comparator排序的最小元素,
如果多個對象擁有同樣的順序,則坑你隨機取其中一個,當獲取隊列時,返回隊列的頭對象
4.優先隊列的大小是不受限制的,但在創建時可以指定初始大小。當我們向優先隊列增加元素的時候,隊列大小會自動增加
5.PriorityQueue是非線程安全的,所以Java提供了PriorityBlockingQueue(實現BlockingQueue接口)用於Java多線程環境。
ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentLinkedQueue的區別和使用場景?
區別:
1.三者都是線程安全的
2.2個BlockingQueue是阻塞的,ConcurrentLinkedQueue是併發的
3.2個BlockingQueue使用鎖機制實現阻塞和線程安全(通過ReentrantLock + Condition阻塞容量爲空時的取操作和容量滿時的寫操作),
ConcurrentLinkedQueue使用cas算法保證線程安全
4.ArrayBlockingQueue使用一個鎖(lock + 2個Condition),而LinkedBlockingQueue使用2個鎖(鎖分離,取用takeLock + Condition,寫用putLock+Condition),所以LinkedBlockingQueue的吞吐量大,併發性能比Array高
LinkedBlockingQueue,對頭和尾採用不同的鎖,提高了吞吐量,適合 "消費者生產者" 模式
ArrayBlockingQueue, 數組實現,使用一把全局鎖並行對queue的讀寫操作,同時使用2個Condition阻塞容量爲空時的讀操作和容量滿時的寫操作
5.正因爲LinkedBlockingQueue使用兩個獨立的鎖控制數據同步,所以可以使存取兩種操作並行執行,從而提高併發效率。
而ArrayBlockingQueue使用一把鎖,造成在存取兩種操作爭搶一把鎖,而使得性能相對低下。LinkedBlockingQueue可以不設置隊列容量,默認爲Integer.MAX_VALUE.其容易造成內存溢出,一般要設置其值
使用場景:
阻塞隊列優點:
多線程操作不需要同步,
隊列會自動平衡負載,即:生產和消費兩邊,處理快了會被阻塞,減少兩邊的處理速度差距,
自動平衡負載特性,造成它能被用於多生產者隊列,隊列滿了就要阻塞等着,直到消費者使隊列不滿才能繼續生產
ConcurrentLinkedQueue:
允許多線程共享訪問一個集合,多用於消息隊列!!!
多消費者消費同一個 用 ConcurrentLinkedQueue:
BlockingQueueue:
多線程共享時阻塞,多用於任務隊列!!!
單消費者用 BlockingQueueue:
總結: 單個消費者用LinkedBlockignQueue, 多消費者用ConcurrentLinkedQueue !!!
單生產者,單消費者 用 LinkedBlockingqueue
多生產者,單消費者 用 LinkedBlockingqueue
單生產者 ,多消費者 用 ConcurrentLinkedQueue
多生產者 ,多消費者 用 ConcurrentLinkedQueue
71.Java多線程同步機制:3種類型
volatile 變量:輕量級多線程同步機制,不會引起上下文切換和線程調度。僅提供內存可見性保證,不提供原子性。 --- 只保證可見性,不保證原子性,不絕對線程安全!!!
CAS 原子指令:輕量級多線程同步機制,不會引起上下文切換和線程調度。它同時提供內存可見性和原子化更新保證。
內部鎖(synchronized)和顯式鎖(各種Lock):重量級多線程同步機制,可能會引起上下文切換和線程調度,它同時提供內存可見性和原子性。
參考:
非阻塞算法在併發容器中的實現:ConcurrentLinkedQueue https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
72.CAS在JDK中的實現
參考:http://blog.csdn.net/canot/article/details/50759424
1.Synchronized鎖機制存在的問題:
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險
優化:
偏向鎖、輕量級鎖、減小鎖粒度
2.鎖分類
悲觀鎖 --- 獨佔鎖 --- synchronized是獨佔鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖
樂觀鎖 --- 每次不加鎖,而是假設沒有衝突,而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止
3.CAS原理 --- 樂觀鎖 實現
CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做
CAS操作:
CAS有3個操作數:
V 內存值
A 舊的預期值
B 要修改的新值
當且僅當預期值A和內存值V相同時,將內存值V修改爲B,否則什麼都不做!!!
非阻塞算法: 一個線程的失敗或掛起,不應該影響其他線程的失敗或掛起的算法
CAS的硬件基礎和實現原理:
現代的CPU提供了特殊指令,可以自動更新共享數據,而且能夠檢測到其他線程的干擾,
而compareAndSet()就用這些代替了鎖定, compareAndSet利用JNI,藉助調用的C語言來完成CPU指令的操作
CAS實現原理:
Java 中通過unsafe類的額compareAndSwap()方法實現的
eg:
AtomicInteger 如何實現無鎖下的線程安全?
//在沒有鎖的機制下可能需要藉助volatile原語,保證線程間的數據是可見的(共享的)。這樣才獲取變量的值的時候才能直接讀取。
private volatile int value;
public final int get(){
return value;
}
//i++操作, 每次從內存中讀取數據,然後將此數據和 +1 後的結果進行CAS操作,如果成功就返回結果,否則重試直到成功爲止
public final int incrementAndGet(){
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
4.CAS的實現 compareAndSet() ---> unsafe.compareAndSwap()
compareAndSwap的4個參數:
參數1:需要改變的對象
參數2:偏移量
參數3:期待的值
參數4:更新之後的值
方法解析:
若調用該方法,value值與expect值相等,則將value修改爲update值,並返回true,
如果調用該方法,value值與expect值不相等,則不做任何操作,並返回false
eg:
參考java.util.concurrent.atomic.AtomicLong的實現 i++操作
//+1操作, 一直執行CAS,失敗則重試,直到操作成功,返回結果
public final long getAndIncrement() {
while (true) {
long current = get();
long next = current + 1;
//當+1操作成功的時候直接返回,退出此循環
if (compareAndSet(current, next))
return current;
}
}
//調用JNI實現CAS
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
5.CAS的缺點: 參考:http://www.cnblogs.com/zhuawang/p/4196904.html
http://blog.csdn.net/ghsau/article/details/17609747
1.存在ABA問題
ABA問題:
一個變量V,如果變量V初次讀取時是A,並在準備賦值時檢測到它還是A,
那能說明它的值沒有被其他線程修改過了嗎? --- 不一定
如果這段時間,它的值被其他線程改爲了B,然後又改回了A, CAS操作會誤認爲它沒有被修改過!!!
如何解決CAS中的ABA問題?
解決方案:
參考樂觀鎖實現機制,
在CAS操作是,帶上版本號或時間戳,每修改一次,版本號+1,
不但比較對象是否相等,還有比較版本號是否一致!!!
JDK的解決方案:
java併發包提供了一個帶有標記的原子引用類 "AtomicStampedReference",它可以通過控制變量值的版本來保證CAS的正確性!!!
參考:http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2.循環時間長,開銷大 --- 自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷
3.只能保證一個共性變量的原子操作
--- 對一個共享變成可以使用CAS進行原子操作,但是多個共享變量的原子操作就無法使用CAS,這個時候只能使用鎖
--- JDK1.5提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象裏來進行CAS操作
73.Java內存模型
參考:http://www.cnblogs.com/dongguacai/p/5970076.html
併發的2個關鍵問題:
1.線程間如何通信 --- 通信是指線程之間以何種機制來交換信息,在命令式編程中,通信機制有兩種:共享內存和消息傳遞;JAVA的併發採用的是共享內存,線程之間的通信總是隱式進行
2.線程間如何同步 --- 同步指程序中用於控制不同線程間操作發生相對順序的機制,在共享內存併發模型中,同步是顯式進行的
Java內存模型:
1.共享變量 --- 分配在堆內存中元素都是共享變量,eg:實例變量、靜態變量、數組和對象
2.非共享變量 --- 分配在棧上的都是非共享變量,主要指:局部變量,該變量爲線程私有,不會在線程間共享,也不存在內存可見性問題
如果線程A和線程B需要進行通信,必須經過以下2個過程:
主內存 <---> JVM堆
本地內存 <---> 線程工作內存
1.線程A把本次內存中修改過的共享變量屬性到主內存中
2.線程B到主內存中讀取線程A已經更新過的共享變量到B的線程本地內存中
內存間交互操作:
主要指工作內存(線程本地內存)與主內存之間的交互,即:一個變量如何從主內存拷貝到工作內存,如何從工作內存刷新到主內存的實現細節
Java內存模型定義了8種操作:
lock: 作用於主內存, 把一個變量標識爲某個線程獨佔狀態
unlock: 作用於主內存,把一個處於鎖定狀態的變量釋放,釋放後變量可被其他線程鎖定
read: 作用於主內存,把一個變量從主內存傳輸到工作內存,用於後面的load操作
load: 作用於工作內存,把read操作從主內存中得到的變量值放入工作內存的變量副本中
use: 作用於工作內存,把變量值傳遞給執行引擎,每當虛擬機需要使用變量的字節碼指令時,將會執行這個操作
assign: 作用於工作內存,把從執行引擎接收到的值,賦值給工作內存的變量,每當虛擬機遇到需要給該變量賦值的字節碼指令時執行這個操作
store: 作用於工作內存,把工作內存中的一個變量值傳到主內存,以便後續的write操作
write: 作用於主內存,把store操作從工作內存中獲取的值賦值給主存中的變量
8個操作的7個原則:
1、不允許read和load,store和write操作單獨出現。
2、不允許一個線程丟棄它最近的assign操作,即變量在工作內存中的更新需要同步到主內存中。
3、不允許線程無原因地(沒有發生過任何assign操作)把數據同步到主內存。
4、一個新的變量只能在主內存中產生,不能在工作內存中直接使用未被初始化的變量。
5、一個變量在同一時刻只能被一個線程lock,並且lock和unlock需要成對出現。
6、如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要執行load或者assgin操作。
7、對一個變量執行unclock之前,必須把此變量同步到主內存中。
74.Zookeeper搭建單機的僞分佈式集羣
參考:http://www.cnblogs.com/tenghoo/p/windows_zookeeper_pseudo_cluster.html
1.修改3個配置文件
修改confg下的zoo_sample.cfg 爲zoo.cfg,修改每個節點的clientPort, 並設置每個節點的dataDir和dataLogDir
Master主節點:
clientPort=2181
dataDir=/tmp/zookeeper/cluster/master/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/master/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave1節點:
clientPort=2182
dataDir=/tmp/zookeeper/cluster/slave1/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave1/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
Slave2點:
clientPort=2183
dataDir=/tmp/zookeeper/cluster/slave2/data
dataLogDir=dataDir=/tmp/zookeeper/cluster/slave2/log
#cluster config
#參數解釋:server.A = B:C:D : A表示這個是第幾號服務器,B 是這個服務器的 ip 地址;C 表示的是這個服務器與集羣中的 Leader 服務器交換信息的端口;D 表示的是萬一集羣中的 Leader 服務器掛了,需要一個端口來重新進行選舉,選出一個新的 Leader
server.1=127.0.0.1:2887:3887
server.2=127.0.0.1:2888:3888
server.3=127.0.0.1:2889:3889
2.添加data和log文件
在/tmp/zookeeper/cluster/目錄下,添加master/data、master/log文件,2個salve同理
3.創建myid
在/tmp/zookeeper/cluster/目錄下的 master/data、slave1/data、salve2/data下創建文件myid,並分別添加內容1,2,3
4.啓動3個Server
用jps查看 Zookeeper進程
75.ReentrantLock + Condition 鎖的複用,實現線程間通信
面試題:
子線程循環10次,接着主線程循環100,接着又到子線程循環10次,接着再回到主線程循環100,如此循環50次
分析:
1.因爲是2個線程的操作,需要線程間通信,wait、notify 或 ReentrantLock + Condition
2.Condition:定義鎖的條件隊列,當在線程中調用其await()方法時,就會將其存放到鎖的條件隊列中,
比內置鎖的優點:可以對一個鎖定義多個條件隊列
signal()方法喚醒隊列中線程,FIFO順序喚醒 !!! , 特別注意:Condition是按順序喚醒隊列中線程,不是隨機!!!
3.Condition,將Object監視器方法(wait、notify和notifyAll)分解成截然不同的對象,
以便通過將這些對象與任意Lock實現組合使用,爲每個對象提供多個等待Set(wait-set), !!!(最重要:Condition爲每個對象,通過多個等待隊列,按照FIFO先進先出的屬性喚醒線程)
Lock替代了synchronized方法和語句,Condition替代了Object監視器方法的使用
、
4.Condition的強大之處:它可以爲多個線程間建立不同的Condition
5.Condition,這個條件 可以爲鎖定的對象,創建多個線程等待隊列,實現鎖的多路複用!!!
static Lock locks = new ReentrantLock();
static Condition conditonMain = locks.newCondition();
static Condition conditonSun = locks.newCondition();
public static void main(String[] args) {
final ExecutorService executorService = Executors.newFixedThreadPool(100);
for(int bb = 0; bb < 10 ; bb++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
locks.lock();
for(int i=0 ; i<10 ; i++){
String name = Thread.currentThread().getName();
System.err.println("子線程循環:"+name+"-----"+i);
}
conditonMain.signal();
conditonSun.await();
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
});
try {
locks.lock();
conditonSun.signal();
conditonMain.await();
for(int i = 0 ; i< 100 ; i++){
String name = Thread.currentThread().getName();
System.err.println("主線程循環:"+name+"-----"+i);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
locks.unlock();
}
}
}
關於條件變量Condition
參考:http://www.tuicool.com/articles/6vANna
--- 很大一個程度是爲了解決 Object.wait/notify/notifyAll難以使用的問題!
--- Condition(條件隊列或條件變量),主要用來等待某個條件的發生,
條件發生後,可以喚醒等待在該線程上的一個線程,或所有線程。
必須與Lock鎖一起協同工作!!!
--- 每個Lock可以有任意多個Condition對象,Condition與Lock是綁定的,
--- 如果Lock是公平鎖,線程按照FIFO的順序從Condition.await()中釋放,如果是非公平鎖,後續的鎖競爭就不保證FIFO順序了
await* 對應於 Object.wait , signal 對應於 Object.notify , signalAll 對應於 Object.notifyAll
--- 實例:concurrent包中阻塞隊列的實現,基本都是基於ReentrantLock + Condition的
76.Java併發編程的3個輔助類 CountDownLatch、CyclicBarrier、Semaphore
參考:http://blog.csdn.net/zhangliangzi/article/details/52526125
77.ConcurrentHashMap並不是絕對線程安全的?
ConcurrentHashMap原理: http://www.cnblogs.com/ITtangtang/p/3948786.html
78.HashMap、LinkedHashMap、TreeMap
HashMap: 用key做hash算法,然後將hash值映射到內存地址,直接取key所對應的的數據value
底層實現:數組(hash尋址)+鏈表(衝突解決),內存地址即:數組的下標索引
存入元素無序,不可重複
高性能保證: 只要HashCode() 和 Hash() 方法實現得足夠好,能夠儘可能地減少衝突的產生,那麼對 HashMap 的操作幾乎等價於對數組的隨機訪問操作
如果 HashCode() 或者 Hash() 方法實現較差,在大量衝突產生的情況下,HashMap 事實上就退化爲幾個鏈表,對 HashMap 的操作等價於遍歷鏈表,此時性能很差
LinkedHashMap: 繼承自HashMap,在其基礎上,內部增加了一個鏈表,用以存放元素的順序
底層實現:Hash表+鏈表, 依靠雙向鏈表保證了迭代屬性是插入的順序,維護着一個運行於所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序
存放元素有序,按存放順序
TreeMap: 通過紅黑樹實現的有序Key-Value集合,
元素有序,默認:按key的自然順序排序
可通過Comparator比較器,對key進行排序
//雙重檢查鎖的單例
public class Sig{
public volatile static sig = null;
private Sig(){}
public static getSig(){
if(sig==null){
synchorinzed(Sig.class){
if(sig==null){
sig = new Sig();
}
}
}
}
}
79.ThreadLocal源碼分析:
ThreadLocal類中有一個Map對象,這個Map以每個Thread對象爲key,保存了這個線程對應的局部變量值!!!!
實現方式:
http://blog.csdn.net/haoyifen/article/details/51126074
80.MySQL隔離級別和鎖的關係
1.事務的4個特徵
原子性:事務中包含的操作,看做一個邏輯單元,要麼全成功,要麼全失敗!
一致性:只有合法的數據可以被寫入數據庫,否則事務應該將其回滾到最初的狀態
隔離性:事務允許多個用戶對同一數據進行併發訪問,而不破壞數據的正確性和完整性,同時並行事務的修改必須與其他並行事務的修改相互獨立
持久性:事務結束後,處理結果必須能持久化
2.數據庫隔離級別
4個,由低到高:
√: 可能出現 ×: 不會出現
髒讀 不可重複讀 幻讀
Read uncommitted√ √ √
Read committed× √ √
Repeatable read× × √
Serializable × × ×
這4個級別可以逐個解決:髒讀、不可重複讀、幻讀這幾類問題
MySQL默認隔離級別: Repeatable Read, 可重複讀級別
http://lib.csdn.net/article/mysql/52873
81.非阻塞算法在併發容器中的實現
典型:
ConcurrentLinkedQueue
ConcurrentLinkedQueue源碼解析: http://ifeve.com/concurrentlinkedqueue/
https://www.ibm.com/developerworks/cn/java/j-lo-concurrent/index.html
1.Java多線程同步機制 --- 3種類型
1.volatile變量:輕量級多線程同步機制,不會引起上下文切換和線程調度。 提供內存可見性和禁止指令排序, 不保證原子性
2.CAS原子指令: 輕量級多線程同步機制,不會引起上下文切換和線程調度。 同時提供可見性和原子性更新保證
3.內部鎖(synchronized)和顯示鎖(各類Lock): 重量級多線程同步機制, 可能會引起上下文切換和線程調度,同時提供內存可見性和原子性
2.多核處理器系統對併發的支持
1.多處理系統提供了特殊的指令來管理對共享數據的併發訪問,這些指令能實現原子化的讀-改-寫操作
Intel和AMD多處理器系統支持 CAS(比較並交換)指令
2.JDK爲concurrent.atomic包中的原子類提供了compareAndSet()方法,
compareAndSet()方法使用機器級別的原子指令來原子化的更新值,
CAS 和 concurrent包中的原子類,是非阻塞算法實現併發容器的基礎
3.非阻塞算法
1.要想提高併發性,就應該儘量使串行部分達到最大程度的並行
即:最小化串行代碼的粒度是提高併發性能的關鍵 !!!
2.與鎖相比,非阻塞算法在更細粒度的層面(機器級別的原子指令)協同多線程間的競爭。
它使得多個線程在競爭相同資源時不會發生阻塞,它的併發性與鎖相比有了質的提高;同時也大大減少了線程調度的開銷
由於幾乎所有的同步原語都只能對單個變量進行操作,這個限制導致非阻塞算法的設計和實現非常複雜
4.非阻塞算法實現
1.基於非阻塞算法實現的併發容器
ConcurrentLinkedQueue: 基於鏈接節點的的無界線程安全隊列
SynchronousQueue: 無容量的阻塞隊列,使用雙重數據結構來實現非阻塞算法
Exchangeer: 能對元數據進行配對和交互的交換器,使用消除技術來實現非阻塞算法
ConcurrentSkipListMap 一個可以根據key進行排序的可伸縮的併發Map
2.ConcurrentLinkedQueue 非阻塞算法實現的併發安全隊列
1.使用CAS原子指令來處理對數據的併發訪問 --- 非阻塞算法得以實現的基礎
2.head/tail並非總是指向隊列的頭/尾節點,即:允許隊列處於不一致狀態,
這個特性把入隊/出隊時,原本需要一起原子化執行的兩個步驟分離開來,從而縮小了入隊/出隊時需要原子化更新值的範圍到唯一變量 --- 非阻塞算法得以實現的關鍵
3.由於隊列有時會處於不一致狀態,爲此,ConcurrentLinkedQueue使用三個不變式來維護阻塞算法的正確性
4.以批處理方式來更新head/tail,從整體上減少入隊/出隊操作的開銷
5.爲了利於垃圾收集,隊列使用特有的head更新機制;爲了確保從已刪除節點向後遍歷,可到達所有的費刪除節點,隊列使用了特有的向後推進策略
3.3個不變式
基本不變式:
在執行方法之前和之後,隊列必須要保持的不變式:
1.當入隊插入新節點之後,隊列中有一個next域爲null的(最後)節點
2.從head開始遍歷隊列,可以訪問所有item域不爲null的節點
head不變式和可變式:
在執行方法之前和之後,head必須保持的不變式:
1.所有"活着"的節點(指未刪除節點),都不能從head通過調用succ()方法遍歷可達
2.head不能爲null
3.head節點的next域不能引用到自身
在執行方法之前和之後,head的可變式
1.head節點的item域可能爲null,也可能不爲null
2.允許tail滯後於head,即:從head開始遍歷隊列,不一定能到達tail
tail不變式和可變式:
在執行方法之前和之後,tail必須保持的不變式:
1.通過tail調用succ()方法,最後節點總是可達的
2.tail不能爲null
在執行方法之前和之後,tail的可變式:
1.tail節點的item域可能爲null,也可能不爲null
2.允許tail滯後於head,即:從head開始遍歷隊列,不一定能到達tail
3.tail節點的next域可以引用到自身
82.JVM內存模型+GC
1.垃圾回收機制
1.引用計數 對象被引用一次,計數器+1,當不再引用時,引用計數器減一;當引用計數器爲0時,對象即可被回
2.根搜索法(可達性算法) 把對象的引用看做圖結構,由根節點集合出發,不可達的節點即可回收
根節點包含的5種元素:
Java棧中的對象引用
本地方法棧中的對象引用
運行時常量池的對象引用
方法區中靜態屬性的對象引用
所有Class對象
2.常用垃圾回收算法
1.標記清除: 2個階段, 第一階:標記可用對象, 第二階段:清除垃圾對象, 效率低,會產生內存碎片(不連續的內存空間),無法再次分配給較大對象
2.複製 : 用於新生代(適合回收生命週期短的對象),將內存分爲2個區域,新對象都分配在一個區域,回收時將可用對象連續複製到另一個區域,清空當前區域內存
不會產生內存碎片,但對象分配只有一個區域,內存利用率低
3.標記整理: 適用於老年代,類似標記清除,在其基礎上,將可用對象移動到一端連續的內存上,解決了內存碎片問題
4.分代算法: 基於分代特點
年輕代:複製算法
分爲較大的Eden區和2個相等的Survivor區(from、to),比例一般是 8:1:1,
對象分配在Eden區,當GC發生時(新生代GC叫Minor GC),將Eden區與from區中的可用對象複製到To區中,
from區和to區互換名稱,循環方法,直到發生如下2種請,對象進入老年代:
1.From區內的對象一大存活代數閾值(經歷一次Minor GC對象的存活代數+1,經歷GC的次數達到設定值), GC時進入To區,直接移動至老年代
2.在回收Eden和from區後,超出to區可容納範圍,則直接將存活對象移動至老年代
特別注意:
大的對象在分配時,直接進入老年代
老年代:標記整理算法
當老年代滿時,會觸發Full GC(新生代與老年代一起進行GC)
3.常用的垃圾回收器?特點?適合場景? 5類
1.Serial --- 絕對不推薦應用於服務器端!!!
年輕代複製算法、串行回收、Stop the world(GC時停止其他一切工作),適合單核CPU環境, 絕對不推薦應用於服務器端!!!
Serial 提供了老年代回收器 Serial Old,採用標記整理算法,特性與新生代一致, Serial + Serial Old適合客戶端場景
2.ParNew --- 推薦用於服務器場景!
相當於Serial的多線程版本,並回收,年輕代用複製算法和"Stop The World"機制,適合多核CPU、低延遲環境,推薦應用於服務器場景
3.Parallel --- 非常適合於服務器場景!!!
與ParNew類似,複製算法、並行回收、“Stop the world”機制,但是與ParNew不同,Parallel可以控制程序吞吐量大小,也被稱爲吞吐量優先的垃圾收集器
Parallel 也有老年代版本, Parallel Old ,採用標記整理算法
Parallel + Parallel Old 非常適合於服務器場景!!!
4.CMS --- 爲高併發、低延遲而生,採用標記-清除算法,會產生大量內存碎片,慎重使用!!!
與Parallel的高吞吐對應,CMS就是爲高併發、低延時而生的。採用標記-清除算法、並行回收、“Stop the world”
5.G1 --- JDK新加的GC收集器,不使用分代內存策略(年輕代+老年代)!!!,而是將內存分爲多個相等的區域
4.GC優化方案
1.不用顯示調用System.gc()
JVM接受這個消息後,並不是立即做垃圾回收,而只是對幾個垃圾回收算法做了加權,使垃圾回收操作容易發生,或提早發生,或回收較多而已。但即便這樣,很多情況下它會觸發Full GC,也即增加了間歇性停頓的次數
2.儘量減少臨時對象的使用
3.對象不用時,最好顯示置爲null
4.儘量使用StringBuffer代替String累加字符串
5.儘量使用基本類型,代替包裝類
6.儘量少用靜態對象變量
靜態變量屬於全局變量,不會被GC回收,會一直佔用內存
7.分散對象創建或刪除的時間
集中在短時間內創建新對象,特別是大對象,會導致突然需要大量內存,JVM在這種情況,只能進行Full GC,以回收內存或整合內存碎片,從而增加主GC頻率
從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現了大量的垃圾對象,空閒空間必然減少,從而大大增加了下一次創建新對象時強制主GC的機會
5.可能導致內存泄露的情況
1.靜態集合類eg:HashMap、Vector等使用,容易出現內存泄露,這些靜態變量的聲明週期和程序一致,所有的對象Object也不能被釋放,他們將一直被集合所引用
2.各類連接:數據庫連接,網絡連接,IO連接,等沒有顯示調用close()關閉,不被GC回收導致內存泄露
3. 監聽器的使用,在釋放對象的同時,沒有相應刪除監聽器的時候,也可能導致內存泄露
83.Jenkins相關:
http://10.10.30.116:8888/jenkins/ 登錄名,密碼:admin admin
說明:
忘記Jenkins的admin密碼,解決:http://blog.csdn.net/qq105319914/article/details/52094463
1.配置文件路徑:/root/.jenkins/
2.初始密碼:/root/.jenkins/secrets/initialAdminPassword
3.安裝參考:http://www.cnblogs.com/h--d/p/5673085.html
http://m.blog.csdn.net/article/details?id=50518959
http://www.cnblogs.com/zz0412/p/jenkins02.html
http://blog.csdn.net/galen2016/article/details/53418708
Jenkins配置和使用: http://www.cnblogs.com/h--d/p/5682030.html
http://blog.csdn.net/tengdazhang770960436/article/details/53842604
4.Jenkins無法下載插件的解決方法:http://blog.csdn.net/russ44/article/details/52266953
常見的無法安裝插件下載地址:
http://updates.jenkins-ci.org/download/plugins/
步驟:
從http://10.10.30.116:8888/jenkins/updateCenter/ 中查看爲安裝成功的插件,進行手動安裝即可
安裝順序:
1.credentials --> plain-credentials --> credentials-binding
2.ssh-credentials --> ssh-slaves
3.git-client --> git-server --> git
4.pam-auth
5.build-pipeline-plugin --> workflow-cps -->workflow-multibranch --> pipeline-multibranch-defaults -->
pipeline-graph-analysis --> pipeline-rest-api
6.subversion
7.github --> github-oauth --> groovy --> github-branch-source -->github-api
5.常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
2.Jenkins Blue Ocean使用
1.安裝
1.Jenkins版本在2.7.x以上
2.登錄Jenkins(admin,密碼:admin或111111),系統管理——>管理插件-->可選插件 -->搜索 Blue Ocean
3.點擊安裝後重啓,或直接安裝,安裝完畢後重啓Jenkins服務器
2.使用 參考:https://testerhome.com/topics/6700
1.點擊Open Blue Ocean進入Ocean UI界面
2.點擊 New Pipeline新建項目 或使用舊版界面創建項目
3.推薦使用舊版界面創建項目:
1.點擊新建 --> name: test1 ,選擇Pipeline OK保存
2.創建Pipeline Script腳本 即: Jenkinsfile 最重要!!!
node {
stage('Clone Code') { // for display purposes
// Get some code from a GitHub repository
git 'https://github.com/trautonen/coveralls-maven-plugin.git/'
}
stage('Code Analysis') {
sh "mvn clean"
sh "infer -- mvn compile"
}
stage('Testing') {
sh "mvn test"
junit 'target/surefire-reports/TEST-*.xml'
}
stage('Package') {
sh "'mvn' -Dmaven.test.skip=true package"
archive 'target/*.jar'
}
stage('Deploy') {
echo 'pipeline success'
}
}
3.常見異常
Cannot run program "nohup" (in directory "C
Windows環境,需要把Jenkinsfile文件中的sh改爲bat
4.新建時 沒有構建Maven項目項,安裝Maven項目插件:Maven Integration plugin
5.源碼沒有Git選項,安裝Git插件: Git Plugin
6.
3.常規的Jenkins項目自動化構建 --- 基於Git
0.先進行全局變量的配置
常用插件的手動安裝: 系統管理 --> Global Tool Configuration
1.JDK配置: 新增JDK --> 取消自動安裝
別名: JDK1.8
JAVA_HOME:/home/hetiewei/software/java/jdk1.8.0_40
2.Maven配置:新增Maven --> 取消自動安裝
別名: maven-3.3.9
MAVEN_HOME:/home/hetiewei/software/maven/apache-maven-3.3.9
1.構建自由風格的軟件項目
1.新建
2.輸入項目名稱 jay-plat,構建一個自由風格的軟件項目,點擊OK
3.添加項目描述
4.選擇源碼管理: Git
輸入 Repository URL :https://github.com/jayfeihe/jay-plat.git
5.構建-->增加構建步驟-->Execute Windows batch commnad :命令進入要構建項目
cd plat-config-server
mvn clean install
6.構建後操作-->增加構建後操作-->shell腳本,將構建後生產的jar或war放到指定服務器並啓動
7.點擊 Apply --> 點擊保存
8.立即構建
2.構建Maven+Git項目
參考:http://blog.csdn.net/pucao_cug/article/details/52373655
https://m.aliyun.com/yunqi/articles/64970
http://jdkleo.iteye.com/blog/2159844
1.新建
2.輸入項目名稱 server2 ---> 選擇 構建一個maven項目 -->點擊OK
3.General ---> 添加項目描述
4.源碼管理 --> Git
Repositories
Repository URL:https://github.com/jayfeihe/jay-plat.git
5.構建環境
1.Pre Steps 可以添加構建之前執行的命令
2.Build
Root POM : plat-config-server\pom.xml 這裏選擇以plat-config-server下的pom.xml構建項目 !!!
6.構建後操作-->增加構建後操作-->shell腳本,將構建後生產的jar或war放到指定服務器並啓動
7.點擊 Apply --> 點擊保存
8.立即構建
特別注意!!!
構建環境中,Build 如果clone下的Git項目中包含多個Pom子項目,
可通過ROOT POM 來指定具體構建哪一個Maven項目 !!!
3.構建Pipeline項目 , 需要寫基於Groovy的Jenkinsfile腳本
Jenkins Pipeline 參考:
http://www.07net01.com/2016/12/1731789.html
http://www.cnblogs.com/wzy5223/p/5554935.html
84.Redis實現分佈式鎖
*
* 基於Redis分佈式鎖實現的秒殺:http://blog.csdn.net/u010359884/article/details/50310387
*
* 在集羣等多服務器中經常使用到同步處理一下業務,這是普通的事務是滿足不了業務需求,需要分佈式鎖
*
* 分佈式鎖的常用幾種實現:
Redission實現的基於redis的分佈式鎖
* 0.數據庫樂觀鎖實現
* 1.Redis實現 --- 使用redis的setnx()、get()、getset()方法,用於分佈式鎖,解決死鎖問題
* 2.Zookeeper實現
* 參考:http://surlymo.iteye.com/blog/2082684
* http://www.jb51.net/article/103617.htm
* http://www.hollischuang.com/archives/1716?utm_source=tuicool&utm_medium=referral
* 1、實現原理:
基於zookeeper瞬時有序節點實現的分佈式鎖,其主要邏輯如下(該圖來自於IBM網站)。大致思想即爲:每個客戶端對某個功能加鎖時,在zookeeper上的與該功能對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。
2、優點
鎖安全性高,zk可持久化
3、缺點
性能開銷比較高。因爲其需要動態產生、銷燬瞬時節點來實現鎖功能。
4、實現
可以直接採用zookeeper第三方庫curator即可方便地實現分佈式鎖
*
* Redis實現分佈式鎖的原理:
* 1.通過setnx(lock_timeout)實現,如果設置了鎖返回1, 已經有值沒有設置成功返回0
* 2.死鎖問題:通過實踐來判斷是否過期,如果已經過期,獲取到過期時間get(lockKey),然後getset(lock_timeout)判斷是否和get相同,
* 相同則證明已經加鎖成功,因爲可能導致多線程同時執行getset(lock_timeout)方法,這可能導致多線程都只需getset後,對於判斷加鎖成功的線程,
* 再加expire(lockKey, LOCK_TIMEOUT, TimeUnit.MILLISECONDS)過期時間,防止多個線程同時疊加時間,導致鎖時效時間翻倍
* 3.針對集羣服務器時間不一致問題,可以調用redis的time()獲取當前時間
85.深入理解JVM
1.對象相關
1.對象的創建:
JVM遇到new指令時,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已被加載、解析和初始化過,
如果沒有,則必須先執行類的加載過程
類加載檢查通過後,虛擬機將爲新生對象分配內存,對象所需內存的大小在類加載完成便可完全確定,爲對象分配空間的任務,等同於把一塊確定大小的內存,從Java堆中劃分出來,把地址賦給引用對象
如果JVM堆是絕對規整的,所有用過的內存都放一邊,沒用過的內存放在另一邊,中間放着一個指針作爲分界點的指示器,
內存分配就僅僅是把指針向空閒空間那邊移動與對象大小相等的距離 ---- 指針碰撞法
如果JVM堆式不規整的,使用的內存和未使用內存相互交錯,則JVM必須維護一個列表,記錄那些內存塊可用,
在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄 ----- 空閒列表法
JVM堆的規整與否,決定了何種分配方式,JVM堆是否規整取決於GC的是否帶有壓縮整理功能!!!
Serial、ParNew收集器,使用複製算法 --- 使用 指針碰撞法 分配對象內存
CMS收集器,使用標記清除算法 --- 使用 空閒列表法 分配對象內存
對象創建存在的問題及解決:
1.線程安全問題,eg:修改一個指針所指向位置, 併發時,可能出現正在給對象A分配內存,指針還沒來得及修改,對象B又同時使用了原來的指針來分配內存的情況
解決:
方案1。對分配內存空間的動作進行同步處理 --- JVM採用CAS失敗重試的樂觀鎖機制保證操作的原子性
方案2。把內存分配的動作按照線程劃分在不同的空間之中進行,
即:每個線程在Java堆中預選分配一小塊內存,稱爲本地線程分配緩存(TLAB),那個線程要分配內存,就在哪個線程的TLAB上分配,
只有TLAB用完並分配新的TLAB時,才需要同步鎖定。
JVM使用使用TLAB,通過 -XX:+-UseTLAB參數設定
對象內存分配完成後,JVM需要將分配到的內存空間都初始化爲零值
然後JVM對對象進行必要的設置(eg:對象所屬類實例,類的元信息,對象的哈希碼,對象的GC分代年齡等信息,這些信息存在對象頭中)
此時,對象init還沒執行
最後執行new指令,按照程序進行初始化,一個對象纔是創建成功!
2.對象內存佈局
1.對象頭
1.存儲對象自身運行時數據,eg:哈希碼、GC分代年齡、鎖狀態標識、線程持有的鎖、偏向線程ID、偏向時間戳等
2.類型指針:即對象指向它的類元數據的指針, JVM依賴這個指針來確定對象是哪個類的實例
2.實例數據
1.程序中定義的各類字段屬性
2.從父類繼承的屬性
3.對齊填充
HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,
當時實例數據部分沒有對齊,就需要通過對齊填充來補全
3.對象的訪問定位
引用(棧) ----> 對象(堆),
對象訪問方式的2種實現:
1.句柄訪問
堆中劃分一塊內存作爲句柄池,reference引用中存儲的是對象的句柄地址,
句柄中包含了對象實例數據和類型數據各自的具體地址
以 句柄 爲中介,通過對象的句柄地址,查找對象的真實地址,從而找到該對象 !!!
---> 堆 實例池(對象實例數據)
棧引用 ---> 句柄
---> 方法區 對象類型數據
優點:
引用中存儲的是穩定的句柄地址,對象被移動(GC回收時,會移動對象到新內存地址)時,只會改變句柄中的對象指針,reference引用本身不用修改
2.直接訪問 --- HotSpot的默認對象訪問方式
棧中存放引用,引用中存儲的是對象的真實地址
優點:
速度更快,節省了一次指針定位的時間開銷,因爲對象的訪問在Java中非常頻繁,減小一次指針尋址開銷,可以提供性能
4.對象分配引起的內存問題
OutOfMemoryError
1.Java堆溢出
-Xmx -Xms 設置JVM的最大和最小堆內存
循環創建對象,而保持引用不回收,會導致堆內存溢出, Java Heap Space
2.棧溢出
-Xss 設置JVM的棧大小
1.線程請求的棧深度大於虛擬機所允許的最大深度 --- StackOverflowError --- 一般由遞歸或方法調用層次太深導致!!!
2.JVM在擴展棧時,無法申請到足夠的內存空間 --- OutOfMemoryError
3.方法區和運行時常量池溢出
-XX:PermSize 和 -XX:MaxPermSize 設置方法區大小
方法區存儲 Class相關信息(類名、訪問修飾符、常量池、字段描述、方法描述等),當方法區申請內存無法被滿足時 --- OutOfMemoryError PermGen Space
測試:
String.intern()方法:如果字符串常量池中已經包含一個等於String對象的字符串,則返回池中字符串,
否則,將此String對象包含的字符串添加到常量池中,並返回此String對象的引用
eg:
降低方法區大小,然後循環向常量池添加字符串,可能導致方法區內存溢出
2.GC相關
1.如何確定回收對象
1.引用計數法
給對象添加一個引用計數器,每當有一個地方引用它,計數+1, 引用失效,計數-1,任何時刻計數=0,則表示對象不再被使用,可回收
缺點:
無法解決循環依賴問題
2.可達性算法
通過一系列的 GC Roots 對象作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲"引用鏈",
當對象到GC Roots沒有任何引用鏈相連時,證明此對象是不可用的
可做GC Roots的對象:
JVM棧中引用的對象
方法區中靜態屬性引用的對象
方法區中常量引用的對象
本地方法棧中引用的對象
3.關於對象引用:
強引用: 類似 Object obj = new Object()的對象,只要強引用還存在,GC用於不會回收掉被引用的對象
軟引用: JDK1.2+提供了SoftReference類來實現軟引用,軟引用關聯着對象,在系統發生內存溢出異常之前,會把這些對象進行二次回收,如果回收軟引用後,還沒有足夠空間,拋出內存溢出異常
弱引用: JDK1.2+提供了WeakReference類實現弱引用,被弱引用關聯的對象,只能生存到下一次垃圾回收發生之前,
GC工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象
虛引用:
2.垃圾收集算法
1.標記-清除
階段一:標記 --- 首先標記出所有需要回收的對象
階段二:清除 --- 標記完成後,統一回收所有被標記的對象
問題:
1.標記和清除效率都不高
2.標記清除後會產生大量不連續的內存碎片,空間碎片過多可能導致程序在分配較大對象時,無法找到足夠內存,而提前引發一次GC收集
2.複製算法 --- 適合新生代
爲了解決效率問題,複製算法將內存按容量劃分爲大小相等的2份,每次只使使用其中的一塊,
當這一塊的內存用完,就將還存活的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉
每次都對整個半區進行內存回收,內存分配時不用考慮內存碎片問題,只要移動堆頂指針,按順序分配內存即可
優點:
實現簡單,運行高效
問題:
將內存縮小了一般
適合: 新生代的GC對象回收
新生代對象,絕大多數生命週期比較短,將新生代分爲 較大的Eden和2個等大較小的from和to區,,
每次使用Eden和其中一個Survivor,當回收時,將Eden和Survivor中還存活的對象一次性複製到另外一個Survivor空間上,
最後清理掉Eden和剛纔使用過的Survivor空間
HotSpot默認 Eden和Survivor比例 8:1:1,即:每次新生代中可用內存空間爲新生代容量的 90%(eden+一個Survivor),只有10%的浪費
3.標記-整理 --- 適合老年代
老年代特點:生命週期長,被回收的少,不適合複製算法
標記過程與算法一相同,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都想一端移動,然後直接清理掉端邊界以外的內存
即:
把未被標記的對象,移動到一端,指針指到臨界端,清除臨界端指針以外的內存空間,避免了內存碎片,同空間浪費,但增加了存活對象的移動開銷
4.分代收集算法
新生代 --- 複製算法 --- 每次GC都有大批對象死去,只有少量存活
老年代 --- 標記整理 --- 對象存活率高,沒有額外空間擔保
3.垃圾收集器 --- 內存回收的具體實現
1.Serial 收集器 --- Client模式下,簡單高效 --- 不能用於服務器環境,GC會停止一切服務!!!
單線程收集器,新生代,GC時,必須暫停其他所有工作線程,直到收集結束
2.ParNew收集器 --- 適合新生代GC
Serial的多線程版本,配合 CMS (老年代收集器) 使用
3.Parallel收集器 --- 新生代收集器,使用複製算法的並行多線程收集器
Parallel目標:達到一個可控制的吞吐量
CMS目標:關注點是儘量縮短垃圾收集時,用戶線程的停頓時間
4.Serial Old --- Serial收集器的老年代版本,單線程,使用標記-整理算法
5.Parallel Old --- Parallel收集器的老年代版本,多線程,使用標記-整理算法, 只能配合新生代是Parallel使用
6.CMS --- 適合老年代,以獲取最短回收停頓時間爲目標的收集器 --- 最適合 服務器端使用!!!
--- 基於"標記清除算法"實現
GC時4個步驟:
1.初始標記
2.併發標記
3.重新標記
4.併發清除
解析:
初始標記和重新標記,需要 "停止一切",
初始標記:標記一下 GC Roots能直接關聯到的對象,速度很快
併發標記:進行 GC Roots 跟蹤的過程
重新標記:爲了修正併發標記期間,因用戶程序繼續運行而導致標記產生變動的那部分對象,這個節點停頓時間一般會比初始標記節點稍長,但遠比並發標記時間短
整個過程,併發標記和併發清除耗時最長, 但這兩個過程都是多線程的,可以與用戶線程一起工作,
總體上看,CMS收集器的內存回收過程和用戶一起併發執行
優點:
併發收集,低停頓,適合老年代!!!
缺點:
標記-清除算法 會產生內存碎片,需要特殊的參數定義:對內存碎片進行合併,以避免大對象直接分配到老年代
GC執行時,會和用戶線程搶CPU,對CPU資源非常敏感
7.G1 --- JDK1.7+提供,面向服務端應用的垃圾收集器!!!
特點:
併發並行,G1能充分利用多CPU,多核環境下的硬件優勢,使用多個CPU來縮短 "停止一切" 的停頓時間
空間整合,整體上看是基於 標記-整理 算法實現的, 但局部上看是基於 複製算法實現的,
不會產生內存碎片,收集後能提供規整的可用內存
注意:
1.Parallel用於新生代 ,老年代必須用Parallel Old
2.ParNew(新生代)+CMS(老年代),吞吐量和性能 > Parallel + Parallel Old
4.內存分配與回收策略
1.對象優先在Eden分配,當Eden區沒有足夠空間進行分配時,觸發一次Minor GC
2.大對象直接進入老年代,可通過參數設置大對象的大小 eg:很長的字符串或數組
3.長期存活的對象進入老年代,每次GC,存活的對象年齡+1,當對象年齡達到閾值,還沒被回收,就進入老年代
4.動態對象年齡判定
如果在Survivor空間中,相同年齡所有對象大小總和大於 Survivor空間的一半,年齡大於或等於概念了的對象可以直接進入老年代,而不必達到年齡設置的閾值
5.空間分配擔保
Minor GC(新生代GC)能回收的內存,儘量不要用Full GC(老年代GC)來做, 性能問題!!!
在發生Minor GC之前, JVM會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,
如果條件成立,則Minor GC可以確保是安全的,
如果不成立,JVM會查看HandlePromotionFailure設置值是否允許擔保失敗,
如果允許,JVM繼續檢查老年代最大可用的連續空間十分大於歷次晉升到老年代對象的平均大小,
如果大於,進行一次Minor GC
如果小於,進行一次Full GC
如果HandlePromotionFailure設置值不允許擔保失敗,則進行一次Full GC
3.JVM類加載機制
1.JVM類加載機制:
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可被虛擬機直接使用的Java類型的過程,叫JVM的類加載機制
Java中,類型的加載、連接、初始化都是在程序運行期間完成的,會稍微增加一些性能開銷,但提高了高度的靈活性,
Java動態擴展特性,就是依賴運行期動態加載和動態連接特定實現的
eg:
面向接口的程序,可以等到運行時再指定其實際實現類!!!
用戶可以通過Java預定義和自定義類加載器,讓一個本地程序可以運行從網絡或其他地方加載的二進制流作爲程序的一部分!!!
1.類加載時機
類的的生命週期
加載 ---> 連接(驗證 --> 準備 --> 解析) ---> 初始化 ---> 使用 ---> 卸載
5種情況立刻對類進行初始化(加載、連接在初始化之前執行)
1.遇到new、getstatic、putstatic、invokestatic時,如果類沒被初始化,則先觸發初始化
即:new關鍵字實例化對象、讀取或設置一個類的靜態字段(final變量,已經在編譯期把結果放入常量池),調用一個類的靜態方法
2.使用java.lang.reflect包的方法對類進行反射調用時,如果類沒被初始化,則先觸發初始化
3.初始化一個類時,其父類未初始化,先初始化其父類
4.JVM啓動時,用戶需要指定一個執行的主類(包含main()方法的類),虛擬機會先初始化這個類
5.使用JDK1.7+動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果,涉及到類的靜態變量或靜態方法時,先對涉及的類進行初始化
解析:
以上5種場景,稱爲對一個類進行主動引用!!!
除此之外,所有引用類的方式都不會觸發初始化,稱爲被動引用!!!
被動引用示例:
1.對於靜態字段,只有直接定義這個字段的類纔會被初始化,!!!
通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化!!!
2.常量會在編譯期存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量類的初始化!!!
eg1:
/**
*被動使用類字段演示一:
*通過子類引用父類的靜態字段,不會導致子類初始化
**/
public class SuperClass{
static{
System.out.println("SuperClass init!");
}
public static int value=123;
}
public class SubClass extends SuperClass{
static{
System.out.println("SubClass init!");
}
} /**
*非主動使用類字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(SubClass.value);
}}
結果:
SuperClass init!
123
分析:
對於靜態字段,只有直接定義這個字段的類纔會被初始化,!!!
通過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化!!!
eg2:
/**
*常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。
**/
public class ConstClass{
static{
System.out.println("ConstClass init!");
} public static final String HELLOWORLD="hello world";
} /**
*非主動使用類字段演示
**/
public class NotInitialization{
public static void main(String[]args){
System.out.println(ConstClass.HELLOWORLD);
}}
結果:
hello world
解析:
常量在編譯期會存入調用類的常量池中,本質上並未引用到定義常量的類,因此ConstClass不會被初始化!!!
2.類加載過程
1.加載:
完成3件事
1.通過類全限定名來獲取定義此類的二進制字節流 --- 全限定名獲取類的二進制字節流
2.將這個字節流所代表的靜態存儲結果轉化爲方法區的運行時數據結構 --- 把字節流,轉化爲方法區運行時數據結構
3.在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口 --- 生成類的Class對象
2.連接:
1.驗證:
確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並不會危害虛擬機自身的安全
1.文件格式驗證
2.元數據驗證
3.字節碼驗證
4.符號引用驗證
2.準備:
1.爲static類變量分配內存(存儲在方法區),並設初始零值,賦真實值實在初始化時,而實例變量則在對象創建時隨對象一起分配在Java堆中
2.爲final變量分配內存(方法區)並賦真實值
3.解析:
把常量池內的符號引用替換爲直接引用的過程
符號引用:以一組符號來描述所引用的目標,符號可以是任何形式的字面量,引用的目標並不一定已經加載到內存中
直接引用:可以是直接指向目標的指針,相對偏移量或一定能間接定位到目標的句柄。直接引用的目標一定以及在內存中存在
3.初始化:
類加載過程中,除了在加載階段用戶可以通過自定義類加載器參與外,其他動作都是由JVM主導和控制的,
初始化階段,才真正開始執行類中定義的Java代碼
1.爲static類變量賦初始值(準備階段賦零值,初始化階段賦代碼中定義的真實值)
2.初始化類變量和其他資源,主要是執行類構造器<cinit>()方法的過程
關於<cinit>()方法
1.<cinit>()主要由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合併產生的,
按照語句在源碼中出現的順序,靜態語句塊中只能訪問定義在其之前的變量,定義在其後的靜態語句塊變量,可以賦值,但不能訪問
2.<cinit>()方法不需要顯示調用父類構造器,JVM會保證在子類的<cinit>方法執行前,父類的<cinit>方法執行完畢,因此JVM中第一個執行的<cinit>()方法是Object
3.父類中定義的靜態語句(塊)先於子類中定義的靜態語句(塊)執行,
4.代碼執行順序
父靜態塊 <-- 子靜態塊 <-- 父普通代碼塊 <-- 父構造器 <-- 子普通代碼塊 <-- 子構造器
1.父類靜態成員和靜態初始化快,按在代碼中出現的順序依次執行。
2.子類靜態成員和靜態初始化塊,按在代碼中出現的順序依次執行。
3. 父類的實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
4.執行父類的構造方法。
5.子類實例成員和實例初始化塊,按在代碼中出現的順序依次執行。
6.執行子類的構造方法。
5.類加載器
用於實現類的加載,對任意一個類,都需要由它的類加載器和這個類本身,一同確立其在JVM中的唯一性。
即:
比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義!!!
即使2個類來源於同一個Class文件,被同一個JVM加載,只要加載它們的類加載器不同,這兩個類就必定不相等!!!
這裏的相等,包括代表類Class對象的:
equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果 和 instanceof 做對象所屬關係的判定
1.雙親委派機制
啓動類加載器Bootstrap ClassLoader(C++實現),是JVM自身的一部分
其他類加載器,由Java實現,獨立於虛擬機外部,繼承自java.lang.ClassLoader
雙親委派機制工作過程:
如果一個類加載器收到了類加載請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,
每一個層次的類加載器都是如此,因此所有的類加載請求最終都應該傳送到頂層的啓動類加載器中,
只有當父加載器反饋自己無法完成這個加載請求時(它的搜索範圍中沒有找到所需的類),子加載器纔會嘗試自己去加載
啓動類加載器:加載 <JAVA_HOME>\lib\rt.jar裏所有的類, C++實現,不是ClassLoader子類
擴展類加載器:加載 <JAVA_HOME>\lib\ext目錄中的類
應用程序類加載器:加載 類路徑ClassPath上指定的類庫和目錄中的class --- 如果沒有自定義加載器,這個是應用程序默認的加載器!!!
類加載器層次關係:
啓動類加載器
Bootstrap ClassLoader
|
|
擴展類加載器
Extension ClassLoader
|
|
應用程序類加載器
Application ClassLoader
| |
| |
自定義類加載器1 自定義類加載器2
4.Java內存模型與線程
1.Java內存模型主要目標是定義程序中各個變量的訪問規則,
即:在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。
注意:
這裏的變量包括實例字段、靜態字段和構成數組對象的元素,
不包括局部變量和方法參數(它們是線程私有的,不會被共享,不存在競爭問題)
Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,
線程的工作內存中保存了該線程使用變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀取主內存中的變量
不同的線程之間也無法直接訪問對方工作內存中變量,線程間變量值的傳遞,需要通過主內存來完成。
一般:
主內存指的是堆內存,
工作內存優先存儲於寄存器或高速緩存總,因爲程序運行時主要訪問讀寫的是工作內存
2.內存間交互操作
主內存與工作內存交互協議:即:一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存指令的實現細節
Java內存模型定義了8種操作:
lock(鎖定): 作用於主內存的變量,把一個變量標識爲一條線程獨佔的狀態
unlock(解鎖): 作用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定
read(讀取): 作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存,以便隨後的load動作使用
load(載入): 作用於工作內存的變,它把read操作從主內存中得到的變量值放入工作內存的變量副本中
use(使用): 作用於工作內存的變量,它把工作變量內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時,執行這個操作
assign(賦值): 作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存變量,每當虛擬機遇到一個給變量賦值的字節碼指令是,執行此操作
store(存儲): 作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨後的write操作使用
write(寫入): 作用於主內存的變量,它把store操作從工作內存中得到的變量值,放入主內存變量中
解析:
如果要把一個變量從主內存複製到工作內存,就要順序執行read和load操作,
如果要把一個變量從工作內存複製到主內存,就要順序執行store和write操作,
特別注意:
Java內存模型只要求,以上2對操作必須按順序執行,而沒有保證是連續執行,
即:read和load,store和write直接是可插入其他指令的,
eg:
對主內存中變量a、b進行訪問時,一種可能出現順序是 read a、read b、load b、load a
注意:
Java內存模型規定了在執行以上8種操作時,必須滿足如下規則:
1.不允許read和load、store和write操作之一單獨出現,即:不允許一個變量從主內存讀去了但工作內存不接受,或從工作內存發起回寫而主內存不接受的情況出現
2.不允許一個線程丟棄它最近的assign操作,即:變量在工作內存中改變之後,必須把變化同步會主內存
3.不允許一個線程無原因的(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存
4.一個變量只能在主內存中 "誕生",不允許在工作內存中直接使用一個未被初始化(read和load)的變量,即:對一個變量實施use、store操作之前,必須先執行過了assign和load操作
5.一個變量在同一時刻只允許一條線程對其進行Lock操作,但lock操作可以被同一條線程重複執行,多次執行後,只有執行相同次數的unlock操作,變量纔會被解鎖
6.如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assgin操作,初始化變量的值
7.變量沒被lock,不允許對其執行unlock操作
8.對一個變量執行unlock之前,必須先把此變量同步會主內存中(執行store、write操作)
3.對volatile變量的特殊規則
1.保證可見性
對所有線程可見,一條線程修改了此變量,新值對其他線程是立即可知的,
強制所有線程,操作該變量時,重新重主內存中獲取,操作完之後,立即同步到主內存
2.禁止指令重排序
3.不保證原子性!!!
valatile用途:
1.用volatile控制併發
2.結合CAS實現的原子性,實現樂觀鎖
4.原子性、可見性、有序性
1.原子性保證
1.synchronized 原理:通過字節碼指令 monitorenter和monitorexit來保證線程安全
2.Lock 對鎖定部分的代碼,執行同步操作,同一時刻只允許一個線程操作鎖定的代碼
3.原子類AutoInteger
2.可見性 volatile
3.有序性 volatile、synchronized、先行發生原則
5.先行發生原則
判斷數據是否存在競爭、線程是否安全的主要依據,依靠這個原則,可以通過幾條規則,一攬子地解決併發環境下兩個操作之間是否可能存在衝突的所有問題!
定義:
先行發生是Java內存模型中定義的兩項操作之間的偏序關係,如果說操作A先行發生於操作B,
即:發生操作B之前,操作A產生的影響能被操作B觀察到 -- "影響" 包括 修改了內存中共享變量的值、發送了消息、調了方法等
5.線程安全
1.實現方法
1.synchronized互斥同步
synchronized關鍵字經過編譯後,會在同步塊的前後分表形成monitorentrer和monitorexit兩個字節碼指令,調用操作系統的mutex互斥鎖實現!!!
根據JVM虛擬機要求,在執行monitorenter指令是,首先要嘗試獲取對象的鎖,如果這個對象沒被鎖定,或者當前線程已經擁有了那個對象的鎖,把鎖的計數器+1;
相應的在執行monitorexit指令時,將鎖計數器 -1,當計數器爲0時,鎖被釋放,如果獲取對象鎖失敗,則當前線程就要阻塞等待,知道對象鎖被另外一個線程釋放爲止
synchronzied是非公平鎖
2.Lock --- ReentrantLock可重入鎖
ReentrantLock增加了高級特性:
1.等待可中斷
當持有鎖的線程長期不釋放鎖時,正在等待的線程可以選擇放棄等待,該做其他事情,可中斷特性對處理執行時間非常長的同步塊很有幫助
2.可實現公平鎖
公平鎖;多個線程在等待同一個鎖時,必須按照申請鎖的時間順序依次獲得鎖,
ReentrantLock默認也是非公平的,但可通過構造器設置其爲公平鎖
3.鎖可以綁定多個Condition條件
一個ReentrantLock可以綁定多個Condition條件
而synchronized中,鎖對象的wait()、notify()、notifyAll()方法可以實現一個隱含條件
3.非阻塞同步
阻塞式同步,悲觀鎖 --- 互斥同步,主要問題是進行線程阻塞和喚醒所帶來的性能問題
總是認爲會出問題,無論共享數據是否真的會出現競爭,都要加鎖
非阻塞同步,樂觀鎖 --- 基於衝突檢測的樂觀併發策略
通常:先執行操作,如果沒有其他線程爭用共享數據,就操作成功,
如果共享數據有爭用,產生了衝突,就採取補償機制(不斷重試,直到成功爲止)。
樂觀併發策略的許多實現並不需要把線程掛起 --- 非阻塞同步
底層實現:
CAS ==> 依賴硬件指令,來保證原子性:
4.鎖優化
自旋鎖 JDK6後默認開啓
互斥同步時,對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操作都需要轉入內核態完成,這些操作給系統的併發性能帶來了很大壓力。
許多應用,共享數據的鎖狀態只會持續很短時間,爲了這段時間去掛起和恢復線程並不值得。
如果物理機有一個以上的處理器,能讓2個或以上的線程同時執行,就可以讓後面請求鎖的那個線程"稍等一下",但不放器處理器的執行時間,
看看持有鎖的線程是否很快就會釋放,爲了讓線程等待,只需要讓線程執行一個忙循環(自旋) --- 自旋鎖
自旋等待本身雖然避免了線程切換的開銷,但要佔用處理時間,鎖佔用時間短時,自旋等待效果好,如果所佔用時間長,自旋的線程只會白白消耗CPU資源
輕量級鎖
本意是在沒有多線程競爭前提下,減少傳統的重量級鎖適用操作系統互斥而產生的性能消耗,
偏向鎖
86.MySQL事務隔離級別和Spring的事務傳播機制?(同程面試)
事務的ACID特性:
原子性(要麼都做,要麼都不做)、
一致性(事務執行結果,必須是使數據庫從一個一致性狀態變到另一個一致性狀態)、
隔離性(事務之間不能干擾)、
永久性(事務一旦提交,數據的改變是永久的)
MySQL的4種隔離級別
--- 用來限定事務內外的哪些改變是可見的,哪些是不可見的。
--- 低級別的隔離級一般支持更高的併發處理,並擁有更低的系統開銷
Read Uncommited --- 讀取未提交內容
所有事務都可以看到其他未提交事務的執行結果。
本隔離級別很少用於實際應用,
讀取未提交的數據 --- 髒讀
Read Commited --- 讀取提交內容
大多數數據的默認隔離級別, 但非MySQL默認的!!!、
一個事務只能看見已經提交事務所做的改變,
這種隔離級別,支持不可重複讀,因爲同一事務的其他實例在該實例處理期間可能會有新的commit,所有同一select可能返回不同結果
Repeated Read --- 可重讀
MySQL的默認事務隔離級別!!!
它確保同一事務的多個實例在併發讀取數據時,會看到同樣的數據行。
幻讀問題:幻讀指當用戶讀取某一範圍的數據行時,另一個事務又在該範圍內插入了新行,當用戶再讀取該範圍的數據行時,會發現有新的 "幻影" 行。
InnoDB通過多版本併發控制機制解決了該問題!!!
Serizliable --- 可串行化
最高的隔離級別,它通過強事務排序,使之不可能相互衝突,從而解決幻讀問題,
即:它在每個讀的數據行上加上共享鎖!!!
該隔離級別,可能導致大量的超時現象和鎖競爭!!!
4種隔離級別是通過採取不同的鎖類型來實現的,若讀完的是同一個數據,就容易發生以下問題:
髒讀:
某個事物已更新一份數據,另一個事務在此時讀取了同一份數據,但由於某些原因,前一個事務回滾了,則後一個事務所讀取的數據是髒數據!!!
不可重複讀:
在一個事務的兩次查詢之中數據不一致,可能是兩次查詢過程中,插入了一個事務更新操作
幻讀:
在一個事務的兩次查詢彙總數據筆數不一致,
eg:一個事務查詢了幾列數據,而另一個事務卻在此時插入了新的幾列數據,
先去的事務在接下來的查詢中,就會發現有借了數據是它之前所沒有的
4種隔離級別與可能產生的問題如下:
隔離解別 髒讀不可重複讀 幻讀
Read UncommittedY Y Y
Read CommittedN Y Y
Repeatable(default)N N Y
Serializable N N N
Spring事務傳播機制
事務傳播行爲類型 說明
PROPAGATION_REQUIRED如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是 最常見的選擇。
PROPAGATION_SUPPORTS支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY使用當前的事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED 類似的操作。
87.Spring中的bean,在初始化之後或銷燬之前執行指定功能,幾種方式? (同程面試)
3種方式:
1.通過註解 @PostConstruct 和 @PreDestory 方法,實現初始化和銷燬bean之前的操作
eg:
public class DataInitializer{
@PostConstruct
public void initMethod() throws Exception {
System.out.println("initMethod 被執行");
}
@PreDestroy
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被執行");
}
}
2.在xml中定義init-method和destory-method方法
1.Bean中定義方法
public class DataInitializer{
public void initMethod() throws Exception {
System.out.println("initMethod 被執行");
}
public void destroyMethod() throws Exception {
System.out.println("destroyMethod 被執行");
}
}
2.xml中對bean配置初始化init-method和銷燬前destory-method執行的方法
<bean id="dataInitializer" class="com.jay.demo.DataInitializer"
init-method="initMethod" destory-method="destroyMethod"/>
3.通過Bean實現InitializingBean和DisposableBean接口
public class DataInitializer implements InitializerBean, DisposableBean{
@Override
public void afterPropertiesSet() throws Exception{
System.out.println("Bean初始化後,afterPropertiesSet被執行");
}
@Override
public void destory() throws Exception{
System.out.println("Bean被銷燬之前, destory被執行");
}
}
分析:
1.方式1和方式2,本質是一個,只是一個註解方式實現,一個xml配置方式實現
2.Bean實例化執行的順序:
初始化:
Constructor ---> @PostConstruct ---> InitializingBean(afterPropertiesSet方法) ---> init-method(xml配置)
銷燬:
@PreDestory ---> DisposableBean(destory方法) ---> destory-method(xml配置)
執行順序源碼分析:
3.實現InitializingBean接口是直接調用afterPropertiesSet方法,比通過反射調用init-method指定方法效率相對來說要高點,但 init-method方式,消除了對Spring的依賴!
4.如果調用afterPropertiesSet()方法時出錯,則不調用init-method指定的方法
5.Spring容器中Bean實例完整的生命週期:
開始 -->創建實例
--> 注入依賴關係
--> 調用afterPropertiesSet()方法
--> 調用init-method方法
--> 對外提供服務
--> 調用destory()方法
--> 調用destory-method指定的方法
--> 結束
注意:
當Bean實現ApplicationAware、BeanNameAware接口後,Spring容器會在該Bean初始化完成後,
即:init-method指定方法(如果有)執行之後,再來回調setApplicationContext(ApplicationContext context)和setBeanName(String beanName)方法
88.在項目啓動前執行預處理方法的幾種方式?
1.Web項目Servlet啓動、執行、銷燬的全過程
1.讀取配置信息
啓動Web項目時,容器(Tomcat)會讀取配置文件(web.xml)中的<listener/>和<context-param/>標籤
2.創建監聽類
由容器創建<listener/>監聽類實例,用於監聽ServletContext、HttpSession的聲明週期及書序變更
3.創建上下文
由容器創建ServletContext上下文實例,這時監聽類實例會調用其contextInitialized(ServletContextEvent args)方法,並傳入讀取的<context-parm/>鍵值對,
在該方法中可以讀取、操作ServletContext鍵值對
eg:
ServletContext = ServletContextEvent.getServletContext();
Value = ServletContext.getInitParameter(Key);
4.容器調用繼承HttpServlet接口的類的構造方法,創建Servlet
5.創建ServletConfig
容器創建ServletConfig對象(包含Servlet初始化信息),並將ServletConfig對象與ServletContext對象關聯
6.初始化Servlet
容器調用Servlet對象的初始化init(ServletConfig config)方法,並傳入ServletConfig參數初始化Servlet
7.接收請求
當容器接收到Servlet請求時,容器創建ServletRequest和ServletResponse對象,然後調用service()方法,並傳入參數,進行處理
8.響應請求
Service()方法通過ServletRequest對象獲得請求信息,並處理該請求,再通過ServletResponse對象生成響應結果
9.銷燬Servlet
當Web應用被終止時,Servlet容器會先調用Web應用中所有Servlet對象的destory()方法,然後再銷燬Servlet對象。
此外容器還會銷燬與Servlet對象關聯的ServletConfig對象。在destroy()方法的實現中,可以釋放servlet所佔用的資源。如關閉文件輸入輸出流,關閉與數據庫的連接
1.在項目啓動時,執行某個方法的5種方式
1.實現Servlet監聽器接口--- ServletContextListener
eg:
1.定義監聽器
public class InitListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent context) {
}
@Override
public void contextInitialized(ServletContextEvent context) {
// 上下文初始化執行
System.out.println("================>[ServletContextListener]自動加載啓動開始.");
SpringUtil.getInstance().setContext( WebApplicationContextUtils.getWebApplicationContext(arg0.getServletContext()));
}
}
2.web.xml中配置該監聽器
<listener>
<listener-class>com.test.init.InitListener</listener-class>
</listener>
2.實現Servlet的過濾器接口--- Filter
1.自定義過濾器,重寫 init()方法
2.在web.xml中配置過濾器
3.編寫Servlet,在web.xml中配置容器啓動後執行即可 --- HttpServlet
1.自定義Servlet,重寫 init()方法
2.web.xml中配置Servlet
4.使用Spring IOC管理Bean,可以指定init-method,在bean加載成後,立即執行某個方法
5.使用Spring IOC管理Bean,可以實現Spring Bean後置處理器接口 --- BeanFactoryPostProcessor --- 表示該Bean加載完成後,執行一些自定義事件
public class KeyWordInit implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
System.out.println("================>[BeanFactoryPostProcessor]自動加載啓動開始.");
ShopService shopService = factory.getBean("shopService", ShopService.class);
List<map<string, object="">> shopList = shopService.findAllShop();
System.out.println("================>" + shopList);
System.out.println("================>[BeanFactoryPostProcessor]自動加載啓動結束.");
}
}
5種方式的執行順序:
4===>5===>1===>2===>3
即:
指定init-method的Bean開始執行。
接着實現spring的Bean後置處理器開始執行
然後是Servlet的監聽器執行
再接下來是Servlet的過濾器執行
最後纔是Servlet執行
特別注意:
Spring提供的項目啓動時執行的2種方法:
1.ApplicationListener<ContextRefreshedEvent> --- Spring容器初始化完成後,執行 onApplicationEvent()方法
可以使用Spring的 ApplicationListener,也可以完成項目啓動時,Spring容器初始化完成後,執行方法!!!
參考:
http://blog.csdn.net/ilovejava_2010/article/details/7953419
@Service
public class StartGateServiceData implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 在web項目中(spring mvc),系統會存在兩個容器,一個是root application context
// ,另一個就是我們自己的 projectName-servlet context(作爲root application context的子容器)。
// 這種情況下,就會造成onApplicationEvent方法被執行兩次。爲了避免這個問題,我們可以只在root
// application context初始化完成後調用邏輯代碼,其他的容器的初始化完成,則不做任何處理。
if (event.getApplicationContext().getParent() == null) {
//需要執行的邏輯代碼,當spring容器初始化完成後就會執行該方法。
}
}
}
2.自定義類,實現InitializingBean接口,然後交由Spring容器管理
eg:
需求:
權限管理系統,在項目啓動時,把用戶權限和資源信息加載到內存
分析:
給web容器添加一個Listener類,在容器啓動的時候執行Listener的“初始化”方法,在這個初始化方法中執行查詢數據庫的所有操作,然後將數據庫中的信息緩存起來
問題:
查詢DB的Service和dao都是Spring IOC控制,Listener類只是在系統啓動時會執行初始化方法,
但此時Service還沒被Spring管理(Spring容器還沒創建),即:Service和Dao無法訪問數據庫
解決:
自定義類,實現InitializingBean接口,然後交由Spring管理
Spring容器啓動後,加載該類,自動執行其中的方法
參考:http://hbiao68.iteye.com/blog/2026210
89.線程安全的集合類
1.Vector、HashTable
2.Collections.synchronizedXxx(List、Set、Map)
3.併發包中
CopyOnWriteArrayList --- 其中的set、add、remove等方法,都使用了ReentrantLock來加鎖和解鎖,
當增加元素時,使用Arrays.copyOf()來拷貝副本,在副本上增加元素,然後改變原引用指向副本
CopyOnWriteArraySet --- 使用了
ConcurrentHashMap --- 允許多個修改操作併發進行,關鍵在於使用了鎖分離技術。
它使用多個鎖來控制對hash表的不同部分(Segment)進行修改,
每個段其實就是一個小的hashtable,它們都有自己的鎖(由Segment繼承ReentrantLock實現),
只要多個修改操作發生在不同的Segment段上,它們就能併發的進行
JDK1.8後,HashMap和ConcurrentHashMap的每個Segment都是通過紅黑樹實現!!!
各類BlockingQueue(ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue(基於優先級隊列實現)、SynchronousQueue)
ConcurrentLinkedQueue
ConcurrentSkipListMap
實現參考:http://www.2cto.com/kf/201212/175026.html
ConcurrentSkipListMap 繼承 AbstractMap 實現ConcurrentMap接口
1.SkipList 跳錶
Skip List是一種隨機化的數據結構,基於並聯的鏈表,其效率可以比擬二叉查找樹(O(log n)),
跳躍列表是對有序的鏈表增加上附加的前進鏈接,增加是以隨機化方式進行的,所以在列表中的查找可以快速的跳過部分列表,
所有操作都是以對數隨機化的時間進行。
Skip List可以很好解決有序鏈表查找特定值的困難
Skip List(跳錶)是一種可以代替平衡樹的數據結構,默認按key值升序。
SkipList讓已排序的數據分佈在多層鏈表中,以0-1隨機數決定一個數據的向上攀升與否,通過 "空間來換取時間"的一個算法,
在每個節點中增加了向前的指針,在插入、刪除、查找時可忽略一些不可能設計到的節點,從而提高了效率
2.ConcurrentSkipListMap
提供了一種線程安全的併發訪問的排序映射表。
內部是SkipList(跳錶)結構實現,理論上能在O(log n)時間內完成查找、插入、刪除操作。
調用ConcurrentSkipListMap的size時,由於多線程可以同時對映射表進行操作,所以映射表需要遍歷整個鏈表才能返回元素個數
3.ConcurrentSkipListMap有幾個ConcurrentHashMap不可比擬的優點:
1.ConcurrentSkipListMap的key是有序的
2.ConcurrentSkipListMap支持更高的併發,存取時間是 O(log N),和線程數幾乎無關,
即:在數據量一定的情況下,併發的線程越多,ConcurrentSkipListMap越能體現出其優勢
4.使用建議:
1.非多線程下,如果要保證有序,儘量使用TreeMap, 對於併發性相對較低的程序,可用Collections.synchronizedSortedMap()將TreeMap進行線程安全同步。
2.對於多線程高併發程序,如果需要對Map的key進行排序時,應當使用ConcurrentSkipListMap,能夠提供更高的併發度
90.關於幾個常用的Map
Map接口的4個實現類
HashMap:繼承自Dictionary類,根據hashCode存儲數據,訪問速度快,無序(遍歷時,取得數據的順序是完全隨機的),最多隻允許一條記錄的key爲null,允許多個value爲null,線程不安全
HashTable:繼承自Dictionary類,不允許記錄的key或value爲null,線程安全,寫入慢
LinkedHashMap:HashMap的子類,保存了記錄插入順序,用Iterator遍歷時,按插入Map順序取值
TreeMap:實現SortMap接口,可根據Key進行自定義排序,通過比較器Comparator或key實現comparable接口來指定順序
HashMap中hash衝突的解決:
拉鍊法 --- 系統總是將新添加的 Entry 對象放入 table 數組的 bucketIndex 索引處——如果 bucketIndex 索引處已經有了一個 Entry 對象,那新添加的 Entry 對象指向原有的 Entry 對象(產生一個 Entry 鏈),如果 bucketIndex 索引處沒有 Entry 對象,也就是上面程序代碼的 e 變量是 null,也就是新放入的 Entry 對象指向 null,也就是沒有產生 Entry 鏈
hash衝突的其他解決方法:
1.開放地址法 --- 線程探測法
即:當發生地址衝突時,安裝某種方法,繼續探測哈希表中的其他存儲單元,直到找到空位置爲止,將該元素放入該槽中
91.JVM常用的分析命令
1.jstat
--- 查看JVM的堆棧信息,能夠查看eden、Survivor、old、perm等內存的capacity、utility信息,對系統是否有內存泄露以及參數設置是否合理有不錯的意義
eg:
jstat -gc pid顯示gc信息、查看gc次數及時間
jstat -gccapacity pid顯示VM內存中三代(young、old、perm)對象的使用和佔用大小
jstat -gcutil pid統計gc信息
jstat -gcnew pid年輕代對象信息
jstat -class pid 顯示加載class的數量,及所佔空間等信息
2.jstack
--- 查看JVM當前的thread dump,可以看到當前JVM中的線程狀況,對於查找阻塞的線程有幫助
--- 用於打印給定的Java進程ID或遠程調用服務的Java堆棧信息
3.jmap
--- 查看JVM當前的heap dump堆內存信息,可以看出當前JVM中各種對象的數量,所佔空間等
項目中遇到的Bug及解決?
1.線程池問題
newCachedThreadPool(),吞吐量大,阻塞隊列使用SynchronousBlockQueue吳榮林的阻塞隊列,put必須等待take,同樣take必須等待put,
適合執行耗時較短的多線程,線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程
如果瞬間進入的線程過多,而且線程耗時長,將會產生上千個線程,導致內存溢出,程序崩潰
2.Future問題,
Future + Callable可以定義新的線程,只有future.get()時,纔會等待線程執行接收,如果沒有調用get()方式,當前線程不會阻塞
如果有多個Future + Callable線程,爲了應用多線程提高併發效率,可以把提交後返回的Future放到一個List<Futrue>中,
遍歷該list,同時調用future.get(), 可以保證每個線程執行完後,才執行主程序中的代碼
3.Spring事務問題,註解式事務不生效 參考 :59
4.MyBatis原理簡述:
MyBatis程序根據XML配置文件創建SqlSessionFactory,SqlSessionFactory再根據配置(來源:配置文件和Java註解)獲取一個SqlSession。
SqlSession包含了執行SQL所需的所有方法,可以通過SqlSession實例直接運行映射的SQL語句,完成對數據的增刪改查和事務提交,用完後關閉SqlSession
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.