不足的地方請大家多多指正,如有其它沒有想到的常問面試題請大家多多評論,一起成長,感謝!~
String可以被繼承嗎?
因為Sting是這樣定義的:public final class String extends Object,里邊有final關鍵字,所以不能被繼承。
接口能繼承接口嗎?
一個接口可以繼承另一個接口,一個抽象類可以實現一個接口。
synchronized與static synchronized 的區別
synchronized是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,注意這里是“類的當前實例”, 類的兩個不同實例就沒有這種約束了。那么static synchronized恰好就是要控制類的所有實例的訪問了,static synchronized是限制線程同時訪問jvm中該類的所有實例同時訪問對應的代碼快。實際上,在類中某方法或某代碼塊中有 synchronized,那么在生成一個該類實例后,改類也就有一個監視快,放置線程并發訪問改實例synchronized保護快,而static synchronized則是所有該類的實例公用一個監視快了,也也就是兩個的區別了,也就是synchronized相當于 this.synchronized,而
static synchronized相當于Something.synchronized.
Spring BeanFactory與FactoryBean的區別
BeanFactory,以Factory結尾,表示它是一個工廠類(接口),用于管理Bean的一個工廠。在Spring中,BeanFactory是IOC容器的核心接口,它的職責包括:實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。BeanFactory,以Factory結尾,表示它是一個工廠類(接口),用于管理Bean的一個工廠。在Spring中,BeanFactory是IOC容器的核心接口,它的職責包括:實例化、定位、配置應用程序中的對象及建立這些對象間的依賴。
FactoryBean以Bean結尾,表示它是一個Bean,不同于普通Bean的是:它是實現了FactoryBean接口的Bean,根據該Bean的ID從BeanFactory中獲取的實際上是FactoryBean的getObject()返回的對象,而不是FactoryBean本身,如果要獲取FactoryBean對象,請在id前面加一個&符號來獲取。
線程有返回值嗎
在Java5之前,線程是沒有返回值的,常常為了“有”返回值,破費周折,而且代碼很不好寫。或者干脆繞過這道坎,走別的路了。
現在Java終于有可返回值的任務(也可以叫做線程)了。
可返回值的任務必須實現Callable接口,類似的,無返回值的任務必須Runnable接口。
執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。
Volatile真的能解決線程并發嗎?
用volatile修飾的變量 是java 語言提供的一種稍弱的同步機制,線程每次操作前都從主內存中刷新值,變量的更新操作也會及時的通知到其他線程。
如果把變量聲明成volatile 類型 編譯器和運行時都會注意變量值。
線程在每次使用變量的時候,都會讀取變量修改后的最的值。volatile很容易被誤用,用來進行原子性操作。
hashmap、concurrenthashmap底層實現和區別
Hashmap本質是數組加鏈表。根據key取得hash值,然后計算出數組下標,如果多個key對應到同一個下標,就用鏈表串起來,新插入的在前面。
ConcurrentHashMap:在hashMap的基礎上,ConcurrentHashMap將數據分為多個segment,默認16個(concurrency level),然后每次操作對一個segment加鎖,避免多線程鎖的幾率,提高并發效率。
hashmap概述
HashMap基于哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。(除了不同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證映射的順序,特別是它不保證該順序恒久不變。
值得注意的是HashMap不是線程安全的,如果想要線程安全的HashMap,可以通過Collections類的靜態方法synchronizedMap獲得線程安全的HashMap。
HashMap的數據結構
HashMap的底層主要是基于數組和鏈表來實現的,它之所以有相當快的查詢速度主要是因為它是通過計算散列碼來決定存儲的位置,能夠很快的計算出對象所存儲的位置。HashMap中主要是通過key的hashCode 來計算hash值的,只要hashCode相同,計算出來的hash值就一樣。如果存儲的對象對多了,就有可能不同的對象所算出來的hash值是相同的,這就出現了所謂的hash沖突。學過數據結構的同學都知道,解決hash沖突的方法有很多,HashMap底層是通過鏈表來解決hash沖突的。
HashMap的初始大小16個Entry,但是我在hashmap里面放了超過16個元素,擴容方法為resize()方法。
HashMap其實就是一個Entry數組,Entry對象中包含了鍵和值,其中next也是一個Entry對象,它就是用來處理hash沖突的,形成一個鏈表。
鏈表長度到了一定的長度,存儲結構就會變成紅黑樹
hashmap工作原理:
通過hash的方法,通過put和get存儲和獲取對象。存儲對象時,我們將K/V傳給put方法時,它調用hashCode計算hash從而得到bucket位置,進一步存儲,HashMap會根據當前bucket的占用情況自動調整容量(超過Load Facotr則resize為原來的2倍)。獲取對象時,我們將K傳給get,它調用hashCode計算hash從而得到bucket位置,并進一步調用equals()方法確定鍵值對。如果發生碰撞的時候,Hashmap通過鏈表將產生碰撞沖突的元素組織起來,在Java 8中,如果一個bucket中碰撞沖突的元素超過某個限制(默認是8),則使用紅黑樹來替換鏈表,從而提高速度。
ConcurrentHashMap
ConcurrentHashMap具體是怎么實現線程安全的呢,肯定不可能是每個方法加synchronized,那樣就變成了HashTable。
從ConcurrentHashMap代碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解為把一個大的Map拆分成N個小的HashTable,根據key.hashCode()來決定把key放到哪個HashTable中。
在ConcurrentHashMap中,就是把Map分成了N個Segment,put和get的時候,都是現根據key.hashCode()算出放到哪個Segment中:
ConcurrentHashMap中默認是把segments初始化為長度為16的數組。
ConcurrentHashMap的工作機制,通過把整個Map分為N個Segment(類似HashTable),可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。
一:spring基本概念
1)struts2是web框架,hibernate是orm框架
2)spring是容器框架,創建bean,維護bean之間的關系
3)spring可以管理web層,持久層,業務層,dao層,spring可以配置各個層的組件,并且維護各個層的關系
二:spring核心原理
1.IOC控制反轉
概念:控制權由對象本身轉向容器,由容器根據配置文件創建對象實例并實現各個對象的依賴關系。
核心:bean工廠
2.AOP面向切面編程
a.靜態代理
根據每個具體類分別編寫代理類
根據一個接口編寫一個代理類
b.動態代理
針對一個方面編寫一個InvocationHandler,然后借用JDK反射包中的Proxy類為各種接口動態生成相應的代理類
spring原理總結
1.使用spring ,沒有new對象,我們把創建對象的任務交給spring框架
2.spring實際上是一個容器框架,可以配置各種bean(action/service/domain/dao),并且可以維護bean與bean的關系,當我們需要使用某個bean的時候,我們可以getBean(id),使用即可.
jvm調優(簡單概括)
1:建議用64位操作系統,Linux下64位的jdk比32位jdk要慢一些,但是吃得內存更多,吞吐量更大。
2:XMX和XMS設置一樣大,MaxPermSize和MinPermSize設置一樣大,這樣可以減輕伸縮堆大小帶來的壓力。
3:調試的時候設置一些打印JVM參數,如-XX:+PrintClassHistogram-XX:+PrintGCDetails-XX:+PrintGCTimeStamps-XX:+PrintHeapAtGC-Xloggc:log/gc.log,這樣可以從gc.log里看出一些端倪出來。
4:系統停頓的時候可能是GC的問題也可能是程序的問題,多用jmap和jstack查看,或者killall-3java,然后查看java控制臺日志,能看出很多問題。有一次,網站突然很慢,jstack一看,原來是自己寫的URLConnection連接太多沒有釋放,改一下程序就OK了。
5:仔細了解自己的應用,如果用了緩存,那么年老代應該大一些,緩存的HashMap不應該無限制長,建議采用LRU算法的Map做緩存,LRUMap的最大長度也要根據實際情況設定。
6:垃圾回收時promotionfailed是個很頭痛的問題,一般可能是兩種原因產生,第一個原因是救助空間不夠,救助空間里的對象還不應該被移動到年老代,但年輕代又有很多對象需要放入救助空間;第二個原因是年老代沒有足夠的空間接納來自年輕代的對象;這兩種情況都會轉向FullGC,網站停頓時間較長。第一個原因我的最終解決辦法是去掉救助空間,設置-XX:SurvivorRatio=65536-XX:MaxTenuringThreshold=0即可,第二個原因我的解決辦法是設置CMSInitiatingOccupancyFraction為某個值(假設70),這樣年老代空間到70%時就開始執行CMS,年老代有足夠的空間接納來自年輕代的對象。
7:不管怎樣,永久代還是會逐漸變滿,所以隔三差五重起java服務器是必要的,我每天都自動重起。
8:采用并發回收時,年輕代小一點,年老代要大,因為年老大用的是并發回收,即使時間長點也不會影響其他程序繼續運行,網站不會停頓。
gc算法
垃圾收集算法
標記 -清除算法
“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。之所以說它是最基礎的收集算法,是因為后續的收集算法都是基于這種思路并對其缺點進行改進而得到的。
它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
復制算法
“復制”(Copying)的收集算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,持續復制長生存期的對象則導致效率降低。
標記-壓縮算法
復制收集算法在對象存活率較高時就要執行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
小編推薦一個學JAVA的學習裙【 一三三,九三零,六九三】,無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!裙內有開發工具,很多貨和技術資料分享
分代收集算法
GC分代的基本假設:絕大部分對象的生命周期都非常短暫,存活時間短。
“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。
垃圾收集器
如果說收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現
Serial收集器
串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。新生代、老年代使用串行回收;新生代復制算法、老年代標記-壓縮;垃圾收集的過程中會Stop The World(服務暫停)
參數控制:-XX:+UseSerialGC 串行收集器
spring中bean的作用域
Spring 3中為Bean定義了5中作用域,分別為singleton(單例)、prototype(原型)、request、session和global session,5種作用域說明如下:
singleton:單例模式,Spring IoC容器中只會存在一個共享的Bean實例,無論有多少個Bean引用它,始終指向同一對象。Singleton作用域是Spring中的缺省作用域,也可以顯示的將Bean定義為singleton模式,配置為:
prototype:原型模式,每次通過Spring容器獲取prototype定義的bean時,容器都將創建一個新的Bean實例,每個Bean實例都有自己的屬性和狀態,而singleton全局只有一個對象。根據經驗,對有狀態的bean使用prototype作用域,而對無狀態的bean使用singleton作用域。
request:在一次Http請求中,容器會返回該Bean的同一實例。而對不同的Http請求則會產生新的Bean,而且該bean僅在當前Http Request內有效。
,針對每一次Http請求,Spring容器根據該bean的定義創建一個全新的實例,且該實例僅在當前Http請求內有效,而其它請求無法看到當前請求中狀態的變化,當當前Http請求結束,該bean實例也將會被銷毀。
session:在一次Http Session中,容器會返回該Bean的同一實例。而對不同的Session請求則會創建新的實例,該bean實例僅在當前Session內有效。
,同Http請求相同,每一次session請求創建新的實例,而不同的實例之間不共享屬性,且實例僅在自己的session請求內有效,請求結束,則實例將被銷毀。
global Session:在一個全局的Http Session中,容器會返回該Bean的同一個實例,僅在使用portlet context時有效。
spring中單例的bean是不是線程安全的
Spring框架并沒有對單例bean進行任何多線程的封裝處理。關于單例bean的線程安全和并發問題需要開發者自行去搞定。但實際上,大部分的Spring bean并沒有可變的狀態(比如Serview類和DAO類),所以在某種程度上說Spring的單例bean是線程安全的。如果你的bean有多種狀態的話(比如 View Model 對象),就需要自行保證線程安全。
最淺顯的解決辦法就是將多態bean的作用域由“singleton”變更為“prototype”。
java8中stream有幾種
Java中的Stream的所有操作都是針對流的,所以,使用Stream必須要得到Stream對象:
1、初始化一個流:
Stream stream = Stream.of("a", "b", "c");
2、數組轉換為一個流:
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
或者
stream = Arrays.stream(strArray);
3、集合對象轉換為一個流(Collections):
List list = Arrays.asList(strArray);
stream = list.stream();
spring springboot springcloud 的關系與區別
Spring Boot框架的核心就是自動配置,只要存在相應的jar包,Spring就幫我們自動配置
1、Spring boot 是 Spring 的一套快速配置腳手架,可以基于spring boot 快速開發單個微服務;Spring Cloud是一個基于Spring Boot實現的云應用開發工具;
2、Spring boot專注于快速、方便集成的單個個體,Spring Cloud是關注全局的服務治理框架;
3、spring boot使用了默認大于配置的理念,很多集成方案已經幫你選擇好了,能不配置就不配置,Spring Cloud很大的一部分是基于Spring boot來實現。
4、Spring boot可以離開Spring Cloud獨立使用開發項目,但是Spring Cloud離不開Spring boot,屬于依賴的關系。
過濾器和攔截器的區別
攔截器,在AOP(Aspect-Oriented Programming)中用于在某個方法或字段被訪問之前,進行攔截然后在之前或之后加入某些操作。攔截是AOP的一種實現策略。
過濾器是一個程序,它先于與之相關的servlet或JSP頁面運行在服務器上。過濾器可附加到一個或多個servlet或JSP頁面上,并且可以檢查進入這些資源的請求信息。
zookeeper的數據結構
ZooKeeper這種數據結構有如下這些特點:
1. 每個子目錄項如NameService都被稱作znode,這個znode是被它所在的路徑唯一標識,如Server1這個znode的標識為/NameService/Server1
2. znode可以有子節點目錄,并且每個znode可以存儲數據,注意EPHEMERAL類型的目錄節點不能有子節點目錄
3. znode是有版本的,每個znode中存儲的數據可以有多個版本,也就是一個訪問路徑中可以存儲多份數據
4. znode可以是臨時節點,一旦創建這個znode的客戶端與服務器失去聯系,這個znode也將自動刪除,ZooKeeper的客戶端和服務器通信采用長連接方式,每個客戶端和服務器通過心跳來保持連接,這個連接狀態成為session,如果znode是臨時節點,這個session失效,znode也就被刪除.
5. znode的目錄名可以自動編號,如App1已經存在,再創建的話,將會自動命名為App2.
6. znode可以被監控,包括這個目錄節點中存儲的數據被修改,子節點目錄的變化等,一旦變化可以通知設置監控的客戶端,這個是ZooKeeper的核心特性.
java8中實現for循環continue的方式是
在使用foreach()處理集合時不能使用break和continue這兩個方法,也就是說不能按照普通的for循環遍歷集合時那樣根據條件來中止遍歷,而如果要實現在普通for循環中的效果時,可以使用return來達到,也就是說如果你在一個方法的lambda表達式中使用return時,這個方法是不會返回的,而只是執行下一次遍歷
索引的數據結構
(1)B-Tree
(2)B+Tree
(3)檢索過程:首先在最上層節點進行二分查找,如果找不到則去對應左子節點或右子節點進行二分查找,以此遞歸
(4)B-Tree和B+Tree區別
—內節點不存儲data,只存儲key
—葉子節點不存儲指針(葉節點和內節點大小一般不相同)
—葉子節點具有指向相鄰葉子節點的指針(方便區間訪問)
(5)索引設計思路
—索引本身也很大,不能直接在主存中存儲,而是存在磁盤上。一個好的索引數據結構應該最大程度減少磁盤IO。
—數據庫設計者巧妙地將【樹節點】用【單位頁】進行對應存儲,這樣一個節點的載入只需要一次磁盤IO。在B-Tree定義中,檢索一次最多需要訪問h(樹高度)個節點,相應地最多需要h-1次IO(根節點常駐內存),時間復雜度O(h)= O(logdN),h為樹高度,d為單個節點中Key數量,d越大,索引性能越好。一般d比較大,即橫向比較長,h比較小,通常不超過3,因此B-Tree作為索引結構效率非常高。
(6)為什么不用二叉樹或紅黑樹作為索引數據結構?
h較深,邏輯上相鄰的父子節點在物理上可能很遠,無法利用局部性
(7)為什么B+Tree優于B-Tree?
因為d越大,索引性能越好,dmax=floor(pagesize/(keysize+datasize+pointsize)),由于B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能
Mysql索引有哪些
從數據結構角度
1、B+樹索引(O(log(n))):關于B+樹索引,可以參考 MySQL索引背后的數據結構及算法原理
2、hash索引:
a 僅僅能滿足"=","IN"和"<=>"查詢,不能使用范圍查詢
b 其檢索效率非常高,索引的檢索可以一次定位,不像B-Tree 索引需要從根節點到枝節點,最后才能訪問到頁節點這樣多次的IO訪問,所以 Hash 索引的查詢效率要遠高于 B-Tree 索引
c 只有Memory存儲引擎顯示支持hash索引
3、FULLTEXT索引(現在MyISAM和InnoDB引擎都支持了)
4、R-Tree索引(用于對GIS數據類型創建SPATIAL索引)
從物理存儲角度
1、聚集索引(clustered index)
2、非聚集索引(non-clustered index)
從邏輯角度
1、主鍵索引:主鍵索引是一種特殊的唯一索引,不允許有空值
2、普通索引或者單列索引
3、多列索引(復合索引):復合索引指多個字段上創建的索引,只有在查詢條件中使用了創建索引時的第一個字段,索引才會被使用。使用復合索引時遵循最左前綴集合
4、唯一索引或者非唯一索引
5、空間索引:空間索引是對空間數據類型的字段建立的索引,MYSQL中的空間數據類型有4種,分別是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL關鍵字進行擴展,使得能夠用于創建正規索引類型的語法創建空間索引。創建空間索引的列,必須將其聲明為NOT NULL,空間索引只能在存儲引擎為MYISAM的表中創建
CREATE TABLE table_name[col_name data type]
[unique|fulltext|spatial][index|key][index_name](col_name[length])[asc|desc]
1、unique|fulltext|spatial為可選參數,分別表示唯一索引、全文索引和空間索引;
2、index和key為同義詞,兩者作用相同,用來指定創建索引
3、col_name為需要創建索引的字段列,該列必須從數據表中該定義的多個列中選擇;
4、index_name指定索引的名稱,為可選參數,如果不指定,MYSQL默認col_name為索引值;
5、length為可選參數,表示索引的長度,只有字符串類型的字段才能指定索引長度;
6、asc或desc指定升序或降序的索引值存儲
ORM
大概地說,這類框架的是為了將類對象和關系建立映射,在應用程序和數據庫的IO之間建立一個中間層,在程序中只需要直接操作對象(數據庫中對象的增刪改查),而不用去關心數據庫中表的列啊,關系啊什么的
相關技術:JPA 、hibernate等 沒必要自己寫,自己寫出來的也不一定有他們的好,并且一些判斷、提示都沒有他們的完善
事務的傳播行為和隔離級別[transaction behavior and isolated level]
Spring中事務的定義:
一、Propagation :
key屬性確定代理應該給哪個方法增加事務行為。這樣的屬性最重要的部份是傳播行為。有以下選項可供使用:
PROPAGATION_REQUIRED--支持當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
PROPAGATION_SUPPORTS--支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支持當前事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則拋出異常。
很多人看到事務的傳播行為屬性都不甚了解,我昨晚看了j2ee without ejb的時候,看到這里也不了解,甚至重新翻起數據庫系統的教材書,但是也沒有找到對這個的分析。今天搜索,找到一篇極好的分析文章,雖然這篇文章是重點分析PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRED_NESTED的
解惑 spring 嵌套事務
這里一個個分析
1: PROPAGATION_REQUIRED
加入當前正要執行的事務不在另外一個事務里,那么就起一個新的事務
比如說,ServiceB.methodB的事務級別定義為PROPAGATION_REQUIRED, 那么由于執行ServiceA.methodA的時候,
ServiceA.methodA已經起了事務,這時調用ServiceB.methodB,ServiceB.methodB看到自己已經運行在ServiceA.methodA
的事務內部,就不再起新的事務。而假如ServiceA.methodA運行的時候發現自己沒有在事務中,他就會為自己分配一個事務。
這樣,在ServiceA.methodA或者在ServiceB.methodB內的任何地方出現異常,事務都會被回滾。即使ServiceB.methodB的事務已經被
提交,但是ServiceA.methodA在接下來fail要回滾,ServiceB.methodB也要回滾
2: PROPAGATION_SUPPORTS
如果當前在事務中,即以事務的形式運行,如果當前不再一個事務中,那么就以非事務的形式運行
這就跟平常用的普通非事務的代碼只有一點點區別了。不理這個,因為我也沒有覺得有什么區別
3: PROPAGATION_MANDATORY
必須在一個事務中運行。也就是說,他只能被一個父事務調用。否則,他就要拋出異常。
4: PROPAGATION_REQUIRES_NEW
這個就比較繞口了。 比如我們設計ServiceA.methodA的事務級別為PROPAGATION_REQUIRED,ServiceB.methodB的事務級別為PROPAGATION_REQUIRES_NEW,
那么當執行到ServiceB.methodB的時候,ServiceA.methodA所在的事務就會掛起,ServiceB.methodB會起一個新的事務,等待ServiceB.methodB的事務完成以后,
他才繼續執行。他與PROPAGATION_REQUIRED 的事務區別在于事務的回滾程度了。因為ServiceB.methodB是新起一個事務,那么就是存在
兩個不同的事務。如果ServiceB.methodB已經提交,那么ServiceA.methodA失敗回滾,ServiceB.methodB是不會回滾的。如果ServiceB.methodB失敗回滾,
如果他拋出的異常被ServiceA.methodA捕獲,ServiceA.methodA事務仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
當前不支持事務。比如ServiceA.methodA的事務級別是PROPAGATION_REQUIRED ,而ServiceB.methodB的事務級別是PROPAGATION_NOT_SUPPORTED ,
那么當執行到ServiceB.methodB時,ServiceA.methodA的事務掛起,而他以非事務的狀態運行完,再繼續ServiceA.methodA的事務。
6: PROPAGATION_NEVER
不能在事務中運行。假設ServiceA.methodA的事務級別是PROPAGATION_REQUIRED, 而ServiceB.methodB的事務級別是PROPAGATION_NEVER ,
那么ServiceB.methodB就要拋出異常了。
7: PROPAGATION_NESTED
理解Nested的關鍵是savepoint。他與PROPAGATION_REQUIRES_NEW的區別是,PROPAGATION_REQUIRES_NEW另起一個事務,將會與他的父事務相互獨立,
而Nested的事務和他的父事務是相依的,他的提交是要等和他的父事務一塊提交的。也就是說,如果父事務最后回滾,他也要回滾的。
二、Isolation Level(事務隔離等級):
1、Serializable:最嚴格的級別,事務串行執行,資源消耗最大;
2、REPEATABLE READ:保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的數據。避免了“臟讀取”和“不可重復讀取”的情況,但是帶來了更多的性能損失。
3、READ COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另一個并行事務已修改但未提交的數據,避免了“臟讀取”。該級別適用于大多數系統。
4、Read Uncommitted:保證了讀取過程中不會讀取到非法數據。
隔離級別在于處理多事務的并發問題。
我們知道并行可以提高數據庫的吞吐量和效率,但是并不是所有的并發事務都可以并發運行,這需要查看數據庫教材的可串行化條件判斷了。
這里就不闡述。
我們首先說并發中可能發生的3中不討人喜歡的事情
1: Dirty reads--讀臟數據。也就是說,比如事務A的未提交(還依然緩存)的數據被事務B讀走,如果事務A失敗回滾,會導致事務B所讀取的的數據是錯誤的。
2: non-repeatable reads--數據不可重復讀。比如事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,然后事務B就把total的數據改成200,事務A再讀一次,結果就發現,total竟然就變成200了,造成事務A數據混亂。
3: phantom reads--幻象讀數據,這個和non-repeatable reads相似,也是同一個事務中多次讀不一致的問題。但是non-repeatable reads的不一致是因為他所要取的數據集被改變了(比如total的數據),但是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。比如Select account.id where account.name="ppgogo*",第一次讀去了6個符合條件的id,第二次讀取的時候,由于事務b把一個帳號的名字由"dd"改成"ppgogo1",結果取出來了7個數據。
Dirty reads non-repeatable reads phantom reads
Serializable 不會 不會 不會
REPEATABLE READ 不會 不會 會
READ COMMITTED 不會 會 會
Read Uncommitted 會 會 會
三、readOnly
事務屬性中的readOnly標志表示對應的事務應該被最優化為只讀事務。這是一個最優化提示。在一些情況下,一些事務策略能夠起到顯著的最優化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)時避免dirty checking(試圖“刷新”)。
四、Timeout
在事務屬性中還有定義“timeout”值的選項,指定事務超時為幾秒。在JTA中,這將被簡單地傳遞到J2EE服務器的事務協調程序,并據此得到相應的解釋。
web安全
前后端約定好加密方式,通過修改request 的parameter
繼承HttpServletRequestWrapper,其實就是上面那種方法多了一層繼承,將你的重復工作交予了它,你也可以這樣做,
全名為:javax.servlet.http.HttpServletRequestWrapper,看來也是一個擴展的通用接口,也就是會對request做一次包裝,OK;跟著進去發現它可以處理類似request一樣的差不多的內容,在這個基礎上做了一次包裝,你可以認為他就是對你自己new的那個,多了一層簡單擴展實現,而你再這個基礎上,可以繼續繼承和重寫。
OK,此時你要重寫如何重寫呢,比如我們要重寫一個getParameter方法和getParameterValues方法,其余的方法保持和原來一致,我們在子類中,自己定義一個Map用來放參數,結合request本身的參數,加上外部其他自定義的參數,做成一個新的參數表。
我們用到的是反射機制 雖然網上有說我們是在裸奔,但是還沒來得及改。。
java并發包
1、ConcurrentHashMap
ConcurrentHashMap其實就是線程安全版本的hashMap。前面我們知道HashMap是以鏈表的形式存放hash沖突的數據,以數組形式存放HashEntry等hash出來不一致的數據。為了保證容器的數據一致性,需要加鎖。HashMap的實現方式是,只有put和remove的時候會引發數據的不一致,那為了保證數據的一致性,我在put和remove的時候進行加鎖操作。但是隨之而來的是性能問題,因為key-value形式的數據,讀寫頻繁是很正常的,也就意味著我有大量數據做讀寫操作時會引發長時間的等待。為了解決這個問題,Java并發包問我們提供了新的思路。在每一個HashEntry上加一把鎖,對于hash沖突的數據,因為采用鏈表存儲,公用一把鎖。這樣我才在做不同hash數值的數據時,則是在不同的鎖環境下執行,基本上是互不干擾的。在最好情況下,可以保證16個線程同時進行無阻塞的操作(HashMap的默認HashEntry是16,亦即默認的數組大小是16)。
那ConcurrentHashMap是如何保證數據操作的一致性呢?對于數據元素的大小,ConcurrentHashMap將對應數組(HashEntry的長度)的變量為voliate類型的,也就是任何HashEntry發生變更,所有的地方都會知道數據的大小。對于元素,如何保證我取出的元素的next不發生變更呢?(HashEntry中的數據采用鏈表存儲,當讀取數據的時候可能又發生了變更),這一點,ConcurrentHashMap采取了最簡單的做法,hash值、key和next取出后都為final類型的,其next等數據永遠不會發生變更。
2、CopyOnWriteArrayList
同樣的,CopyOnWriteArrayList是線程安全版本的ArrayList。和ArrayList不同的是,CopyOnWriteArrayList默認是創建了一個大小為0的容器。通過ReentrantLock來保證線程安全。CopyOnWriteArrayList其實每次增加的時候,需要新創建一個比原來容量+1大小的數組,然后拷貝原來的元素到新的數組中,同時將新插入的元素放在最末端。然后切換引用。
針對CopyOnWriteArrayList,因為每次做插入和刪除操作,都需要重新開辟空間和復制數組元素,因此對于插入和刪除元素,CopyOnWriteArrayList的性能遠遠不如ArrayList,但是每次讀取的時候,CopyOnWriteArrayList在不加鎖的情況下直接鎖定數據,會快很多(但是可能會引發臟讀),對于迭代,CopyOnWriteArrayList會生成一個快照數組,因此當迭代過程中出現變化,快照數據沒有變更,因此讀到的數據也是不會變化的。在讀多寫少的環境下,CopyOnWriteArrayList的性能還是不錯的。
3、CopyOnWriteArraySet
CopyOnWriteArraySet是基于CopyOnWriteArrayList實現的。但是CopyOnWriteArraySet鑒于不能插入重復數據,因此每次add的時候都要遍歷數據,性能略低于CopyOnWriteArrayList。
4、ArrayBlockingQueue
ArrayBlockingQueue是基于數組實現的一個線程安全的隊列服務,其相關的功能前面我們已經用到過了,這里就不多提了。
5、Atomic類,如AtomicInteger、AtomicBoolean
線程問題
1. 進程和線程之間有什么不同?
一個進程是一個獨立(self contained)的運行環境,它可以被看作一個程序或者一個應用。
而線程是在進程中執行的一個任務。Java運行環境是一個包含了不同的類和程序的單 一進程。
線程可以被稱為輕量級進程。線程需要較少的資源來創建和駐留在進程中,并且可以共享進程中的資源。
2. 多線程編程的好處是什么?
在多線程程序中,多個線程被并發的執行以提高程序的效率,CPU不會因為某個線程需要等待資源而進入空閑狀態。
多個線程共享堆內存(heap memory),因此創建多個線程去執行一些任務會比創建多個進程更好。
舉個例子,Servlets比CGI更好,是因為Servlets支持多線程而 CGI不支持。
3. 用戶線程和守護線程有什么區別?
當我們在Java程序中創建一個線程,它就被稱為用戶線程。
一個守護線程是在后臺執行并且不會阻止JVM終止的線程。
當沒有用戶線程在運行的時候,JVM關閉程序并且退出。
一個守護線程創建的子線程依然是守護線程。
4. 我們如何創建一個線程?
有兩種創建線程的方法:
一是實現Runnable接口,然后將它傳遞給Thread的構造函數,創建一個Thread對象;
二是直接繼承Thread類。
5. 有哪些不同的線程生命周期?
當我們在Java程序中新建一個線程時,它的狀態是New。當我們調用線程的start()方法時,狀態被改變為Runnable。
線程調度器會為Runnable線程池中的線程分配CPU時間并且講它們的狀態改變為Running。
其他的線程狀態還有Waiting,Blocked 和Dead。
6. 可以直接調用Thread類的run()方法么?
當然可以,但是如果我們調用了Thread的run()方法,它的行為就會和普通的方法一樣,為了在新的線程中執行我們的代碼,必須使用Thread.start()方法。
7. 如何讓正在運行的線程暫停一段時間?
我們可以使用Thread類的Sleep()方法讓線程暫停一段時間。需要注意的是,這并不會讓線程終止,一旦從休眠中喚醒線程,線程的狀態將會被改變為Runnable,并且根據線程調度,它將得到執行。
8. 你對線程優先級的理解是什么?
每一個線程都是有優先級的,一般來說,高優先級的線程在運行時會具有優先權,
但這依賴于線程調度的實現,這個實現是和操作系統相關的(OS dependent)。
我們可以定義線程的優先級,但是這并不能保證高優先級的線程會在低優先級的線程前執行。
線程優先級是一個int變量(從 1-10),1代表最低優先級,10代表最高優先級。
9. 什么是線程調度器(Thread Scheduler)和時間分片(Time Slicing)?
線程調度器是一個操作系統服務,它負責為Runnable狀態的線程分配CPU時間。
一旦我們創建一個線程并啟動它,它的執行便依賴于線程調度器的實現。
時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。
分配CPU時間可以基于線程優先級或者線程等待的時間。
線程調度并不受到Java虛擬機控制,所以由應用程序來控制它是更好的選擇(也就是說不要讓你的程序依賴于線程的優先級)。
10. 在多線程中,什么是上下文切換(context-switching)?
上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。
上下文切換是多任務操作系統和多線程環境的基本特征。
11. 你如何確保main()方法所在的線程是Java程序最后結束的線程?
我們可以使用Thread類的joint()方法來確保所有程序創建的線程在main()方法退出前結束。
12.線程之間是如何通信的?
當線程間是可以共享資源時,線程間通信是協調它們的重要的手段。
Object類中wait() otify() otifyAll()方法可以用于線程間通信關于資源的鎖的狀態。
13.為什么線程通信的方法wait(), notify()和notifyAll()被定義在Object類里?
Java的每個對象中都有一個鎖(monitor,也可以成為監視器) 并且wait(),
notify()等方法用于等待對象的鎖或者通知其他線程對象的監視器可用。
在Java的線程中并沒有可供任何對象使用的鎖和同步器。
這就是為什么這些方法是Object類的一部分,這樣Java的每一個類都有用于線程間通信的基本方法
14. 為什么wait(), notify()和notifyAll()必須在同步方法或者同步塊中被調用?
當一個線程需要調用對象的wait()方法的時候,這個線程必須擁有該對象的鎖,
接著它就會釋放這個對象鎖并進入等待狀態直到其他線程調用這個對象 上的notify()方法。
同樣的,當一個線程需要調用對象的notify()方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可以得到這個對象 鎖。
由于所有的這些方法都需要線程持有對象的鎖,這樣就只能通過同步來實現,所以他們只能在同步方法或者同步塊中被調用。
15. 為什么Thread類的sleep()和yield()方法是靜態的?
Thread類的sleep()和yield()方法將在當前正在執行的線程上運行。‘
所以在其他處于等待狀態的線程上調用這些方法是沒有意義的。
這 就是為什么這些方法是靜態的。它們可以在當前正在執行的線程中工作,并避免程序員錯誤的認為可以在其他非運行線程調用這些方法。
16.如何確保線程安全?
在Java中可以有很多方法來保證線程安全——同步,使用原子類(atomic concurrent classes),實現并發鎖,使用volatile關鍵字,使用不變類和線程安全類。
17. volatile關鍵字在Java中有什么作用?
當我們使用volatile關鍵字去修飾變量的時候,所以線程都會直接讀取該變量并且不緩存它。
這就確保了線程讀取到的變量是同內存中是一致的。
18. 同步方法和同步塊,哪個是更好的選擇?
同步塊是更好的選擇,因為它不會鎖住整個對象(當然你也可以讓它鎖住整個對象)。
同步方法會鎖住整個對象,哪怕這個類中有多個不相關聯的同步塊,這通常會導致他們停止執行并需要等待獲得這個對象上的鎖。
19.如何創建守護線程?
使用Thread類的setDaemon(true)方法可以將線程設置為守護線程,
需要注意的是,需要在調用start()方法前調用這個方法,否則會拋出IllegalThreadStateException異常。
20. 什么是ThreadLocal
ThreadLocal用于創建線程的本地變量,我們知道一個對象的所有線程會共享它的全局變量,
所以這些變量不是線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量。
每個線程都會擁有他們自己的Thread變量,它們可以使用get()set()方法去獲取他們的默認值或者在線程內部改變他們的值。
小編推薦一個學JAVA的學習裙【 一三三,九三零,六九三】,無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!裙內有開發工具,很多貨和技術資料分享ThreadLocal實例通常是希望它們同線程狀態關聯起來是private static屬性。
21. 什么是Thread Group?為什么建議使用它?
ThreadGroup是一個類,它的目的是提供關于線程組的信息。
ThreadGroup API比較薄弱,它并沒有比Thread提供了更多的功能。
它有兩個主要的功能:一是獲取線程組中處于活躍狀態線程的列表;
二是設置為線程設置未捕獲異常 處理器(ncaught exception handler)。
但在Java 1.5中Thread類也添加了setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法,
所以ThreadGroup是已經過時的,不建議繼續使用。
22. 什么是Java線程轉儲(Thread Dump),如何得到它?
線程轉儲是一個JVM活動線程的列表,它對于分析系統瓶頸和死鎖非常有用。
有很多方法可以獲取線程轉儲——使用Profiler,Kill -3命令,jstack工具等等。我更喜歡jstack工具,因為它容易使用并且是JDK自帶的。由于它是一個基于終端的工具,所以我們可以編寫一些腳本 去定時的產生線程轉儲以待分析。讀這篇文檔可以了解更多關于產生線程轉儲的知識。
23. 什么是死鎖(Deadlock)?如何分析和避免死鎖?
死鎖是指兩個以上的線程永遠阻塞的情況,這種情況產生至少需要兩個以上的線程和兩個以上的資源。
分析死鎖,我們需要查看Java應用程序的線程轉儲。我們需要找出那些狀態為BLOCKED的線程和他們等待的資源。每個資源都有一個唯一的id,用這個id我們可以找出哪些線程已經擁有了它的對象鎖。
避免嵌套鎖,只在需要的地方使用鎖和避免無限期等待是避免死鎖的通常辦法,閱讀這篇文章去學習如何分析死鎖。
24. 什么是Java Timer類?如何創建一個有特定時間間隔的任務?
java.util.Timer是一個工具類,可以用于安排一個線程在未來的某個特定時間執行。Timer類可以用安排一次性任務或者周期任務。
java.util.TimerTask是一個實現了Runnable接口的抽象類,我們需要去繼承這個類來創建我們自己的定時任務并使用Timer去安排它的執行。
這里有關于java Timer的例子。
25. 什么是線程池?如何創建一個Java線程池?
一個線程池管理了一組工作線程,同時它還包括了一個用于放置等待執行的任務的隊列。
java.util.concurrent.Executors提供了一個 java.util.concurrent.Executor接口的實現用于創建線程池。線程池例子展現了如何創建和使用線程池,或者閱讀ScheduledThreadPoolExecutor例子,了解如何創建一個周期任務。
Java并發面試問題
1. 什么是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?
原子操作是指一個不受其他操作影響的操作任務單元。原子操作是在多線程環境下避免數據不一致必須的手段。
int++并不是一個原子操作,所以當一個線程讀取它的值并加1時,另外一個線程有可能會讀到之前的值,這就會引發錯誤。
為了解決這個問題,必須保證增加操作是原子的,在JDK1.5之前我們可以使用同步技術來做到這一點。到 JDK1.5,java.util.concurrent.atomic包提供了int和long類型的裝類,它們可以自動的保證對于他們的操作是原子的 并且不需要使用同步。可以閱讀這篇文章來了解Java的atomic類。
2. Java Concurrency API中的Lock接口(Lock interface)是什么?對比同步它有什么優勢?
Lock接口比同步方法和同步塊提供了更具擴展性的鎖操作。他們允許更靈活的結構,可以具有完全不同的性質,并且可以支持多個相關類的條件對象。
它的優勢有:
可以使鎖更公平
可以使線程在等待鎖的時候響應中斷
可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間
可以在不同的范圍,以不同的順序獲取和釋放鎖
閱讀更多關于鎖的例子
3. 什么是Executors框架?
Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一個根據一組執行策略調用,調度,執行和控制的異步任務的框架。
無限制的創建線程會引起應用程序內存溢出。所以創建一個線程池是個更好的的解決方案,因為可以限制線程的數量并且可以回收再利用這些線程。利用Executors框架可以非常方便的創建一個線程池,閱讀這篇文章可以了解如何使用Executor框架創建一個線程池。
4. 什么是阻塞隊列?如何使用阻塞隊列來實現生產者-消費者模型?
java.util.concurrent.BlockingQueue的特性是:當隊列是空的時,從隊列中獲取或刪除元素的操作將會被阻塞,或者當隊列是滿時,往隊列里添加元素的操作會被阻塞。
阻塞隊列不接受空值,當你嘗試向隊列中添加空值的時候,它會拋出NullPointerException。
阻塞隊列的實現都是線程安全的,所有的查詢方法都是原子的并且使用了內部鎖或者其他形式的并發控制。
BlockingQueue 接口是java collections框架的一部分,它主要用于實現生產者-消費者問題。
閱讀這篇文章了解如何使用阻塞隊列實現生產者-消費者問題。
5. 什么是Callable和Future
Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回一個對象或者拋出一個異常。
Callable接口使用泛型去定義它的返回類型。Executors類提供了一些有用的方法去在線程池中執行Callable內的任務。由于 Callable任務是并行的,我們必須等待它返回的結果。java.util.concurrent.Future對象為我們解決了這個問題。在線程池 提交Callable任務后返回了一個Future對象,使用它我們可以知道Callable任務的狀態和得到Callable返回的執行結果。 Future提供了get()方法讓我們可以等待Callable結束并獲取它的執行結果。
閱讀這篇文章了解更多關于Callable,Future的例子。
6. 什么是FutureTask
FutureTask是Future的一個基礎實現,我們可以將它同Executors使用處理異步任務。通常我們不需要使用FutureTask 類,單當我們打算重寫Future接口的一些方法并保持原來基礎的實現是,它就變得非常有用。我們可以僅僅繼承于它并重寫我們需要的方法。閱讀Java FutureTask例子,學習如何使用它。
7.什么是并發容器的實現?
Java集合類都是快速失敗的,這就意味著當集合被改變且一個線程在使用迭代器遍歷集合的時候,迭代器的next()方法將拋出ConcurrentModificationException異常。
并發容器支持并發的遍歷和并發的更新。
主要的類有ConcurrentHashMap, CopyOnWriteArrayList 和CopyOnWriteArraySet,閱讀這篇文章了解如何避免ConcurrentModificationException。
8. Executors類是什么?
Executors為Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable類提供了一些工具方法。
Executors可以用于方便的創建線程池。
冒泡排序的實現原理
冒泡排序(Bubble Sort),是一種計算機科學領域的較簡單的排序算法。
它重復地走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把他們交換過來。走訪數列的工作是重復地進行直到沒有再需要交換,也就是說該數列已經排序完成。
這個算法的名字由來是因為越大的元素會經由交換慢慢“浮”到數列的頂端,故名。
冒泡排序算法的原理如下:(從后往前)
比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。在這一點,最后的元素應該會是最大的數。
針對所有的元素重復以上的步驟,除了最后一個。
持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
二叉樹遍歷的三種方法
1、遞歸法
這是思路最簡單的方法,容易想到并且容易實現。遞歸的終止條件是當前節點是否為空。首先遞歸調用遍歷左子樹,然后訪問當前節點,最后遞歸調用右子樹。代碼如下:
[cpp] view plain copy
//recursive
class Solution1 {
public:
vector inorderTraversal(TreeNode* root) {
vector ret;
if(root==NULL)return ret;
inorderHelper(ret,root);
return ret;
}
private:
void inorderHelper(vector& ret,TreeNode* root)
{
if(root==NULL)return;
inorderHelper(ret,root->left);
ret.push_back(root->val);
inorderHelper(ret,root->right);
}
};
2、迭代法
在迭代方法中,從根節點開始找二叉樹的最左節點,將走過的節點保存在一個棧中,找到最左節點后訪問,對于每個節點來說,它都是以自己為根的子樹的根節點,訪問完之后就可以轉到右兒子上了。代碼如下:
[cpp] view plain copy
//iterate,using a stack
class Solution2 {
public:
vector inorderTraversal(TreeNode* root) {
vector ret;
if(root==NULL)return ret;
TreeNode *curr=root;
stack st;
while(!st.empty()||curr!=NULL)
{
while(curr!=NULL)
{
st.push(curr);
curr=curr->left;
}
curr=st.top();
st.pop();
ret.push_back(curr->val);
curr=curr->right;
}
return ret;
}
};
這種方法時間復雜度是O(n),空間復雜度也是O(n)。
3、Morris法
這種方法是Morris發明的,看完之后感覺精妙無比。這種方法不使用遞歸,不使用棧,O(1)的空間復雜度完成二叉樹的遍歷。這種方法的基本思路就是將所有右兒子為NULL的節點的右兒子指向后繼節點(對于右兒子不為空的節點,右兒子就是接下來要訪問的節點)。這樣,對于任意一個節點,當訪問完它后,它的右兒子已經指向了下一個該訪問的節點。對于最右節點,不需要進行這樣的操作。注意,這樣的操作是在遍歷的時候完成的,完成訪問節點后會把樹還原。整個循環的判斷條件為當前節點是否為空。例如上面的二叉樹,遍歷過程如下(根據當前節點c的位置):
(1)當前節點為10,因為左兒子非空,不能訪問,找到c的左子樹的最右節點p:
小編推薦一個學JAVA的學習裙【 一三三,九三零,六九三】,無論你是大牛還是小白,是想轉行還是想入行都可以來了解一起進步一起學習!裙內有開發工具,很多貨和技術資料分享
結果:[]
(2)找節點c的左子樹的最右節點有兩種終止條件,一種右兒子為空,一種右兒子指向當前節點。下面是右兒子為空的情況,這種情況先要構造,將節點p的右兒子指向后繼節點c,然后c下移:
結果:[]
(3)當前節點c的左兒子為空,進行訪問。訪問后將c指向右兒子(即后繼節點):
結果:[5]
(4)繼續尋找左子樹的最右節點,這次的終止條件是最右節點為當前節點。這說明當前節點的左子樹遍歷完畢,訪問當前節點后,還原二叉樹,將當前節點指向后繼節點:
結果:[5,10]
(5)重復上述過程,直到c指向整棵二叉樹的最右節點:
左兒子為空,進行訪問,c轉到右兒子。右兒子為空,不滿足判斷條件,循環結束,遍歷完成。結果如下:
[5,10,6,15,2]
這就是Morris方法,時間復雜度為O(n),空間復雜度是O(1)。代碼如下:
[cpp] view plain copy
//Morris traversal,without a stack
class Solution3 {
public:
vector inorderTraversal(TreeNode* root) {
vector ret;
if(root==NULL)return ret;
TreeNode *curr=root;
TreeNode *pre;
while(curr)
{
if(curr->left==NULL)
{
ret.push_back(curr->val);
curr=curr->right;
}
else
{
pre=curr->left;
while(pre->right&&pre->right!=curr)
pre=pre->right;
if(pre->right==NULL)
{
pre->right=curr;
curr=curr->left;
}
else
{
ret.push_back(curr->val);
pre->right=NULL;
curr=curr->right;
}
}
}
return ret;
}
};
TCP三次握手
第一次握手:建立連接時,客戶端發送syn包(syn=j)到服務器,并進入SYN_SENT狀態,等待服務器確認;SYN:同步序列編號(Synchronize Sequence Numbers)。
第二次
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手。
java數據結構
數據結構分類:線性結構和非線性結構
問題一:
什么是線性和非線性;
我個人的理解是:數據結構中線性結構指的是數據元素之間存在著“一對一”的線性關系的數據結構;
線性結構包括:數組,鏈表,隊列,棧;
非線性結構包括:樹,圖,表;
詳解:
一.線性結構
1.數組
特點:我們都知道數組中的元素在內存中連續存儲的,可以根據是下標快速訪問元素,因此,查詢速度很快,然而插入和刪除時,需要對元素移動空間,比較慢。
數組使用場景:頻繁查詢,很少增加和刪除的情況。
2.鏈表
特點:元素可以不連續內存中,是以索引將數據聯系起來的,當查詢元素的時候需要從頭開始查詢,所以效率比較低,然而添加和刪除的只需要修改索引就可以了
使用場景:少查詢,需要頻繁的插入或刪除情況
3.隊列
特點:先進先出,
使用場景:多線程阻塞隊列管理非常有用
4.棧
特點:先進后出,就像一個箱子,
使用場景:實現遞歸以及表示式
5.數組與鏈表的區別
數組連續,鏈表不連續(從數據存儲形式來說)
數組內存靜態分配,鏈表動態分配
數組查詢復雜度0(1),鏈表查詢復雜度O(n)
數組添加或刪除,復雜度o(n),鏈表添加刪除,復雜度O(1)
數組從棧中分配內存。鏈表從堆中分配內存。
Java內存溢出問題的定位過程
對于java.lang.OutOfMemoryError: PermGen space 這種情況更多的是靠程序猿的經驗來解決:
PermGen space的全稱是Permanent Generation space,是指內存的永久保存區域, 這塊內存主要是被JVM存放Class和Meta信息的,Class在被Load時就會被放到PermGen space中, 它和存放類實例(Instance)的Heap區域不同,GC(Garbage Collection)不會在主程序運行期對 PermGen space進行清理,所以如果你的應用中有很多CLASS的話,就很可能出現PermGen space錯誤。
通過上面的描述就可以得出:如果要加載的class與jar文件大小超過-XX:MaxPermSize就有可能會產生java.lang.OutOfMemoryError: PermGen space 。
換句話說-XX:MaxPermSize的大小要超過class與jar的大小。通常-XX:MaxPermSize為-Xmx的1/8。
對于java.lang.OutOfMemoryError: Java heap space 可能定位的過程就需要折騰一翻了:
雖然各種java虛擬機的實現機制不一,但是heap space內存溢出產生的原因相同:那就是堆內存不夠虛擬機分配了。
我對java虛擬機的實現不感興趣,對各種虛擬機的內存分配機制與gc的交互關系也不了解。但是我大致認為內存分配機制與gc是有聯系的,也就是說內存不夠分配時gc肯定也釋放不了堆內存。從這一點出發,我們就需要找為什么gc釋放不了堆內存。通常來說釋放不了是因為內存還在使用。對于java對象產生的堆內存占用,只要其不再被繼續引用gc是能夠順利回收的(對于通過本地方法調用,即JNI調用產生內存泄露的情況暫不考慮)。
問題的關鍵就找到了,當產生heap space內存溢出時,堆內存中對象數量過多的就可能是問題的根源了。例外的情況是,程序確實需要那么多內存,這時就要考慮增大堆內存。
java sleep和wait的區別
首先,要記住這個差別,“sleep是Thread類的方法,wait是Object類中定義的方法”。盡管這兩個方法都會影響線程的執行行為,但是本質上是有區別的。
Thread.sleep不會導致鎖行為的改變,如果當前線程是擁有鎖的,那么Thread.sleep不會讓線程釋放鎖。如果能夠幫助你記憶的話,可以簡單認為和鎖相關的方法都定義在Object類中,因此調用Thread.sleep是不會影響鎖的相關行為。
Thread.sleep和Object.wait都會暫停當前的線程,對于CPU資源來說,不管是哪種方式暫停的線程,都表示它暫時不再需要CPU的執行時間。OS會將執行時間分配給其它線程。區別是,調用wait后,需要別的線程執行notify/notifyAll才能夠重新獲得CPU執行時間。
線程的狀態參考 Thread.State的定義。新創建的但是沒有執行(還沒有調用start())的線程處于“就緒”,或者說Thread.State.NEW狀態。
Thread.State.BLOCKED(阻塞)表示線程正在獲取鎖時,因為鎖不能獲取到而被迫暫停執行下面的指令,一直等到這個鎖被別的線程釋放。BLOCKED狀態下線程,OS調度機制需要決定下一個能夠獲取鎖的線程是哪個,這種情況下,就是產生鎖的爭用,無論如何這都是很耗時的操作。
字符串哈西相等,equals相等嗎反過來呢
在java中,equals和hashcode是有設計要求的,equals相等,則hashcode一定相等,反之則不然。
為何會有這樣的要求?
在集合中,比如HashSet中,要求放入的對象不能重復,怎么判定呢?
首先會調用hashcode,如果hashcode相等,則繼續調用equals,也相等,則認為重復。
如果重寫equals后,如果不重寫hashcode,則hashcode就是繼承自Object的,返回內存編碼,這時候可能出現equals相等,而hashcode不等,你的對象使用集合時,就會等不到正確的結果
Java中的幾種設計模式
如果從事JAVA相關的開發,都不可避免的要用到抽象和封裝,這是JAVA的一個特點,同時也是每個開發者必須掌握的,JAVA是這樣,Android更是如此。而設計模式就是告訴我們應該如何寫出高效且更具應用性和拓展性的代碼,最近也是學習了幾類比較常用的設計模式,下面一一列舉出來,雖然說的不細,但是應該知道的我會做個總結。
#####單例設計模式#####
單例設計模式的一般定義:一個類中只允許有一個實例。
實現思路:讓類的構造方法私有化,同時提供一個靜態方法去實例化這個類。
代碼 :
static class SingleTon {
private static SingleTon instance;
private SingleTon(){};
public static SingleTon newInstance() {
if(instance==null) {
synchronized (SingleTon.class) {
if(instance==null) {
ins和tance=new SingleTon();
}
}
}
return instance;
}
這是一個較為標準的單例模式,為了安全我給他加了鎖,然而這樣寫并不是最好的寫法。單例模式有兩種寫法,懶漢寫法和餓漢寫法。
懶漢式:在靜態方法中初始化。時間換空間。(不推薦,時間很重要)
餓漢式:在聲明對象就初始化。空間換時間。(推薦,空間不是問題)
所以,在實際開發中用的更多的是餓漢寫法,可以對這個類加鎖,在變量聲明的時候就初始化。具體如何實現這里我就不介紹了,可以自己去實現。
#####簡單工廠設計模式#####
簡單工廠設計模式的一般定義:簡單工廠又叫靜態工廠,由一個工廠對象決定創建哪一個產品對象。
實現思路:寫一個類,讓他制造出我們想要的對象。
代碼:
public class 學生工廠 {
public static 學生 getStudent(int type) {
switch (type) {
case 學生類型.學神:
return new 學神();
case 學生類型.學霸:
return new 學霸();
case 學生類型.學弱:
return new 學弱();
case 學生類型.學渣:
return new 學渣();
default:
break;
}
return null;
}
}
通過這個工廠,我們可以很方便的生產出我們想要的對象。這里我用偽代碼表示,為了更加形象,實際中這樣的寫法是絕對不允許的!
#####適配器設計模式#####
適配器模式的一般定義:某類繼承這個適配器,從而實現我們需要實現的方法。
實現思路:通過寫一個適配器類,里面寫了所有的抽象方法,但是這些方法是空的,并且適配器類要定義成抽象的,如果適配器類可以自己實現就沒有意義了。適配器的作用,繼承適配器,重新需要重新的方法就可以了,簡化操作。
優點
* 客戶端不需要在負責對象的創建,從而明確了各個類的職責,如果有新的對象增加,只需要增加一個具體的類和具體的工廠類即可,不影響已有的代碼,后期維護容易,增強了系統的擴展性。
缺點
* 需要額外的編寫代碼,增加了工作量
代碼:
public class Adapter {
public static void main(String[] args) {
阿樂 a=new 阿樂();
a.啪啪啪();
}
interface 談戀愛 {
void 吃飯();
void 看電影();
void 逛街();
void 啪啪啪();
}
abstract class 戀愛中的妹子 implements 談戀愛{
@Override
public void 吃飯() {
// TODO Auto-generated method stub
}
@Override
public void 看電影() {
// TODO Auto-generated method stub
}
@Override
public void 逛街() {
// TODO Auto-generated method stub
}
@Override
public void 啪啪啪() {
// TODO Auto-generated method stub
}
}
class 阿樂 extends 戀愛中的妹子{
public void 啪啪啪() {
// TODO Auto-generated method stub
System.out.println("阿樂:亞美爹,好害羞");
}
}
}
這里我寫了一個接口,接口中定義了幾個方法,當某類實現這個接口的時候就需要實現這些方法。用時適配器不允許自己實現這些方法,需要交給他的子類去繼承,讓子類選擇需要實現什么代碼,Android中的Adapter就是用了適配器模式。
#####模板設計模式#####
模板設計模式的一般定義:定義一個算法骨架將具體實現交給子類去實現。
實現方法:在類中定義一個抽象方法,距離實現交由子類去實現
代碼:
public class A {
public final void run {
........
void howRun();
........
}
public abstract void howRun();
}
public class B extends A {
public void howRun () {
..........
}
}
優點
* 使用模版方法模式,在定義算法骨架的同時,可以很靈活的實現具體的算法,滿足用戶靈活多變的需求
缺點
* 如果算法骨架有修改的話,則需要修改抽象類
MySQL InnoDB事務隔離級別臟讀、可重復讀、幻讀
MySQL InnoDB事務的隔離級別有四級,默認是“可重復讀”(REPEATABLE READ)。
· 1).未提交讀(READUNCOMMITTED)。另一個事務修改了數據,但尚未提交,而本事務中的SELECT會讀到這些未被提交的數據(臟讀)( 隔離級別最低,并發性能高 )。
· 2).提交讀(READCOMMITTED)。本事務讀取到的是最新的數據(其他事務提交后的)。問題是,在同一個事務里,前后兩次相同的SELECT會讀到不同的結果(不重復讀)。會出現不可重復讀、幻讀問題(鎖定正在讀取的行)
· 3).可重復讀(REPEATABLEREAD)。在同一個事務里,SELECT的結果是事務開始時時間點的狀態,因此,同樣的SELECT操作讀到的結果會是一致的。但是,會有幻讀現象(稍后解釋)。會出幻讀(鎖定所讀取的所有行)。
· 4).串行化(SERIALIZABLE)。讀操作會隱式獲取共享鎖,可以保證不同事務間的互斥(鎖表)。
‘
四個級別逐漸增強,每個級別解決一個問題。
· 1).臟讀。另一個事務修改了數據,但尚未提交,而本事務中的SELECT會讀到這些未被提交的數據。
· 2).不重復讀。解決了臟讀后,會遇到,同一個事務執行過程中,另外一個事務提交了新數據,因此本事務先后兩次讀到的數據結果會不一致。
· 3).幻讀。解決了不重復讀,保證了同一個事務里,查詢的結果都是事務開始時的狀態(一致性)。但是,如果另一個事務同時提交了新數據,本事務再更新時,就會“驚奇的”發現了這些新數據,貌似之前讀到的數據是“鬼影”一樣的幻覺。
具體地:
1). 臟讀
首先區分臟頁和臟數據
臟頁是內存的緩沖池中已經修改的page,未及時flush到硬盤,但已經寫到redo log中。讀取和修改緩沖池的page很正常,可以提高效率,flush即可同步。臟數據是指事務對緩沖池中的行記錄record進行了修改,但是還沒提交!!!,如果這時讀取緩沖池中未提交的行數據就叫臟讀,違反了事務的隔離性。臟讀就是指當一個事務正在訪問數據,并且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。
2). 不可重復讀
是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改,第二個事務已經提交。那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果只有在作者全部完成編寫后編輯人員才可以讀取文檔,則可以避免該問題
3). 幻讀 :
是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺一樣。例如,一個編輯人員更改作者提交的文檔,但當生產部門將其更改內容合并到該文檔的主復本時,發現作者已將未編輯的新材料添加到該文檔中。如果在編輯人員和生產部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免該問題。
結論:MySQL InnoDB事務默認隔離級別是可重復讀并不保證避免幻讀,需要應用使用加鎖讀來保證。而這個加鎖度使用到的機制就是next-key locks。
MySQL存儲引擎MyISAM與InnoDB的主要區別對比
InnoDB與Myisam的六大區別
MyISAM、InnoDB
構 成上的區別:
每個MyISAM在磁盤上存儲成三個文件。第一個 文件的名字以表的名字開始,擴展名指出文件類型。
.frm文件存儲表定義。
數據文件的擴 展名為.MYD (MYData)。
索引文件的擴 展名是.MYI (MYIndex)。
基于磁盤的資源是InnoDB表空間數據文件和它的日志文件,InnoDB 表的 大小只受限于操作系統文件的大小,一般為 2GB
事務處理上方面:
MyISAM類型的表強調的是性能,其執行數 度比InnoDB類型更快,但是不提供事務支持
InnoDB提供事務支持事務,外部鍵等高級 數據庫功能
SELECT UPDATE,INSERT,Delete操 作
如果執行大量的SELECT,MyISAM是更好的選擇
1.如果你的數據執行大量的INSERT或UPDATE,出于性能方面的考慮,應該使用InnoDB表
2.DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的 刪除。
3.LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導入數據后再改成InnoDB表,但是對于使用的額外的InnoDB特性(例如外鍵)的表不適用
對AUTO_INCREMENT的 操作
每表一個AUTO_INCREMEN列的內部處理。
MyISAM為INSERT和UPDATE操 作自動更新這一列。這使得AUTO_INCREMENT列更快(至少10%)。在序列頂的值被刪除之后就不 能再利用。(當AUTO_INCREMENT列被定義為多列索引的最后一列, 可以出現重使用從序列頂部刪除的值的情況)。
AUTO_INCREMENT值可用ALTER TABLE或myisamch來重置
對于AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但 是在MyISAM表中,可以和其他字段一起建立聯 合索引
更好和更快的auto_increment處理
如果你為一個表指定AUTO_INCREMENT列,在數據詞典里的InnoDB表句柄包含一個名為自動增長計數 器的計數器,它被用在為該列賦新值。
自動增長計數 器僅被存儲在主內存中,而不是存在磁盤上
關于該計算器 的算法實現,請參考
AUTO_INCREMENT列 在InnoDB里 如何工作
表 的具體行數
select count(*) from table,MyISAM只要簡單的讀出保存好的行數,注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的
InnoDB 中不 保存表的具體行數,也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行
鎖
表鎖
提供行鎖(locking on row level),提供與 Oracle 類型一致的不加鎖讀取(non-locking read in
SELECTs),另外,InnoDB表的行鎖也不是絕對的,如果在執 行一個SQL語句時MySQL不能確定要掃描的范圍,InnoDB表同樣會鎖全表,例如update table set num=1 where name like “%aaa%”
6種負載均衡算法
1、輪詢法
將請求按順序輪流地分配到后端服務器上,它均衡地對待后端的每一臺服務器,而不關心服務器實際的連接數和當前的系統負載。
2、隨機法
通過系統的隨機算法,根據后端服務器的列表大小值來隨機選取其中的一臺服務器進行訪問。由概率統計理論可以得知,隨著客戶端調用服務端的次數增多,
其實際效果越來越接近于平均分配調用量到后端的每一臺服務器,也就是輪詢的結果。
3、源地址哈希法
源地址哈希的思想是根據獲取客戶端的IP地址,通過哈希函數計算得到的一個數值,用該數值對服務器列表的大小進行取模運算,得到的結果便是客服端要訪問服務器的序號。采用源地址哈希法進行負載均衡,同一IP地址的客戶端,當后端服務器列表不變時,它每次都會映射到同一臺后端服務器進行訪問。
4、加權輪詢法
不同的后端服務器可能機器的配置和當前系統的負載并不相同,因此它們的抗壓能力也不相同。給配置高、負載低的機器配置更高的權重,讓其處理更多的請;而配置低、負載高的機器,給其分配較低的權重,降低其系統負載,加權輪詢能很好地處理這一問題,并將請求順序且按照權重分配到后端。
5、加權隨機法
與加權輪詢法一樣,加權隨機法也根據后端機器的配置,系統的負載分配不同的權重。不同的是,它是按照權重隨機請求后端服務器,而非順序。
6、最小連接數法
最小連接數算法比較靈活和智能,由于后端服務器的配置不盡相同,對于請求的處理有快有慢,它是根據后端服務器當前的連接情況,動態地選取其中當前
積壓連接數最少的一臺服務器來處理當前的請求,盡可能地提高后端服務的利用效率,將負責合理地分流到每一臺服務器。