垃圾回收算法
1.標記-清除算法
標記-清除算法是最基本的算法,和他的名字一樣,分為兩個步驟,一個步驟是標記需要回收的對象。在標記完成后統一回收被標記的對象。這個算法兩個問題。一個是效率問題,標記和清除的效率不高。第二個問題是標記-清除之后會有大量不連續的碎片空間,如果我們需要更大的連續內存就必須GC。
2.復制算法
復制算法,不同於標記-清除,復制算法大多數用於新生代,它需要大小相等的兩塊內存,每次只使用一塊內存,當GC的時候會把這塊內存存活的對象復制到另外一塊內存上面,解決了時間效率和空間碎片問題。在新生代中會把他分為三個內存一個Eden 兩個Survivor默認是8比1,開始會使用一個Eden和一個Surivivor裝載內存,清除時會將這兩個保留的對象都保存另外在Survivor中,並且年齡加1(以后提升為老年代),如果超出了Survivor中的限制會用老年代的內存擔保。
3.標記-整理算法
主要特點是,解決了碎片問題。標記整理算法,標記過程和第一算法一樣,但是他處理的時候會讓存活的對象向一邊移動解決了空間碎片問題,用於老年代的處理。
4.分代收集算法
分代收集算法不是新思想,只是把上面的算法結合起來了。
AtomicInteger實現原理
AtomicInteger使用value來保存值,value是volatile的,保證了可見性。
對於get方法直接返回value,對於自增一或者添加值使用了CAS自旋鎖,使用了一個死循環,如果cas返回為true就可以退出循環。對於CAS 全稱是compare and swap比較和交換,CAS需要三個操作數,一個是變量內存地址,一個是excepted過期值,一個是現在要更新的值,我們操作的時候僅當V符合舊預期的值的時候才能更新我們新的。對於它的自增的操作,首先是卸載一個for循環里面然后獲得當前的值,給這個值+1,然后進行cvs要是失敗會再次Get()最新值再次寫.
參考:http://ifeve.com/atomic-operation/
synchronized和lock的區別
主要有三個區別1、用法區別,性能區別,鎖機制的區別。
(1)對於用法區別:synchronized可以在方法中上使用也可以在特定代碼塊中使用,括號中表示需要鎖的對象,如果在方法上就是對該對象的鎖,如果是在類的方法上就是類的鎖,使用Lock必須自己用代碼顯示申明何時開啟鎖,何時關閉鎖。synchronized是jvm的底層實現,而Lock是由代碼執行。
(2)對於性能的區別:ReentrantLock 功能上要多於synchronized,多了鎖投票,定時鎖等。如果在小規模的競爭上synchronized效率比較高,如果在大規模的競爭上synchronize就比較低而Lock基本不變、
(3)鎖的機制也不同:synchronized獲得鎖和釋放鎖都是在塊中,都是自動釋放,不會引起死鎖,而Lock需要自己定位釋放,不然會引起死鎖。在Lock中也使用了tryLock方法用非阻塞的方式獲取鎖。
在lock中用一個鎖變量和隊列維護同步。
gc停頓原因,如何降低GC停頓
原因:gc停頓的意思就像是在整個分析期間凍結在某個時間點上,具體的原因是防止在分析的時候,對象引用關系還在不斷的變化,如果沒有GC停頓很有可能分析不准確。
如何降低:在Serial的老年代垃圾收集器中,會把所有線程的暫停,停下來收集哪些是死亡對象。在CMS和G1中都采取了初始標記、並發標記、短暫GC停頓重新標記,初始標記會直接記錄能GC ROOTS 關聯的對象,在並發標記的時候有一個線程來標記,這個時候對象的發生的變化都會記錄下來,在重新標記的時候會修正,這樣就會降低GC停頓時間
jvm如何調優,參數怎么調?如何利用工具分析jvm狀態?
合理的分配內存,分配棧和堆的內存,在堆中我們還可以詳細划分新生代和老年代的內存比例,在新生代中我們也可以划分Eden和Surivior的內存比例(調該比例大小),合理的划分內存區域大小,可以幫助我們jvm調優,我們采取合適的垃圾回收器,比如在新生代啟用serial垃圾回收器,在老年代采用cms並發標記,可以降低GC停頓,當然也可以嘗試去采用G1垃圾回收器
jvm中類加載過程
類加載到類被卸載過程包括7個階段
1.加載 通過類的全限定名把類文件的二進制流加入進來,通過這個字節流(這個二進制流也是我們代理類的方法),然后通過這個二進制流把靜態存儲結構轉化為運行時方法區的結構(不包括類變量,類變量在准備階段),在內 存中生成一個Class對象,作為方法區訪問的入口。
2.驗證 驗證是驗證Class文件的字節流包含的信息是否符合當前虛擬機的要求規范,防止惡意攻擊、
3.准備 在方法區為類變量分配內存和設置初始值,這個時候的初始值是數據的0值,不是我們定義的值,如果是常量的話准備階段就會設置為我們定義的值
4.解析 將符號引用(這里的符號引用指的是字面量的形式,只需要無歧義地定位到目標)替換為直接變量
5.初始化 類初始化 階段是我們加載過程的最后一步,執行類構造器,合並static語句,有static的順序決定。
6.使用
7.卸載
Spring中bean的加載機制,bean生成具體步驟
Ioc—Inversion of Control,即“控制反轉”,不是什么技術,而是一種設計思想。在Java開發中,Ioc意味着將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。
spring中Bean的加載機制其實就是IOC容器的初始化,比如我們這里定義了一個IOC容器,BeanFactroy的子類ClassXMLPathApplicationContext,在他的構造函數中我們會把xml路徑寫進去這個步驟就是定位,接下來就是BeanDefiniton的載入,在構造函數當中有一個refresh()的函數,這個就是載入BeanDefinition的接口,這個方法進去之后是一個同步代碼代碼塊,把之前的容器銷毀和關閉創建了一個BeanFatroy,就像對我們的容器重新啟動一樣,然后我們對BeanDefiniton載入和解析解析完畢之后會把beanDefinition和beanName放入BeanFactory的HashMap中維護。在這里Bean已經被創建完成,然后我們就像IOC容器索要Bean,如果是第一次索要會觸發依賴注入,會遞歸的調用gebBean實現依賴出入.
講下java鎖的原理
對於synchronized關鍵字,在jvm中在編譯的時候在同步塊的前后形成監視進入和監視退出兩個字節碼,這兩個字節碼都需要一個引用類型的參數來指定要鎖定和解鎖的對象,如果指定了的話就使用指定的對象,如果沒有指定看是類方法還是對象方法來決定,在執行監視進入的指令的時候,會判斷能否進入,進入成功之后會把鎖計數器加1,如果不成功就會繼續等待和其他的線程競爭,出鎖的時候會把鎖計數器減1變為0,也就是釋放了鎖。在這里要說明一點java的線程時映射到系統原生線程之上,如果要阻塞或者喚醒一個線程都需要操作系統幫忙,這就需要從用戶態轉換到核心態中,因此狀態轉換需要耗費很多的處理器時間。有可能比用戶的代碼執行時間還長。在jdk1.6之后對synchronized優化是非常的號的,比如鎖粗化,鎖自旋,鎖消除。輕量級鎖和偏向鎖。
而對於ReentrantLock是代碼上的實現
線程和進程的區別
進程是一段正在執行的程序,線程也叫作”輕量級進程“,他是程序執行的最小單元,一個進程可以有多個線程,各個線程之間共享程序的內存空間(比如說堆空間)及一些進程級的資源,進程和進程之間不能共享內存只能共享磁盤文件,線程也有4中狀態:就緒,運行,掛起,死亡。
(新版本)進程是程序執行時的一個實例,從內核的觀點看,進程的目的就是擔當分配系統資源的基本單位。
線程是進程的一個執行流,是cpu調度和分配的基本單位,它是比進程更小的能獨立運行的基本單位。一個進程由幾個線程組成,線程和同屬一個進程的其他的線程共享進程所擁有的全部資源。
進程-資源分配的最小單位。線程-程序執行的最小單位。
進程由獨立的空間地址,線程沒有單獨的地址空間 同一進程內的共享進程的地址空間,只有自己獨立的堆棧和局部變量。對於我們來說實現一個多線程的任務比實現一個多進程的任務好,
為什么分配線程比分配一般的對象更需要花費更大的代價?
首先他的資源非常“節儉”。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段,堆棧段和數據段,這是一種“昂貴”的多任務工作方式。而運行一個進程中的多個進程,他們彼此之間使用相同的地址空間,共享進程內的大部分數據,啟動一個進程的時間遠大於一個線程的時間。
(1)地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有自己獨立的地址空間;
(2)資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源
(3)線程是處理器調度的基本單位,但進程不是.
Spring AOP是怎么實現
首先簡單說一下Spring AOP幾個比較重要的名詞:
通知:定義在連接點做什么,為切面增強提供織入接口,意思就是說增強的內容以及增強的方式
切點:PointCut:決定通知應用於什么方法上或者類上。(點:方法或者類)
通知器:連接切點和通知結合起來,可以讓我們知道那個通知應用於哪個結點之上,這個結合為應用使用Ioc容器配置AoP應用
開始我們使用AOP的時候是用得java編寫的切面通知使用XML配置,后面我們摒棄了采用@AspectJ注解對其進行標注
然后AOP的實現原理有一個ProxyFactoryBean(代理工廠),這個ProxyFactoryBea是在Spring Ioc環境之中,創建AOP應用的最底層。在ProxyFactoryBean中我們會配置好通知器Advisor,在ProxyFactory需要為目標對象生成代理對象。ProxyFactory有一個getObject方法,在我們IOC容器中如果獲取這個bean會自動調用這個方法,首先第一步初始化通知器鏈,通知器鏈只會初始化一次,使用標志位判斷,遍歷通知器,把所有通知器加入攔截器鏈,接下來就是代理對象的生成,利用目標對象以及攔截器我們可以正確的生成代理對象,這里生成代理對象有兩種方法一種是jdk一種是cglib,在得到AopProxy代理對象之后,我們首先會根據配置來對攔截器是否與當前的調用方法想匹配,如果當前方法匹配就會發揮作用,他會遍歷Proxy代理對象中設置攔截器鏈的所有攔截器,攔截器調用完成之后才是目標對象的調用,這個時候會有一個注冊機制,在攔截器中運行的攔截器,會注冊,我們就不需要再判斷。Aop的源代碼中也大量使用了IOC容器,比如從IOC中找到通知器。
SpringMVC的主要流程
首先我們會在web.xml中配置DispathcerServlet,這個就是SpringMVC的入口,DispathcerServlet的父類FrameWorkServlet首先會初始化WebApplicationContext,DispacherServlet初始化了9個組件,初始完畢后我們開始進入,FramkeworkServlet中做了三件事一個是調用doService模板方法具體處理請求。將當前所有的請求都合並在一個方法里面和我們的httpServlet做法有點不同,在DispathcerServlet中有一個doService的方法,其中調用了doDispatch這也是最核心的首先根據request找到Handler,根據Handler找到了HderAdapter,用HandlerAdapter處理Handler其中包含一些參數的處理,處理完成后就行方法調用之后得到結果然后把View渲染給用戶或者把數據發給用戶。
詳細版本:
1.輸入一個網址,比如http請求,首先我們tomcat服務器,會對請求創建出我們request和response,然后就交給我們對應的servlet處理。
創建線程方式
實現runnable接口重寫run方法,繼承Thread,利用線程池來創建。
想讓所有線程都等到一個時刻同時執行有哪些方法
CountDownLatch:CountDownLatch首先我們在構造函數當中傳入了一個標志值,然后在需要阻塞的地方調用await(),直到其他線程把countDown減少完。這個是不可重用的。
CyclicBarrier:和他的名字一樣柵欄,我們對他的構造函數傳入一個柵欄值,在需要阻塞的地方調用await的時候我們就對其基礎值加一,直到等於柵欄值。調用CylicBarrier的reset方法可以對他進行重置。
Semaphore信號量:Semaphore可以同時控制訪問的線程個數,如果需要這個資源我們會acquire()以阻塞的方式去請求,如果沒有可用的信號量,就等待,release釋放信號量,這個機制有點類似於鎖。
在jdk1.7中提供了一個同步器Phaser,作用和countdownLatch,CyclicBarrier類似,但PHaser的使用方式更為靈活。使用register注冊方法遞增計數器,使用arriveAndDeregister()方法來遞減計數器,使用arriveAndAwaitAdvane()方法阻塞線程,當計數器歸0喚醒。
volatile的作用
參看博客:http://blog.csdn.net/libing13820393394/article/details/48582999
第一:volatile是Java虛擬機提供的最輕量級的同步機制,使變量對所有的線程可見,保證了可見性,但是並不能保證它的原子性。
第二個:禁止指令重排序優化。普通變量僅僅保證在該方法所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行一樣。從硬件的方面來說,並不是指令任意重拍,他只是把多條指令不安程序規定順序分發給電路處理單元,比如說2*3+5 2*3之間是有依賴,5就可以排到他們前面。volatile會幫助我們加入內存屏障防止重排序。volatile讀操作性能消耗與普通變量幾乎沒區別,寫操作會慢一些,因為它需要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。
注意:對於volatile修飾的變量,jvm只是保證從主內存加載到線程的工作的內存是最新的
談一談java內存模型
(1)java虛擬機規范試圖定義一種JAVA內存模型來屏蔽掉各種硬件和操作系統的內存訪問的差異。
(2)java內存模型的主要目標是定義程序中各個變量的訪問規則,這里的變量不包含局部變量和方法參數,而是指的是實例字段、靜態字段、和構成數組對象的元素。
(3)java內存模型規定了所有的變量都存儲在主內存中,而線程內的局部變量在自己的工作內存中,並且還有被該線程使用到的變量的主內存 的副本拷貝,線程對變量的操作(讀取、賦值)都在工作內存中進行,不能直接讀寫主內存的變量,不同的線程無法直接訪問對方工作內存的變量,線程鍵的變量值的傳遞需要通過主內存來完成,在內存模型中比較重要的就是工作線程和主內存的交互。
內存之間的交互:
java內存模型定義的操作:
Lock (鎖定)
Unlock(解鎖)
Read(讀取)
Load(載入)
Use(使用)
Assign(賦值)
Store(存儲)
Write(寫入)
變量從主內存到工作內存:按照順序執行read load操作
變量從工作內存到主內存:按照順序執行Store write操作
重排序:
包括:編譯器優化重排序、指令級並行重排序、內存系統重排序
什么時候使用LinkedList?
首先分寫LinkedList和ArrayList的不同,在經常插入和刪除的時候,在實現棧和隊列的時候,不適合隨機查找元素。
Object有哪些方法(九大方法),clone是深復制還是淺復制,finalize一般在什么時候使用:
wait,notify,notifyall,clone,getclass,toString,equals,hashcode,finalize。
1、Clone()方法
private保護方法,實現對象的淺復制,只有類實現了Clonable接口才可以調用該方法,否則拋出CloneNotSupportException。clone是淺復制,復制完成后其中的變量引用還是和以前的一樣,如果要實現深復制需要我們把所有的變量引用都遞歸復制一次,然后再賦值。(或者額使用序列化,也可以實現深拷貝)如果我們要自己實現clone()方法必須要實現克隆接口clonable。
2、Equals()方法
在object中與==是一樣的,子類一般需要重寫該方法
3、hashCode()方法
該方法用於哈希查找,重寫了equals方法一般都要重寫hashcode方法,這個方法在一些具有哈希功能的collection中使用
4、getClass()方法
final方法,獲得運行時的類型
5、Wait()方法
使得當前的線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。Wait方法會一直等待,直到獲得鎖(到了睡眠的時間間隔也會喚醒自己)或者被中斷掉。
調用該方法,當前的線程會進入到睡眠的狀態,直到調用該對象的notify方法、notifyAll方法、調用interrupt中斷該線程,時間間隔到了。
6、Notify()方法
喚醒在該對象上的等待的某個線程
7、notifyAll()方法
喚醒在該對象上的等待到所有的線程
8、toString()方法
把對象轉換成string類型進行輸出
9、finalize()方法
finalize在我們垃圾回收器回收這個對象的時候工作,可以做一些后續的工作,即進行一些必要的清理和清除的工作,比如說關閉流。當然我們也可以在這個里面對我們即將被回收的對象逃出回收。這里需要注意的是系統只會調用一次finalize()方法。但是一般我們不推薦使用這個方法,因為這個方法是為了對開始C和C++程序員的一種妥協,因為C中有析構函數,這個方法運行代價高,不確定大,我們還是會推薦使用try{}finally,他做的方法try{}finally都可以做。
如何管理線程(主要介紹各種線程池的實現)
使用線程池來管理線程
在Java中實現多種線程池
我們使用executors工廠產生我們的線程池,當線程池達到負載的時候會在我們線程池管理的Runnable阻塞隊列中等待,不會像線程那樣競爭CPU
第一種 newFixedThreadPool,和它的名字一樣這是一個固定線程池,我們可以設置基本大小也就是我們沒有任何任務執行的時候的大小,最大大小,只有在工作隊列滿了才能達到最大大小。
第二種 newCachedThreadPool這種線程池把大小設置為Integer.MAX_VALUE,基本大小設置為0,空閑超時設置1分鍾,這種線程池可以無限擴展,並且當需求降低時會自動收縮。
第三種 newSingleThreadPool這種線程池把基本大小設置為1,最大大小都設置為1,只允許同一時刻一個線程。
固定線程池和單線程池固定默認使用的是阻塞隊列無界的LinkedBlockingQueue,在這個阻塞隊列中可以無限增長。但是對於我們的newCachedThreadPool來說他的線程池是無限大的,不需要阻塞等待,我們這里使用的是SynchronousQueue來避免排隊,其實這個東西不是一個隊列,是直接在線程之間進行移交,當線程池的大小小於所需要的時候,要么創建一個要么拒絕一個。我們一般在使用的時候可以擴展,使用使用信號量來控制提交速率。
如何讓線程A等待線程B結束
1.使用join方法可以等待A線程結束,或者單線程池中 阻塞隊列的方式讓A先獲得單線程池的線程,然后B一直阻塞,知道A釋放自己的線程。
如何優化jvm參數
,首先設置堆的大小,一般設置xmx和xms大小相同,如果老年代容易溢出可以擴充老年代,也要適當的調整永久代大小,選擇自己合適的收集器,調整新生代對象年齡閥值等。
什么是守護線程
線程會分為兩種:
普通線程和守護線程。在JVM啟動時創建的所有線程中,除了主線程其他都是守護線程,比如說垃圾回收器就是守護線程,當普通線程全部退出的時候守護線程也會退出,我們自己也可以手動設置手動線程在線程啟動之前,但是我們應該盡可能少使用守護線程,因為我們很少有操作可以在不進行清理就可以安全地拋棄,比如說I/O操作。
TCP如何控制擁塞
擁塞控制就是防止過多的數據注入網絡中,這樣可以使網絡中的路由器或鏈路不致過載。
發送方維持一個叫做擁塞窗口cwnd(congestion window)的狀態變量。
為了防止cwnd增長過大引起網絡擁塞,還需設置一個慢開始門限ssthresh狀態變量。ssthresh的用法如下:
當cwnd<ssthresh時,使用慢開始算法。也就是乘法算法
當cwnd>ssthresh時,改用擁塞避免算法。也就是加法算法
當cwnd=ssthresh時,慢開始與擁塞避免算法任意。
當出現擁塞的時候就把心的門限值設為此時窗口大小的一般,窗口大小設置為1,再重新執行上面的步驟。
當收到連續三個重傳的時候這就需要快重傳和快恢復了,當收到連續三個重傳 這個時候發送方就要重傳自己的信息,然后門限減半但是這個時候並不是網絡阻塞,窗口只會減半執行擁塞避免算法。
ThreadLoacl?
我們使用ThreadLocal為每個使用該類型的變量提供了一個獨立的副本,具體的實現是在每個線程中保存了一個ThreadLocalMap,這個ThreadLoaclMap會在我們第一次使用ThreadLoal中的set方法創建出來,set方法就是保存在ThreadLocalMap中,該變量為key,值為value,get方法也從這個HashMap中找。
OSI網絡模型?
網卡在哪一層(物理層)
交換機在哪一層(鏈路層)
路由器在哪一層(網絡層)
傳輸TCP
會話 SQL
表示 IMG
html在應用層
HTTP1.0和Http1.1區別?
1.0默認是多次tcp連接多次請求,然后增加了keep alive功能,但是必須在request Header手動增加Connection:keepalive
1.1是一次tcp連接多次請求,新的persistence功能
POST和GET方法的區別?
長的說:
對於GET方式的請求,瀏覽器會把http header和data一並發送出去,服務器響應200(返回數據);
而對於POST,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。
也就是說,GET只需要汽車跑一趟就把貨送到了,而POST得跑兩趟,第一趟,先去和服務器打個招呼“嗨,我等下要送一批貨來,你們打開門迎接我”,然后再回頭把貨送過去。
因為POST需要兩步,時間上消耗的要多一點,看起來GET比POST更有效。因此Yahoo團隊有推薦用GET替換POST來優化網站性能。但這是一個坑!跳入需謹慎。為什么?
1. GET與POST都有自己的語義,不能隨便混用。
2. 據研究,在網絡環境好的情況下,發一次包的時間和發兩次包的時間差別基本可以無視。而在網絡環境差的情況下,兩次包的TCP在驗證數據包完整性上,有非常大的優點。
3. 並不是所有瀏覽器都會在POST中發送兩次包,Firefox就只發送一次。
1.get是從服務器上獲取數據,post是向服務器傳送數據。
2.get是通過URL來傳遞數據,POST是通過表單傳遞,因此get數據限制在1024k,而POST沒有限制
3.在java服務器端get是通過request.qureySting post通過request.getParameterNames和reque.getParameterValue
4.get是安全的,冪等的 POST即不安全又不冪等(多次操作和一次操作一樣)
在rest中設計的話,一般get用來查詢數據,POST用來添加數據,PUT用來更新數據,Delete用來刪除數據
filter執行順序?
多個filter的執行順序是web.xml中的配置順序
影響SQL執行效率的因素?
1.is null和is not null
2.通配符的like
3.order by
4.not
5.in和exists
GBK和UTF-8的區別
GBK包含全部中文字符; UTF-8則包含全世界所有國家需要用到的字符。
GBK的文字編碼是雙字節來表示的,即不論中、英文字符均使用雙字節來表示,只不過為區分中文,將其最高位都定成1。
至於UTF-8編碼則是用以解決國際上字符的一種多字節編碼,它對英文使用8位(即一個字節),中文使用24位(三個字節)來編碼。對於英文字符較多的論壇則用UTF-8節省空間。
UTF8是國際編碼,它的通用性比較好,外國人也可以瀏覽論壇 GBK是國家編碼,通用性比UTF8差,不過UTF8占用的數據庫比GBK大~
stringBuffer和StringBuilder組
1.三者在執行速度方面的比較:StringBuilder > StringBuffer > String
看servlet和Filter的實現原理
StringBuffer是線程安全的,St不是線程安全的,內部的實現是使用char數
什么是rest
一次網站訪問的全過程:
樓主提到TCP/IP分層的時候用的是網絡接口層,那么樓主的TCP/IP分層概念應該是:應用層、傳輸層、網絡層、網絡接口層(包含了七層模型中的數據鏈路層和物理層)。
我嘗試回答一下樓主的問題,希望大家繼續拍磚,如果訪問www.163.COM這個網站,那么他的主要過程應該是:
一、主機向DNS服務器發起域名解析請求,以得到相對應的IP地址
二、應用層應用HTTP協議發送數據
三、數據到達傳輸層封裝成數據段,主機使用1024以后的隨機源端口號,目標端口號為80
四、數據段到達網絡層封裝成數據包,加入主機源IP地址和目標IP地址
五、數據包到達網絡接口層首先封裝成數據幀,加入源MAC地址和目標MAC地址(注:此目標MAC地址為本地網關的MAC地址,源和目的MAC地址在數據轉發的過程中,會由路由器不斷的改變)。封裝后將數據轉換為物理層的數據流,通過互聯網發送至目標服務器。
什么時候拋出InvalidMonitorStateException異常?為什么?
調用 wait ()/notify ()/notifyAll ()中的任何一個方法時,如果當前線程沒有獲得該對象的鎖,
那么就會拋出 IllegalMonitorStateException 的異常
也就是說程序在沒有執行對象的任何同步塊或者同步方法時,
仍然嘗試調用 wait ()/notify ()/notifyAll ()時。由於該異常是 RuntimeExcpetion 的子類,
所以該異常不一定要捕獲(盡管你可以捕獲只要你願意
作為 RuntimeException,此類異常不會在 wait (),notify (),notifyAll ()的方法簽名提及。
Collections.synchronizedXX 方法的原理
返回了一個同步容器,在這個同步容器中的所有方法都有一個鎖為當前對象或者指定鎖的同步塊,用這種阻塞同步的方法可以讓我們容器同步
什么是Future
Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果。
1.cancel方法用來取消任務
2.isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。
3.isDone()表示是否完成
4.get()獲得執行結果,這個方法會一直阻塞
5.在時間范圍內獲取執行結果
FutureTask是Future的實現類
說出數據連接池的工作機制是什么?
J2EE服務器啟動時會建立一定數量的池連接,並一直維持不少於此數目的池連接。
調用:客戶端程序需要連接時,池驅動程序會返回一個未使用的池連接並將其表記為 忙。如果當前沒有空閑連接,池驅動程序就新建一定數量的連接,新建連接的數量有配置參數決定。
釋放:當使用的池連接調用完成后,池驅動程序將此連接表記為空閑, 其他調用就可以使用這個連接。
數據庫連接池在初始化時將創建一定數量的數據庫連接放到連接池中,這些數據庫連接的數量是由最小數據庫連接數來設定的。無論這些數據庫連接是否被 使用,連接池都將一直保證至少擁有這么多的連接數量。連接池的最大數據庫連接數量限定了這個連接池能占有的最大連接數,當應用程序向連接池請求的連接數超 過最大連接數量時,這些請求將被加入到等待隊列中。
數據庫連接池的最小連接數和最大連接數的設置要考慮到下列幾個因素:
1) 最小連接數是連接池一直保持的數據庫連接,所以如果應用程序對數據庫連接的使用量不大,將會有大量的數據庫連接資源被浪費;
2) 最大連接數是連接池能申請的最大連接數,如果數據庫連接請求超過此數,后面的數據庫連接請求將被加入到等待隊列中,這會影響之后的數據庫操作。
3) 如果最小連接數與最大連接數相差太大,那么最先的連接請求將會獲利,之后超過最小連接數量的連接請求等價於建立一個新的數據庫連接。不過,這些大於最小連接數的數據庫連接在使用完不會馬上被釋放,它將被放到連接池中等待重復使用或是空閑超時后被釋放。
存儲過程和函數的區別
存儲過程是用戶定義的一系列sql語句的集合,涉及特定表或其它對象的任務,用戶可以調用存儲過程,而函數通常是數據庫已定義的方法,它接收參數並返回某種類型的值並且不涉及特定用戶表。
事務是什么?
事務是作為一個邏輯單元執行的一系列操作。
游標的作用?如何知道游標已經到了最后?
游標用於定位結果集的行,通過判斷全局變量@@FETCH_STATUS可以判斷是否到了最后,通常此變量不等於0表示出錯或到了最后。
系統進程間通信的方式
管道( pipe ):管道是一種半雙工的通信方式,數據只能單向流動,而且只能在具有親緣關系的進程間使用。進程的親緣關系通常是指父子進程關系。
命名管道 (named pipe) : 命名管道也是半雙工的通信方式,但是它允許無親緣關系進程間的通信。
信號量( semophore ) : 信號量是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作為一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作為進程間以及同一進程內不同線程之間的同步手段。
消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩沖區大小受限等缺點。
信號 ( sinal ) : 信號是一種比較復雜的通信方式,用於通知接收進程某個事件已經發生。
共享內存( shared memory ) :共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是最快的 IPC 方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量,配合使用,來實現進程間的同步和通信。
套接字( socket ) : 套解口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同及其間的進程通信。
jvm調優:內存溢出和內存泄露:
溢出解決:
1.在代碼中減少不必要的實例構造
2.設置堆和永久代的大小 -xms堆最小 -xmx堆最大
內存泄露:
內存泄露不能通過配置解決代碼的問題。比如資源在使用完畢后沒有釋放,一些對象存在無效引用我們不能回收。
http和https的區別
http協議是無狀態的明文傳輸,Https而SSL+HTTP協議構建的可進行加密傳輸。https的服務器必須向CA申請一個證明服務器用途的證書,而客戶端通過該證書確認服務器,所以銀行都是https,所有的通訊都是在密鑰加密的情況下,而密鑰則是通過證書交換,所以第三方攔截的數據沒有密鑰也沒有用。
SSL用以保障在Internet上數據傳輸之安全,利用數據加密(Encryption)技術,可確保數據在網絡上之傳輸過程中不會被截取及竊聽。
虛擬機性能監控狀況
jps:顯示系統內所有進程的信息。
jstat:收集虛擬機各種運行狀態信息的命令行工具。 -gc,監視java堆 -class 就是監視類加載,還可以監視編譯狀況。
jinfo:java配置信息工具。
jmap:用於生成堆轉儲快照 有些選項只能在linux下才能看見。
jhat:配合jmap。
jstack:堆棧追蹤。
Servlet生命周期
1.加載:在Servlet容器啟動的時候會通過類加載器加載我們的Servlet的Class文件。
2.創建:在創建過程的時候如果沒有在web.xml文件中使用load-on-startup我們在第一次訪問我們的Servlet的時候會初始化實例,如果配置了這個並且大於1會在容器啟動的時候就創建。
3.初始化:init()初始化的方法只會被調用一次。在我們實例化被創建之后就會執行初始化。
4.處理客戶請求:service()在Servlet的service方法中會根據不同的http方法來調用。
5.卸載:destroy()當我們Servlet需要被卸載的時候就會調用我們的destory()方法,隨后垃圾回收器會回收。
Minor GC和FULL GC
當我們需要向新生代中分配內存時出現不足的情況:會出現Minor GC,在新生代中 都是朝生夕滅的對象,頻率比較高。Minor GC發生在新生代。
FULL GC指在老年代發生的GC,一般情況下出現了FULL GC都會伴隨着一次Minor GC。
為什么MinorGC 和FULL GC速度有差距呢?
在Minor GC中使用的copy算法,在FullGC中使用的是標記清除 或者 標記整理算法。
copy算法是用空間換時間 mark(標記)和copy(復制)是一個動作。
但是mark-sweap或mark-compact都是分為兩個階段,先標記再清除是兩步驟。
所以Minro GC速度會快於FullGC。
JVM調優問題
對於JVM調優的重點是垃圾回收和內存管理。
垃圾回收我們可以使用我們的cms垃圾回收器。
對於內存管理有:
永久代溢出、棧溢出、堆溢出的情況
永久代溢出:
針對永久代溢出在JVM默認情況只有64M的永久代大小,很多東西都需要我們永久代區內存,尤其是使用Spring等框架的時候會有cglib的動態字節碼生成class,都會存儲在我們的永久代。所以我們需要擴充永久代防止內存溢出。
堆溢出:
對於堆溢出我們也是比較常見的比如說我們使用容器的時候沒有釋放內存很有可能就會導致堆溢出,需要動態擴展。
棧溢出:
對於棧我們也可以設置提高。
單例模式
(1)惡漢式的單例模式
利用靜態static的方式進行實例化,在類被加載時就會創建實例。
/**
* 餓漢式實現單例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();//在類加載時實例單例對象
private Singleton() {//私有的構造器
}
public static Singleton getInstance() {//返回一個實例對象
return instance;
}
}
(2)懶漢式實現單例模式
在被第一次引用時才去創建對象。
/**
* 懶漢式實現單例模式
*/
public class Singleton {
private static Singleton instance;//創建私有的靜態變量
private Singleton() {//私有的構造函數
}
// synchronized方法,多線程情況下保證單例對象唯一
public static synchronized Singleton getInstance() {
//如果實例對象為空,就重新去實例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:這中方法的實現,效率不高,因為該方法定義為同步的方法。
(3)雙重鎖實現的單例模式double check
/**
* DCL實現單例模式
*/
public class Singleton {
private static valotile Singleton instance = null;//這里要加入valotile關鍵字,避免指令重排序, 可能先賦值但是沒有分配內存
private Singleton() {
}
public static Singleton getInstance() {
// 兩層判空,第一層是為了避免不必要的同步
// 第二層是為了在null的情況下創建實例
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析:資源的利用率較高,在需要的時候去初始化實例,而且可以保證線程的安全,該方法沒有去進行同步鎖,效率比較好。
(4)靜態內部類實現單例模式
/**
* 靜態內部類實現單例模式
*/
public class Singleton {
private Singleton() {
}
//返回實例的方法
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 靜態內部類
*/
private static class SingletonHolder {
//靜態私有的實例對象
private static Singleton instance = new Singleton();
}
}
分析:第一次加載類時不會去初始化instance,只有第一次調用getInstance()方法時,虛擬機才會加載內部類,初始化instance
可以保證線程的安全,單例對象的唯一,延遲了單例的初始化。
(5)枚舉單例
/**
* 枚舉實現單例模式
*/
public enum SingletonEnum {
INSTANCE;//直接定義一個實例對象
public void doSomething() {
System.out.println("do something");
}
}
分析:枚舉實例的創建是線程安全的,即使反序列化也不會生成新的實例,在任何的情況下都是單例的。
設計模式6大原則
1.單一職責。2.里氏替換 3.依賴導致4接口隔離5.迪米特法則6.開閉原則。
XML和JSON優缺點
XML的優點
A.格式統一,符合標准;
B.容易與其他系統進行遠程交互,數據共享比較方便。
C.可讀性高
XML的缺點
A.XML文件龐大,文件格式復雜,傳輸占帶寬;
B.服務器端和客戶端都需要花費大量代碼來解析XML,導致服務器端和客戶端代碼變得異常復雜且不易維護;
C.客戶端不同瀏覽器之間解析XML的方式不一致,需要重復編寫很多代碼;
D.服務器端和客戶端解析XML花費較多的資源和時間。
JSON的優缺點
JSON的優點:
A.數據格式比較簡單,易於讀寫,格式都是壓縮的,占用帶寬小;
B.易於解析,客戶端JavaScript可以簡單的通過eval()進行JSON數據的讀取;
C.支持多種語言,包括ActionScript, C, C#, ColdFusion, Java, JavaScript, Perl, PHP, Python, Ruby等服務器端語言,便於服務器端的解析;
D.在PHP世界,已經有PHP-JSON和JSON-PHP出現了,偏於PHP序列化后的程序直接調用,PHP服務器端的對象、數組等能直接生成JSON格式,便於客戶端的訪問提取;
E.因為JSON格式能直接為服務器端代碼使用,大大簡化了服務器端和客戶端的代碼開發量,且完成任務不變,並且易於維護。
JSON的缺點
A.沒有XML格式這么推廣的深入人心和喜用廣泛,沒有XML那么通用性;
B.JSON格式目前在Web Service中推廣還屬於初級階段。
C:可讀性低。
四種讀取XML文件讀取的辦法
1.DOM生成和解析XML文檔
為XML文檔的已解析版本定義了一組接口。解析器讀入整個文檔,然后構建一個駐留內存的樹結構。
優點:整個文檔樹在內存中,便於操作;支持刪除,修改,重新排列等。
缺點: 把整個文檔調入內存,存在很多無用的節點,浪費了時間和空間。
2.SAX為解決DOM
1、邊讀邊解析,應用於大型XML文檔
2、只支持讀
3、訪問效率低
4、順序訪問
3.DOM4J生成和解析XML文檔(解析工具) 性能最好 SUM的JAXM也大量采用DOM4J
HIBERNATE采用DOM4J
雖然DOM4J代表了完全獨立的開發結果,但最初,它是JDOM的一種智能分支。它合並了許多超出基本XML文檔表示的功能,包括集成的XPath支持、XML Schema支持以及用於大文檔或流化文檔的基於事件的處理。它還提供了構建文檔表示的選項,它通過DOM4J API和標准DOM接口具有並行訪問功能。從2000下半年開始,它就一直處於開發之中。
為支持所有這些功能,DOM4J使用接口和抽象基本類方法。DOM4J大量使用了API中的Collections類,但是在許多情況下,它還提供一些替代方法以允許更好的性能或更直接的編碼方法。直接好處是,雖然DOM4J付出了更復雜的API的代價,但是它提供了比JDOM大得多的靈活性。
在添加靈活性、XPath集成和對大文檔處理的目標時,DOM4J的目標與JDOM是一樣的:針對Java開發者的易用性和直觀操作。它還致力於成為比JDOM更完整的解決方案,實現在本質上處理所有Java/XML問題的目標。在完成該目標時,它比JDOM更少強調防止不正確的應用程序行為。
DOM4J是一個非常非常優秀的Java XML API,具有性能優異、功能強大和極端易用使用的特點,同時它也是一個開放源代碼的軟件。如今你可以看到越來越多的Java軟件都在使用DOM4J來讀寫XML,特別值得一提的是連Sun的JAXM也在用DOM4J.
4.JDOM
JDOM
優點:①是基於樹的處理XML的Java API,把樹加載在內存中
②沒有向下兼容的限制,因此比DOM簡單
③速度快,缺陷少
④具有SAX的JAVA規則
缺點:
①不能處理大於內存的文檔
②JDOM表示XML文檔邏輯模型。不能保證每個字節真正變換。
③針對實例文檔不提供DTD與模式的任何實際模型。
④不支持與DOM中相應遍歷包。
最適合於:JDOM具有樹的便利,也有SAX的JAVA規則。在需要平衡時使用
如何防止Sql注入
有兩種辦法
1.第一種消毒,通過正則匹配過濾請求數據中可能注入的SQL。
2.使用預編譯手段preparedStatemengt。
DB第一范式,第二范式,第三范式
第一范式:沒一列屬性不可再分,沒有多值屬性
第二范式:在符合第一范式的基礎上,存在主鍵
第三范式:在符合第二范式的基礎上,非關鍵字獨立於其他的非關鍵字,並且依賴關鍵字。不能存在傳遞依賴。
public、protected、private、默認權限
private:用於修飾類和方法,只允許該類訪問。
默認:只允許在同一個類和同一個包中進行訪問。
protected:用於修飾類和方法,允許該類和子類訪問以及同一個包中訪問。
public:用於修飾類和方法,允許該包下面和其他包的訪問,即在全局范圍都可以訪問。
數據庫事務
事務的特性:
原子性:事務是不可再分的;
一致性:事務的實行前后,數據庫的狀態保持一致;
隔離性:事務的並發訪問,事務之間的執行互不干擾;
持久性:事務結束后數據永久保存在數據庫中。
什么是臟讀?
臟讀就是一個事務讀取了該數據並且對該數據做出了修改,另一個事務也讀取了該修改后的數據但是前一個事務並沒有提交,這是臟數據。
讀取到保存在數據庫內存中的數據。
什么是不可重復讀?
一個事務:在同一個事務中讀取同一數據,得到的內容不同。一個事務讀取另外一個事務更新的數據,導致二次的查詢的數據不一致。
什么是幻讀?
幻讀是當事務不獨立發生的。比如一個事務刪除了所有數據,另一個事務又插入了一條,那么第一個事務的用戶會發現表中還沒有修改的數據行。一個事務讀取到另外一個事務提交的數據,導致查詢的結果不一致的問題。
數據庫的隔離級別:
Read uncommitted:未提交讀:三中都有可能發生
Read committed :已提交讀 避免臟讀
Repeated read:重復讀:避免臟讀 不可重復讀
Serializable:串行化讀 都可以避免
WebService到底是什么
一言以蔽之:WebService是一種跨編程語言和跨操作系統平台的遠程調用技術。
所謂跨編程語言和跨操作平台,就是說服務端程序采用java編寫,客戶端程序則可以采用其他編程語言編寫,反之亦然!跨操作系統平台則是指服務端程序和客戶端程序可以在不同的操作系統上運行。
所謂遠程調用,就是一台計算機a上的一個程序可以調用到另外一台計算機b上的一個對象的方法,譬如,銀聯提供給商場的pos刷卡系統,商場的POS機轉賬調用的轉賬方法的代碼其實是跑在銀行服務器上。再比如,amazon,天氣預報系統,淘寶網,校內網,百度等把自己的系統服務以webservice服務的形式暴露出來,讓第三方網站和程序可以調用這些服務功能,這樣擴展了自己系統的市場占有率,往大的概念上吹,就是所謂的SOA應用。
其實可以從多個角度來理解WebService,從表面上看,WebService就是一個應用程序向外界暴露出一個能通過Web進行調用的API,也就是說能用編程的方法通過Web來調用這個應用程序。我們把調用這個WebService的應用程序叫做客戶端,而把提供這個WebService的應用程序叫做服務端。從深層次看,WebService是建立可互操作的分布式應用程序的新平台,是一個平台,是一套標准。它定義了應用程序如何在Web上實現互操作性,你可以用任何你喜歡的語言,在任何你喜歡的平台上寫Web service ,只要我們可以通過Web service標准對這些服務進行查詢和訪問。
WebService平台需要一套協議來實現分布式應用程序的創建。任何平台都有它的數據表示方法和類型系統。要實現互操作性,WebService平台必須提供一套標准的類型系統,用於溝通不同平台、編程語言和組件模型中的不同類型系統。Web service平台必須提供一種標准來描述Web service,讓客戶可以得到足夠的信息來調用這個Web service。最后,我們還必須有一種方法來對這個Web service進行遠程調用,這種方法實際是一種遠程過程調用協議(RPC)。為了達到互操作性,這種RPC協議還必須與平台和編程語言無關。
java中鎖的優化
1.減少鎖持有的時間,可以減少其它線程的等待時間,不能讓一個線程一直控制着某個鎖不釋放,導致競爭加劇。
2.減少鎖的粒度,合適的鎖的代碼塊,可以減少競爭,控制鎖的范圍。
3.鎖分離,將鎖安功能划分,比如讀寫鎖,讀讀不互斥,讀寫互斥,寫寫互斥,保證了線程的安全,提高了性能。比如阻塞隊列中的take和put
4.鎖粗化,如果對同一個鎖不停的進行請求,同步和釋放,這個消耗是非常的大的,所以適當的時候可以粗化。
5.鎖消除,編譯器可以幫助我們優化比如一些代碼根本不需要鎖。
虛擬機內的鎖優化
1.偏向鎖:偏向當前已經占有鎖的線程,在無競爭的時候,之前獲得鎖的線程再次獲得鎖時,會判斷是否偏向鎖指向我,那么該線程將不用再次獲得鎖,直接進入同步塊。
2.輕量級鎖:偏向鎖失敗后,利用cas補救補救失敗就會升級為重量級鎖。
3.自旋鎖:會做空操作,並且不停地嘗試拿到這個鎖。
java中一億個數找前10000個最大的
先利用Hash法去重復,去除大量的之后 然后等量的分成100份 用小頂堆 來獲得10000個,再把所有的1萬個都合在一起就OK
java中線程的狀態
java中的線程的狀態有5種(新建、就緒、運行、阻塞、結束)
1.新建:創建后尚未啟動的線程處於這種狀態,新建出一個線程對象。
2.就緒狀態:當針對該對象掉用了start()方法,該線程等待獲取CPU的使用權
3.運行狀態:在就緒狀態下,獲得了CPU處於運行狀態。
4.阻塞:
等待阻塞:運行的線程執行wait方法,JVM會把該線程放入等待池
同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被其他的線程鎖占用,則jvm會把該線程放入鎖池中。
其他阻塞:運行的線程在執行sleep()方法或者join()方法時,或者發出IO請求,JVM會把線程置為阻塞狀態。
5.結束:
也就是我們的死亡,表明線程結束。
Maven的生命周期
maven有三套相互獨立的生命周期
1.clean生命周期
pre-clean,clean,post-clean
2.default生命周期 構建項目
1.validate:驗證工程是否正確,所有需要的資源是否可用
2.compile:編譯項目源代碼
3.test:使用合適的單元框架來測試已編譯的源代碼。
4.Package:把已編譯的代碼打包成可發布的格式,jar。
4)Package:把已編譯的代碼打包成可發布的格式,比如jar。
5)integration-test:如有需要,將包處理和發布到一個能夠進行集成測試的環境。
6)verify:運行所有檢查,驗證包是否有效且達到質量標准。
7)install:把包安裝到maven本地倉庫,可以被其他工程作為依賴來使用。
8)Deploy:在集成或者發布環境下執行,將最終版本的包拷貝到遠程的repository,使得其他的開發者或者工程可以共享。
3.
site生命周期:建立和發布項目站點,phase如下
1)pre-site:生成項目站點之前需要完成的工作
2)site:生成項目站點文檔
3)post-site:生成項目站點之后需要完成的工作
4)site-deploy:將項目站點發布到服務器
數據庫索引
什么是索引?
(1)索引是對記錄集多個字段進行排序的方法。
(2)也是一個數據結構,在一張表中為一個字段創建索引,將創建另外一個數據結構,包含字段的數值以及指向相關記錄的指針,就可以對該數據結構進行二分法排序,當需要查詢時就可以降低時間復雜度。
優勢:快速存取數據;保證數據記錄的唯一性;實現表和表之間的參照完整性;在使用order by group by子句進行數據的檢索時,利用索引可以減少排序和分組的時間。
弊端:建立索引表也是會需要額外的空間。
索引的工作原理:
在對表中記錄進行搜索時並不是對表中的數據進行全部的掃描遍歷,而是查看在索引中定義的有序列,一旦在索引中找到了要查詢的記錄,就會得到一個指針,它會指向相應的表中數據所保存的位置。
索引的類型:
(1)聚集索引:數據頁在物理上的有序的存儲,數據頁的物理順序是按照聚集索引的順序進行排列。在聚集索引中數據頁聚集索引的葉子節點,數據頁之間通過雙向的鏈表形式相連接,實際的數據存儲在葉節點中。
(2)非聚集索引:葉子節點不存放具體的數據頁信息,只存放索引的鍵值。非聚集索引的葉子節點包含着指向具體數據的指針,數據頁之間沒有連接,是相對獨立的。
(3)唯一索引:在整個表中僅僅會出現一次(主鍵約束/UNIQUE)
(4)非唯一索引:在提取數據時允許出現重復的值。
(5)單一索引和組合索引
哪些情況下索引會失效?
1.條件中有or但是前后沒有同時使用索引
2.多列索引,不是使用前面部分
3.like查詢是以%開頭
4.字符類型應該加單引號 防止轉換為int類型
數據庫查詢優化(Sql)
1、應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
2、對查詢進行優化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
3、應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num is null
可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢:
select id from t where num=0
4、盡量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10
union all
select id from t where num=20
5、下面的查詢也將導致全表掃描:(不能前置百分號)
select id from t where name like ‘�c%’
若要提高效率,可以考慮全文檢索。
6、in 和 not in 也要慎用,否則會導致全表掃描,如:
select id from t where num in(1,2,3)
對於連續的數值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
7、如果在 where 子句中使用參數,也會導致全表掃描。因為SQL只有在運行時才會解析局部變量,但優化程序不能將訪問計划的選擇推遲到運行時;它必須在編譯時進行選擇。然 而,如果在編譯時建立訪問計划,變量的值還是未知的,因而無法作為索引選擇的輸入項。如下面語句將進行全表掃描:
select id from t where num=@num
可以改為強制查詢使用索引:
select id from t with(index(索引名)) where num=@num
8、應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改為:
select id from t where num=100*2
9、應盡量避免在where子句中對字段進行函數操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where substring(name,1,3)=’abc’–name以abc開頭的id
select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′生成的id
應改為:
select id from t where name like ‘abc%’
select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′
10、不要在 where 子句中的“=”左邊進行函數、算術運算或其他表達式運算,否則系統將可能無法正確使用索引。
11、在使用索引字段作為條件時,如果該索引是復合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統使用該索引,否則該索引將不會被使 用,並且應盡可能的讓字段順序與索引順序相一致。
12、不要寫一些沒有意義的查詢,如需要生成一個空表結構:
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結果集,但是會消耗系統資源的,應改成這樣:
create table #t(…)
13、很多時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)
14、並不是所有索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重復時,SQL查詢可能不會去利用索引,如一表中有字段 sex,male、female幾乎各一半,那么即使在sex上建了索引也對查詢效率起不了作用。
15、索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有 必要。
16.應盡可能的避免更新 clustered 索引數據列,因為 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。若應用系統需要頻繁更新 clustered 索引數據列,那么需要考慮是否應將該索引建為 clustered 索引。
17、盡量使用數字型字段,若只含數值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因為引擎在處理查詢和連接時會 逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。
18、盡可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。
19、任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。
20、盡量使用表變量來代替臨時表。如果表變量包含大量數據,請注意索引非常有限(只有主鍵索引)。
21、避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。
22、臨時表並不是不可使用,適當地使用它們可以使某些例程更有效,例如,當需要重復引用大型表或常用表中的某個數據集時。但是,對於一次性事件,最好使 用導出表。
23、在新建臨時表時,如果一次性插入數據量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,為了緩和系統表的資源,應先create table,然后insert。
24、如果使用到了臨時表,在存儲過程的最后務必將所有的臨時表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統表的較長時間鎖定。
25、盡量避免使用游標,因為游標的效率較差,如果游標操作的數據超過1萬行,那么就應該考慮改寫。
26、使用基於游標的方法或臨時表方法之前,應先尋找基於集的解決方案來解決問題,基於集的方法通常更有效。
27、與臨時表一樣,游標並不是不可使用。對小型數據集使用 FAST_FORWARD 游標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用游標執行的速度快。如果開發時 間允許,基於游標的方法和基於集的方法都可以嘗試一下,看哪一種方法的效果更好。
28、在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。無需在執行存儲過程和觸發器的每個語句后向客戶端發送 DONE_IN_PROC 消息。
29、盡量避免向客戶端返回大數據量,若數據量過大,應該考慮相應需求是否合理。
30、盡量避免大事務操作,提高系統並發能力。查詢sql語句中哪些比較慢
1.慢查詢日志,一般設置查詢超過兩秒就記錄
2.processlist:顯示哪些線程正在運行.
3.explain關鍵字可以讓我們更好的優化
數據庫設計優化:
1.反范式設計,盡量是單表,可以高效利用索引
2.可以使用查詢緩存,Mysql也會自帶查詢緩存
3.盡量查詢通過搜索引擎查不通過我們
4.key,value數據庫
鎖的優化策略
1.讀寫分離,鎖分離
2.減少鎖持有時間,可以減少其他的鎖的持有時間
3.以正確的順序獲得和釋放鎖
4.適當的鎖的范圍擴大或者縮小,控制鎖的粒度
Spring Bean中作用域
singleton:單例模式,在整個Spring IoC容器中,使用singleton定義的Bean將只有一個實例。
prototype:原型模式,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產生一個新的Bean實例。
request:對於每次HTTP請求,使用request定義的Bean都將產生一個新實例,即每次HTTP請求將會產生不同的Bean實例。只有在Web應用中使用Spring時,該作用域才有效。
session:對於每次HTTP Session,使用session定義的Bean都將產生一個新實例。同樣只有在Web應用中使用Spring時,該作用域才有效。
Global session:每個全局的HTTP Session,使用session定義的Bean都將產生一個新實例。典型情況下,僅在使用portlet context的時候有效。同樣只有在Web應用中使用Spring時,該作用域才有效。
java中啟定時任務
1.利用sleep特性//休眠
2.time和timerTask//定時器
3.ScheduledExecutorService service.scheduleAtFixedRate(runnable, 10, 1, TimeUnit.SECONDS);
//任務調度服務
操作系統如何進行分頁調度
用戶程序的地址空間被划分成若干固定大小的區域,稱為“頁”,相應地,內存空間分成若干個物理塊,頁和塊的大小相等。可將用戶程序的任一頁放在內存的任一塊中,實現了離散分配。
linux內核的三種主要調度策略:
1,SCHED_OTHER 分時調度策略,
2,SCHED_FIFO實時調度策略,先到先服務
3,SCHED_RR實時調度策略,時間片輪轉
TCP和UDP相關
TCP通過什么方式提供可靠性:
1.超時重發,發出報文段要是沒有收到及時的確認,會重發。
2.數據包的校驗,也就是校驗首部數據和。
3.對失序的數據重新排序
4.進行流量控制,防止緩沖區溢出
5.快重傳和快恢復
6.TCP會將數據截斷為合理的長度
TCP和UDP的區別:
1.UDP是無連接的,TCP必須三次握手建立連接
2.UDP是面向報文,沒有擁塞控制,所以速度快,適合多媒體通信要求,比如及時聊天,支持一對一,一隊多。多對一,多對多。
3.TCP只能是一對一的可靠性傳輸
TCP的RPC,在協議棧的下層,能夠靈活的對字段進行定制,減少網絡傳輸字節數,降低網絡開銷,提高性能,實現更大的吞吐量和並發數。但是實現代價高,底層復雜,難以得到開源社區的支持,難以實現跨平台
集群調優
1.load
load是被定義為特定時間間隔內運行隊列中的平均線程數,uptime查看,一般load不大於3,我們認為負載是正常的,如果每個CPU的線程數大於5,表示負載就非常高了。
2.CPU利用率
查看cpu的消耗的情況命令:top | grep Cpu
查看磁盤的剩余空間命令:df -h
查看系統的內存的使用情況:free -m
心跳檢測方法
1.使用ping命令
對於full gc導致不響應,網絡攻擊這種 ping展示不明確
2.使用curl
訪問我們的自測地址
3.對不同的功能使用curl檢測,在response中加入狀態頭,表示正常
可以計算qps通過28原則
innodb存儲引擎通過預寫事務日志的方式保障事務的原子性,也就是在寫入數據之前,先將數據操作寫入日志,這種成為預寫日志
輕量級鎖認為在程序運行過程中,絕大部分的鎖,在整個同步周期內都是不存在競爭的,利用cas操作避免互斥開銷。
偏向鎖是jdk1.6中引入的一項優化,甚至可以省掉CAS操作,偏向鎖偏向第一個獲得他鎖的線程,如果在接下來執行過程中,這個鎖沒有被其他線程獲取,則持有偏向鎖的線程永遠不需要同步。
GC調優
查看GC日志,根據GC日志來優化
我們可以通過jps找到我們虛擬機正在運行的進程。參數 通過jps -help了解。
Jstat -gc 或者 -gcutil 查看堆使用情況-class 配合Jps得到進程
BTrace原理利用hotspot虛擬中的熱替換,把代碼動態的替換到java程序內,可在不需要重啟的時候就可以排除問題
JConsole
我們也可以使用JConsole來分析這是一個圖形化的,比較容易讓我們操作
使用VisualVM 進行遠程連接 使用JMX方式,也有修改tomcat的catalina就行了
內部類去訪問外部變量,為什么需要加final?
題目有點問題,並不是所有的外部變量才加final,我們的內部類訪問我們的成員變量就不需要加final,但是訪問局部變量就必須要加final,因為方法(main方法)結束我們棧幀也就銷毀了,但是我們內部類在堆中並沒有被銷毀,如果引用了成員變量,這時候被銷毀了肯定是不行的,所以我們就需要成員變量設置為final,讓其在方法(main方法)結束時不會被銷毀。
泛型
泛型的作用:在我們沒有泛型的時候,我們通過對Object的引用來對參數的任意化,任意化有個缺點就是要做顯示的強制類型轉換,強制轉換有一個不好的地方是運行的時候才會報錯,泛型的好處實在編譯的時候檢查類型安全,所以泛型的特點就是簡單安全,泛型的原理是類型擦除,java的泛型是偽泛型,在編譯期間,所有的泛型信息都會被擦除掉。在生成的java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器編譯的時候去掉。比如List<Object>信息在編譯后都是List。
nginx和apache的對比
1.nginx相對於apache來說
(1)輕量級占用的內存少;
(2)抗並發,nginx是異步非阻塞,apache是阻塞的,在高並發的情況下,nginx的性能優;
(3)高度模塊化的設計,編寫模塊相對簡單;
(4)社區活躍,各種高性能模塊有。
適合場景:apache適合於動態的請求,而負載均衡和靜態請求適合nginx,nginx采用的是epoll,並且有自己的一套sendfile系統,減少在內存中的賦值次數。
2.apache 相對於nginx 的優點:
(1)rewrite ,比nginx 的rewrite 強大 ;
(2)模塊超多,基本想到的都可以找到 ;
(3)少bug ,nginx 的bug 相對較多 ;
(4)超穩定 。
線程、進程的共享和獨立
共享的部分:
1.進程代碼段
2.進程的公有數據(利用這些共享的數據,線程很容易的實現相互之間的通訊)
3.進程打開的文件描述符、
4.信號的處理器、
5.進程的當前目錄
6.進程用戶ID與進程組ID
線程獨有的內容包括:
1.線程ID
2.寄存器組的值
3.線程的堆棧
4.錯誤返回碼
5.線程的信號屏蔽碼
最大的優勢就是線程極高的執行效率。因為子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,線程的性能優勢就越明顯。
第二大優勢就是不需要多線程的鎖機制,因為只有一個線程,也不存在同時寫變量沖突,在線程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。
簡單的說一下nginx的優點
1.作為高性能的web服務器:相比Apache,Nginx使用更少的資源,支持更多的並發連接,體現更高的效率,這點讓Nginx受到虛擬主機提供商的歡迎。一個Nginx實例能夠輕松支持高達5萬並發。
2.作為負載均衡服務器:Nginx即可以在內部直接支持PHP,也可以支持作為HTTP代理服務器對外進行服務,獨有的send files系統,減少文件復制次數。
3.作為郵件代理服務器:也比Apache好很多。
4.Nginx安裝簡單,配置文件非常簡潔。啟動容易,7*24小時幾乎不間斷,可以進行熱更新。
BIO
在BIO中讀和寫都是同步阻塞的,阻塞的時間取決於對方I/O線程的處理速度和網絡的傳輸速度。本質上來講,我們是無法保證生產環境的網絡狀況和對端的應用程序可以足夠快,應用程序是不應該依賴對方的處理速度,它的可靠性就非常差。BIO就算用線程池實現,要是所有可用線程都被阻塞到故障點中,后續的所有I/O消息都將在隊列中排隊。
NIO
(1)提供了高速,面向塊的I/O。
(2)在NIO中所有數據都是用緩沖區來處理的,也就是使用我們的jvm中的direct memory(直接內存)。緩沖區是一個數組,但是緩沖區不僅僅是一個數組,緩沖區提供了對數據的結構化訪問以及維護讀寫位置等信息。
(3)在NIO中channel(通道)也是特別重要的他是我們數據讀寫的通道,一般來說流比如inputStream和outputStream都是單向的,而通道是雙向的,是全雙工的。
(4)多路復用器Selector也是比較重要的,掌握它對於我們的NIO編程來說是比較重要的。多路復用器提供選擇已經就緒的任務的能力。Selector會不斷輪訓注冊在其上的Channel,如果某個Channel上面發生讀或者寫事件,這個Channel就處於就緒狀態,會被Selector輪詢出來,通過Selection Key可以獲取就緒Channel的集合,進行后續的I/o操作。我們只需要一個線程就可以管理我們多個客戶端。
垃圾收集器
1.Serial收集器
Serial收集器是JAVA虛擬機中最基本、歷史最悠久的收集器,在JDK 1.3.1之前是JAVA虛擬機新生代收集的唯一選擇。Serial收集器是一個單線程的收集器,但它的“單線程”的意義並不僅僅是說明它只會使用一個 CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。
Serial收集器到JDK1.7為止,它依然是JAVA虛擬機運行在Client模式下的默認新生代收集器。它也有着優於其他收集器的地方:簡單而高 效(與其他收集器的單線程比),對於限定單個CPU的環境來說,Serial收集器由於沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集 效率。在用戶的桌面應用場景中,分配給虛擬機管理的內存一般來說不會很大,收集幾十兆甚至一兩百兆的新生代(僅僅是新生代使用的內存,桌面應用基本上不會 再大了),停頓時間完全可以控制在幾十毫秒最多一百多毫秒以內,只要不是頻繁發生,這點停頓是可以接受的。所以,Serial收集器對於運行在 Client模式下的虛擬機來說是一個很好的選擇。
2. Parallel(並行)收集器
這是 JVM 的缺省收集器。就像它的名字,其最大的優點是使用多個線程來通過掃描並壓縮堆。串行收集器在GC時會停止其他所有工作線程(stop-the- world),CPU利用率是最高的,所以適用於要求高吞吐量(throughput)的應用,但停頓時間(pause time)會比較長,所以對web應用來說就不適合,因為這意味着用戶等待時間會加長。而並行收集器可以理解是多線程串行收集,在串行收集基礎上采用多線 程方式進行GC,很好的彌補了串行收集的不足,可以大幅縮短停頓時間(如下圖表示的停頓時長高度,並發比並行要短),因此對於空間不大的區域(如 young generation),采用並行收集器停頓時間很短,回收效率高,適合高頻率執行。
3.CMS收集器
CMS(Concurrent Mark Sweep)收集器是基於“標記-清除”算法實現的,它使用多線程的算法去掃描堆(標記)並對發現的未使用的對象進行回收(清除)。整個過程分為6個步驟,包括:
初始標記(CMS initial mark)
並發標記(CMS concurrent mark)
並發預清理(CMS-concurrent-preclean)
重新標記(CMS remark)
並發清除(CMS concurrent sweep)
並發重置(CMS-concurrent-reset)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,並發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一 般會比初始標記階段稍長一些,但遠比並發標記的時間短。其他動作都是並發的。
需要注意的是,CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS並發清理階段用戶線程還在運行着,伴隨程序的運行自然還會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之后,CMS無法在本次 收集中處理掉它們,只好留待下一次GC時再將其清理掉。這一部分垃圾就稱為“浮動垃圾”。也是由於在垃圾收集階段用戶線程還需要運行,即還需要預留足夠的 內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分空間提供並發收集時的程序運作使 用。在默認設置下,CMS收集器在老年代使用了68%的空間后就會被激活,這是一個偏保守的設置,如果在應用中老年代增長不是太快,可以適當調高參數 -XX:CMSInitiatingOccupancyFraction的值來提高觸發百分比,以便降低內存回收次數以獲取更好的性能。要是CMS運行期 間預留的內存無法滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這時候虛擬機將啟動后備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置 得太高將會很容易導致大量“Concurrent Mode Failure”失敗,性能反而降低。
try catch有return 在字節碼中 可以看到會出現Goto跳轉行數,跳轉到finally中的return
Tomcat總結構
最外層的Server提供接口訪問內部的Service服務。
Service服務的作用就是把Connector 在Connetor中監聽端口和Container連接起來,方便我們的操縱控制。
在tomcat中生命周期是統一的由Lifecycle來管理的
在lifecycle中有兩個方法比較重要start和stop,調用server的start會去調用自己下面所有service的start方法
Connector最重要的功能就是接受連接請求然后分配線程處理
在Container中 有4個級別Engine,Host,Context,warpper,這四個組件不是平行的,而是父子關系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。通常一個 Servlet class 對應一個 Wrapper,如果有多個 Servlet 就可以定義多個 Wrapper,如果有多個 Wrapper 就要定義一個更高的 Container 了,如 Context。在Host中有個value比較重要,類似於一個管道,和攔截器鏈差不多,我們在中間可以進行一些處理。
在Engine中只能添加子容器Host,不能添加父容器.Engine下可以配置多個虛擬主機Virtual Host,每個虛擬主機都有一個域名
當Engine獲得一個請求時,它把該請求匹配到某個Host上,然后把該請求交給該Host來處理Engine有一個默認虛擬主機,當請求無法匹配到任何一個Host上的時候,將交給該默認Host來處理
一個Host就類似於一個虛擬主機,用來管理應用。代表一個Virtual Host,虛擬主機,每個虛擬主機和某個網絡域名Domain Name相匹配
每個虛擬主機下都可以部署(deploy)一個或者多個Web App,每個Web App對應於一個Context,有一個Context path
當Host獲得一個請求時,將把該請求匹配到某個Context上,然后把該請求交給該Context來處理
匹配的方法是“最長匹配”,所以一個path==""的Context將成為該Host的默認Context
所有無法和其它Context的路徑名匹配的請求都將最終和該默認Context匹配。
Context 代表 Servlet 的 Context,它具備了 Servlet 運行的基本環境,理論上只要有 Context 就能運行 Servlet 了。簡單的 Tomcat 可以沒有 Engine 和 Host。
Context 最重要的功能就是管理它里面的 Servlet 實例,Servlet 實例在 Context 中是以 Wrapper 出現的,還有一點就是 Context 如何才能找到正確的 Servlet 來執行它呢?
一個Context對應於一個Web Application,一個Web Application由一個或者多個Servlet組成
Context在創建的時候將根據配置文件$CATALINA_HOME/conf/web.xml和$WEBAPP_HOME/WEB-INF/web.xml載入Servlet類
當Context獲得請求時,將在自己的映射表(mapping table)中尋找相匹配的Servlet類
如果找到,則執行該類,獲得請求的回應,並返回.
線程池參數
JDK1.5中引入了強大的concurrent包,其中最常用的莫過了線程池的實現。ThreadPoolExecutor(線程池執行器),它給我們帶來了極大的方便,但同時,對於該線程池不恰當的設置也可能使其效率並不能達到預期的效果,甚至僅相當於或低於單線程的效率。
ThreadPoolExecutor類可設置的參數主要有:
(1)corePoolSize 基本大小
核心線程數,核心線程會一直存活,即使沒有任務需要處理。當線程數小於核心線程數時,即使現有的線程空閑,線程池也會優先創建新線程來處理任務,而不是直接交給現有的線程處理。
核心線程在allowCoreThreadTimeout被設置為true時會超時退出,默認情況下不會退出。
(2)maxPoolSize 最大大小
當線程數大於或等於核心線程corePoolSize,且任務隊列已滿時,線程池會創建新的線程,直到線程數量達到maxPoolSize。如果線程數已等於maxPoolSize,且任務隊列已滿,則已超出線程池的處理能力,線程池會拒絕處理任務而拋出異常。
keepAliveTime 大於coolPoolSize的 會退出
當線程空閑時間達到keepAliveTime,該線程會退出,直到線程數量等於corePoolSize。如果allowCoreThreadTimeout設置為true,則所有線程均會退出直到線程數量為0。
allowCoreThreadTimeout 是否退出核心線程
是否允許核心線程空閑退出,默認值為false。
queueCapacity
任務隊列容量。從maxPoolSize的描述上可以看出,任務隊列的容量會影響到線程的變化,因此任務隊列的長度也需要恰當的設置。
線程池按以下行為執行任務
當線程數小於核心線程數時,創建線程。
當線程數大於等於核心線程數,且任務隊列未滿時,將任務放入任務隊列。
當線程數大於等於核心線程數,且任務隊列已滿
若線程數小於最大線程數,創建線程
若線程數等於最大線程數,拋出異常,拒絕任務
系統負載
參數的設置跟系統的負載有直接的關系,下面為系統負載的相關參數:
tasks,每秒需要處理的最大任務數量
tasktime,處理每個任務所需要的時間
responsetime,系統允許任務最大的響應時間,比如每個任務的響應時間不得超過2秒。
參數設置
corePoolSize:
每個任務需要tasktime秒處理,則每個線程每鈔可處理 1/tasktime個任務。系統每秒有tasks個任務需要處理,則需要的線程數為:tasks/(1/tasktime),即 tasks*tasktime個線程數。假設系統每秒任務數為100~1000,每個任務耗時0.1秒,則需要100*0.1至1000*0.1,即 10~100個線程。那么corePoolSize應該設置為大於10,具體數字最好根據8020原則,即80%情況下系統每秒任務數,若系統80%的情 況下第秒任務數小於200,最多時為1000,則corePoolSize可設置為20。
queueCapacity:
任務隊列的長度要根據核心線程數,以及系統對任務響應時間的要求有關。隊列長度可以設置為(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即隊列長度可設置為400。
隊列長度設置過大,會導致任務響應時間過長,切忌以下寫法:
LinkedBlockingQueue queue = new LinkedBlockingQueue();
這實際上是將隊列長度設置為Integer.MAX_VALUE,將會導致線程數量永遠為corePoolSize,再也不會增加,當任務數量陡增時,任務響應時間也將隨之陡增。
maxPoolSize:
當系統負載達到最大值時,核心線程數已無法按時處理完所有任務,這時 就需要增加線程。每秒200個任務需要20個線程,那么當每秒達到1000個任務時,則需要(1000-queueCapacity)* (20/200),即60個線程,可將maxPoolSize設置為60。
keepAliveTime:
線程數量只增加不減少也不行。當負載降低時,可減少線程數量,如果一個線程空閑時間達到keepAliveTiime,該線程就退出。默認情況下線程池最少會保持corePoolSize個線程。
allowCoreThreadTimeout:
默認情況下核心線程不會退出,可通過將該參數設置為true,讓核心線程也退出。
以上關於線程數量的計算並沒有考慮CPU的情況。若結合CPU的情況,比如,當線程數量達到50時,CPU達到100%,則將 maxPoolSize設置為60也不合適,此時若系統負載長時間維持在每秒1000個任務,則超出線程池處理能力,應設法降低每個任務的處理時間 (tasktime)。
使用ThreadLocal解決SimpleDateFormat,
privatestatic ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { returnnew SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } };
或者推薦Joda-Time處理時間比較推薦
在java線程中有6個狀態也就是Thread中的枚舉類
NEW,RUNNABLE,WAITING,TIME_WAITING,BLOCKED,TERMINATED
應該在try{} catch{}中重新設置中斷狀態,因為發出中斷異常被退出了。
java內存模型
jvm規范定了jvm內存模型來屏蔽掉各種操作系統,虛擬機實現廠商和硬件的內存訪問差異,確保java程序在所有操作系統和平台上能夠實現一次編寫,到處運行的效果。
為什么IP協議也能夠進行數據的不可靠傳輸,還需要Udp
1.我們需要端口號來實現應用程序間的區分。
2.UDP校驗和可以實現傳輸層的校驗,雖然UDP協議不具備糾錯能力,但是可以對出錯的數據包進行丟棄,而IP的校驗只是在校驗IP報頭,而不是整個數據包,整個數據包的校驗是在傳輸層完成的,如果出錯了,就會把出錯的數據包丟棄。這也就是為什么需要有傳輸層
進程通信中管道和共享內存誰的速度快?
1.管道通信方式的中間介質是文件,通常稱這種文件為管道文件。兩個進程利用管道進行通信,一個進程為寫進程,另一個進程為讀進程。寫進程通過往管道文件中寫入信息,讀進程通過讀端從管道文件中讀取信息,這樣的方式進行通信。
2.共享內存是最快的可用IPC形式,他通過把共享的內存空間映射到進程的地址空間,進程間的數據傳遞不在通過執行任何進入內核的系統調用,節約了時間。java內存模型就采用的是共享內存的方式。各個進程通過公共的內存區域進行通信。
java線程池shutdown和shutdownNow的區別?
Shutdown()方法
當線程池調用該方法時,線程池的狀態則立刻變成SHUTDOWN狀態。此時,則不能再往線程池中添加任何任務,否則將會拋出RejectedExecutionException(拒絕執行異常)異常。但是,此時線程池不會立刻退出,直到添加到線程池中的任務都已經處理完成,才會退出。
shutdownNow()方法
根據JDK文檔描述,大致意思是:執行該方法,線程池的狀態立刻變成STOP狀態,並試圖停止所有正在執行的線程,不再處理還在池隊列中等待的任務,當然,它會返回那些未執行的任務。
它試圖終止線程的方法是通過調用Thread.interrupt()方法來實現的,但是大家知道,這種方法的作用有限,如果線程中沒有sleep 、wait、Condition、定時鎖等應用, interrupt()方法是無法中斷當前的線程的。所以,ShutdownNow()並不代表線程池就一定立即就能退出,它可能必須要等待所有正在執行的任務都執行完成了才能退出。
Integer的裝箱和拆箱的基本原理
Integer包裝類是Java最基本的語法糖優化,如果我們寫一段程序,通過反編譯就會看到,通過的是Integer.valueOf()或者Interger.intValue()來進行轉換的。
Integer和int比較會自動拆箱,
當Integer和integer比較的時候是不會自動拆箱的,除非遇到了算術符,才會調用intValue()方法去拆箱。並且在Integer中的equals是不會處理數據類型轉換關系的,使用時是需要慎用,在equals方法中判斷為Integer類的才會執行真正的判斷流程也就是拆箱去判斷,所以不會處理數據類型轉換比如Long。如果是基本類型比較,編譯器會隱形的把int轉換為long,就可以得出正確的結論。
為什么不推薦使用resume和suspend??
因為在使用suspend()去掛起線程的時候,suspend在導致線程暫停的同時,不會去釋放任何鎖的資源。必須要等待resume()操作,被掛起的線程才能繼續。如果我們resume()操作意外地在suspend()前就執行了,那么被掛起的線程可能很難有機會被繼續執行。並且,更嚴重的是:所占用的鎖不會被釋放,因此可能會導致整個系統工作不正常
yield是謙讓,調用后會使當前線程讓出CPU,但是注意的地方讓出CPU並不表示當前線程不執行了。當前線程在讓出CPU后,還會進行CPU資源的爭奪,有可能剛剛一讓馬上又進。
數據庫事務的隔離級別
1.未提交讀 都不能解決
2.已提交讀 能解決臟讀
3.可重復讀 能解決臟讀,不可重復讀
4.序列化讀 能解決臟讀,不可重復讀,幻讀
Docker和虛擬機的比較
1.傳統的虛擬機在宿主機操作系統上面會利用虛擬機管理程序去模擬完整的一個虛擬機操作系統,docker只是在操作系統上的虛擬化,直接復用本地主機的操作系統,非常輕量級。
docker啟動速度一般在秒級,虛擬機啟動速度一般在分鍾級。
2.對於資源的使用一般是mb,一台機器可以有上千個docker容器,但是虛擬機占用資源為GB,只能支持幾個。
3.性能:接近原生,由於又虛擬化了一層所以效率低。
4.docker采用類似git的命令學習升本低,指令簡單。
5.虛擬機隔離性是完全隔離,容器是安全隔離
lucence組件
每一個詞都會有一個倒排表,多個可以合並
為了標識webSocket:會在請求頭中寫一個upgrade:webSocket
與HTTP比較
同樣作為應用層的協議,WebSocket在現代的軟件開發中被越來越多的實踐,和HTTP有很多相似的地方,這里將它們簡單的做一個純個人、非權威的比較:
相同點
都是基於TCP的應用層協議。
都使用Request/Response模型進行連接的建立。
在連接的建立過程中對錯誤的處理方式相同,在這個階段WS可能返回和HTTP相同的返回碼。
都可以在網絡中傳輸數據。
不同點
WS使用HTTP來建立連接,但是定義了一系列新的header域,這些域在HTTP中並不會使用。
WS的連接不能通過中間人來轉發,它必須是一個直接連接。
WS連接建立之后,通信雙方都可以在任何時刻向另一方發送數據。f
WS連接建立之后,數據的傳輸使用幀來傳遞,不再需要Request消息。
WS的數據幀有序。
微服務架構
首先看一下微服務架構的定義:微服務(MSA)是一種架構風格,旨在通過將功能分解到各個離散的服務中以實現對解決方案的解耦。它有如下幾個特征:
小,且只干一件事情。
獨立部署和生命周期管理。
異構性
輕量級通信,RPC或者Restful。
BIO通信模型圖:
BIO以是一客戶端一線程,一個Acceptor線程來接受請求,之后為每一個客戶端都創建一個新的線程進行鏈路處理,最大的問題缺乏彈性伸縮能力,客戶端並發訪問量增加后,線程數急劇膨脹。可以用線程池緩解但是還是不行。
NIO非阻塞IO解決了這個問題,一個線程就可以管理多個Socket.
在NIO中有三個需要我們了解Buffer,Channel,Selector。
Buffer:NIO是面向緩沖區,IO是面向流的。緩沖區實質上一個數組
,緩沖區不僅僅是一個數組,緩沖區提供了對數據結構化訪問以及維護讀寫位置等信息。
通道Channel:
Channel是一個通道,網絡數據通過Channel讀取和寫入。通道與流的不同之處在於通道是雙向的,流是一個方向上移動,通道可以用於讀寫操作,特別是在UNIX網絡編程模型底層操作系統的通道都是全雙工的,同時支持讀寫操作。
Selector:多路復用器NIo編程的基礎,多路復用器提供選擇就緒任務的 能力。簡單來說Selector會不斷注冊在其上的Channel,如果某個Channel上面發生讀或者寫時間,這個Channel就處於就緒狀態,會被Selector輪詢出來,通過SelectionKey,一個多路復用器可以同時輪詢多個Channel,JDK使用了epoll代理傳統select實現,所以沒有最大連接句柄fd的限制,意味着一個線程負責Selector的輪詢,就可以接入成千上萬的客戶端
select/poll 和 epoll: select poll順序掃描fd,就緒就返回fd。epoll則是 采用事件驅動,用回調的方式返回fd。
面向對象設計七大原則
1. 單一職責原則(Single Responsibility Principle)
每一個類應該專注於做一件事情。
2. 里氏替換原則(Liskov Substitution Principle)
超類存在的地方,子類是可以替換的。
3. 依賴倒置原則(Dependence Inversion Principle)
實現盡量依賴抽象,不依賴具體實現。
4. 接口隔離原則(Interface Segregation Principle)
應當為客戶端提供盡可能小的單獨的接口,而不是提供大的總的接口。
5. 迪米特法則(Law Of Demeter)
又叫最少知識原則,一個軟件實體應當盡可能少的與其他實體發生相互作用。
6. 開閉原則(Open Close Principle)
面向擴展開放,面向修改關閉。
7. 組合/聚合復用原則(Composite/Aggregate Reuse Principle CARP)
盡量使用合成/聚合達到復用,盡量少用繼承。原則: 一個類中有另一個類的對象。
攔截器和過濾器的區別
強類型:不允許隱形轉換
弱類型:允許隱形轉換
靜態類型:編譯的時候就知道每一個變量的類型,因為類型錯誤而不能做的事情是語法錯誤.
動態類型:編譯得時候不知道每一個變量的類型,因為類型錯誤而不能做的事情是運行時錯誤
編譯語言和解釋語言:解釋性編程語言,每個語句都是執行的時候才翻譯而且是一句一句的翻譯就很低。
編譯的語言就只需要一次 就可以了
繼承Thread 和 接口Runnabel的區別
主要是繼承和實現接口的兩個區別,如果只想重寫run方法就可以使用,如果不重寫其他方法 就使用runnable,如果使用實現接口的實現,讓自己方便管理線程以及讓線程復用,可以使用線程池去創建。
hash算法(特點、哈希函數構造、解決沖突的策略)
哈希表的概念:
哈希表就是一種以 鍵-值(key-indexed) 存儲數據的結構,我們只要輸入待查找的值即key,即可查找到其對應的值。
哈希表的實現思路:
如果所有的鍵都是整數,那么就可以使用一個簡單的無序數組來實現:將鍵作為索引,值即為其對應的值,這樣就可以快速訪問任意鍵的值。這是對於簡單的鍵的情況,我們將其擴展到可以處理更加復雜的類型的鍵。對於沖突的情況,則需要處理地址的沖突問題。所以,一方面要構造出良好的哈希函數,對鍵值集合進行哈希,另外一方面需要設計出良好的解決沖突的算法,處理哈希碰撞的沖突。
哈希表的查找步驟:
(1)使用哈希函數將被查找的鍵轉換為數組的索引。在理想的情況下,不同的鍵會被轉換為不同的索引值,但是在有些情況下我們需要處理多個鍵被哈希到同一個索引值的情況。所以哈希查找的第二個步驟就是處理沖突
(2)處理哈希碰撞沖突。有很多處理哈希碰撞沖突的方法,本文后面會介紹拉鏈法和線性探測法。
哈希表的思想:
是一個在時間和空間上做出權衡的經典例子。如果沒有內存限制,那么可以直接將鍵作為數組的索引。那么所有的查找時間復雜度為O(1);如果沒有時間限制,那么我們可以使用無序數組並進行順序查找,這樣只需要很少的內存。哈希表使用了適度的時間和空間來在這兩個極端之間找到了平衡。只需要調整哈希函數算法即可在時間和空間上做出取舍。
哈希表的工作步驟:
1) 哈希(Hash)函數是一個映象,即將關鍵字的集合映射到某個地址集合上,它的設置很靈活,只要這個地址集合的大小不超出允許范圍即可;
2) 由於哈希函數是一個壓縮映象,因此,在一般情況下,很容易產生“沖突”現象,即: key1!=key2,而 f (key1) = f(key2)。鍵不同,但是對應的取值相同。
3). 只能盡量減少沖突而不能完全避免沖突,這是因為通常關鍵字集合比較大,其元素包括所有可能的關鍵字,而地址集合的元素僅為哈希表中的地址值。
哈希函數的構造方法:
1、直接地址法
以數據元素的關鍵字k本身或者他的線性的函數作為它 的哈希地址,也就是H(k)=k,或者H(k)=a*k+b;
適用的場景:地址集合的大小==關鍵字的集合。
2、數字分析法
取數據元素關鍵字中某些取值較均勻的數字位作為哈希地址的方法
適用的場景:能預先估計出全體關鍵字的每一位上各種數字出現的頻度。
3、折疊法
將關鍵字分割成位數相同的幾部分(最后一部分的位數可以不同),然后取這幾部分的疊加和(舍去進位),這方法稱為折疊法
適用場景:關鍵字的數字位數特別多。
4、平方取中法
先取關鍵字的平方,然后根據可使用空間的大小,選取平方數是中間幾位為哈希地址。
適用場景:通過取平方擴大差別,平方值的中間幾位和這個數的每一位都相關,則對不同的關鍵字得到的哈希函數值不易產生沖突,由此產生的哈希地址也較為均勻。
5、減去法
6、基數轉換法
7、除留余數法
8、隨機數法
9、隨機乘數法
10、旋轉法
構造哈希哈希函數的原則:
1、計算哈希函數的時間
2、關鍵子的長度
3、哈希表的長度
4、關鍵字的分布的情況
5、記錄查找頻率
哈希函數的沖突解決的方法:
1、開放地址法
這種方法也稱再散列法,其基本思想是:當關鍵字key的哈希地址p=H(key)出現沖突時,以p為基礎,產生另一個哈希地址p1,如果p1仍然沖突,再以p為基礎,產生另一個哈希地址p2,…,直到找出一個不沖突的哈希地址pi ,將相應元素存入其中。這種方法有一個通用的再散列函數形式:
Hi=(H(key)+di)% m i=1,2,…,n
其中H(key)為哈希函數,m 為表長,di稱為增量序列。增量序列的取值方式不同,相應的再散列方式也不同。主要有以下三種:
l 線性探測再散列
dii=1,2,3,…,m-1
這種方法的特點是:沖突發生時,順序查看表中下一單元,直到找出一個空單元或查遍全表。
l 二次探測再散列
di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )
這種方法的特點是:沖突發生時,在表的左右進行跳躍式探測,比較靈活。
l 偽隨機探測再散列
di=偽隨機數序列。
2、再哈希法
這種方法是同時構造多個不同的哈希函數:
Hi=RH1(key) i=1,2,…,k
當哈希地址Hi=RH1(key)發生沖突時,再計算Hi=RH2(key)……,直到沖突不再產生。這種方法不易產生聚集,但增加了計算時間。
3、鏈地址法
這種方法的基本思想是將所有哈希地址為i的元素構成一個稱為同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
4、建立公共溢出區
這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表
類加載的方式
1.通過new
2.通過反射利用當前線程的classloader
3.自己繼承實現一個classloader實現自己的類裝載器
class.forName 和 Classloader.loadClass區別
Classloader
HashMap內部是怎么實現的?(拉鏈式結構)
核心:hashMap采用拉鏈法,構成“鏈表的數組”
存儲示意圖:
索引index建立規則:
(1)一般情況下通過hash(key)%length實現,元素存儲在數組中的索引是由key的哈希值對數組的長度取模得到。
(2)hashmap也是一個線性的數組實現的,里面定義一個內部類Entry,屬性包括key、value、next.Hashmap的基礎就是一個線性數組,該數組為Entry[] ,map里面的內容都保存在entry[]數組中。
(3)確定數組的index:hashcode%table.length
數組的下標index相同,但是不表示hashcode相同。
實現隨機存儲的方法:
// 存儲時:
int hash = key.hashCode(); // 這個hashCode方法這里不詳述,只要理解每個key的hash是一個固定的int值
int index = hash % Entry[].length;
Entry[index] = value;
// 取值時:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
put方法的實現:
如果兩個key通過hash%Entry[].length得到的index相同,會不會有覆蓋的危險?
這里HashMap里面用到鏈式數據結構的一個概念。上面我們提到過Entry類里面有一個next屬性,作用是指向下一個Entry。打個比方, 第一個鍵值對A進來,通過計算其key的hash得到的index=0,記做:Entry[0] = A。一會后又進來一個鍵值對B,通過計算其index也等於0,現在怎么辦?HashMap會這樣做:B.next = A,Entry[0] = B,如果又進來C,index也等於0,那么C.next = B,Entry[0] = C;這樣我們發現index=0的地方其實存取了A,B,C三個鍵值對,他們通過next這個屬性鏈接在一起。所以疑問不用擔心。也就是說數組中存儲的是最后插入的元素。
get方法的實現:
先定位到數組元素,再遍歷該元素處的鏈表
table的大小:
table的初始的大小並不是initialCapacity,是initialCapacity的2的n次冪
目的在於:當哈希表的容量超過默認的容量時,必須重新調整table的大小,當容量已經達到最大的可能的值時,這時需要創建一張新的表,將原來的表映射到該新表。
req.getSession().invalidate(); 銷毀session
req.getSession().setMaxInactiveInterval(30); 設置默認session的過期時間,tomcat的默認過期時間是 30分鍾
利用線程池的優勢:
1、降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
2、提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
3、提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。
boolean 單獨使用的時候還會被變成int,4個字節
boolean[] 數組使用的時候變成byte,1個字節
Cache和Buffer的區別
Cache:緩存區,位於cpu和主內存之間容量很小但速度很快的存儲器,因為CPU的速度遠遠高於主內存的速度,CPU從內存中讀取數據許要等待很長的時間,而Cache保存着CPU剛用過的數據或循環使用的部分數據,Cache讀取數據更快,減少cpu等待時間。
Buffer:緩沖區,用於存儲速度不同步的情況,處理系統兩端速度平衡,為了減小短期內突發I/O的影響,起到流量整形的作用。速度慢的可以先把數據放到buffer,然后達到一定程度在讀取數據
內連接:
必須兩個表互相匹配才會出現
外鏈接
左外鏈接:左邊不加限制
右外連接:右邊不加限制
全連接:左右都不加限制
三、如何創建索引
全文檢索的索引創建過程一般有以下幾步:
第一步:一些要索引的原文檔(Document)。
為了方便說明索引創建過程,這里特意用兩個文件為例:
文件一:Students should be allowed to go out with their friends, but not allowed to drink beer.
文件二:My friend Jerry went to school to see his students but found them drunk which is not allowed.
第二步:將原文檔傳給分詞器(Tokenizer)。
分詞器(Tokenizer)會做以下幾件事情(此過程稱為Tokenize):
1. 將文檔分成一個一個單獨的單詞。
2. 去除標點符號。
3. 去除停詞(Stop word)。
所謂停詞(Stop word)就是一種語言中最普通的一些單詞,由於沒有特別的意義,因而大多數情況下不能成為搜索的關鍵詞,因而創建索引時,這種詞會被去掉而減少索引的大小。
英語中停詞(Stop word)如:“the”,“a”,“this”等。
對於每一種語言的分詞組件(Tokenizer),都有一個停詞(stop word)集合。
經過分詞(Tokenizer)后得到的結果稱為詞元(Token)。
在我們的例子中,便得到以下詞元(Token):
“Students”,“allowed”,“go”,“their”,“friends”,“allowed”,“drink”,“beer”,“My”,“friend”,“Jerry”,“went”,“school”,“see”,“his”,“students”,“found”,“them”,“drunk”,“allowed”。
第三步:將得到的詞元(Token)傳給語言處理組件(Linguistic Processor)。
語言處理組件(linguistic processor)主要是對得到的詞元(Token)做一些同語言相關的處理。
對於英語,語言處理組件(Linguistic Processor)一般做以下幾點:
1. 變為小寫(Lowercase)。
2. 將單詞縮減為詞根形式,如“cars”到“car”等。這種操作稱為:stemming。
3. 將單詞轉變為詞根形式,如“drove”到“drive”等。這種操作稱為:lemmatization。
而且在此過程中,我們驚喜地發現,搜索“drive”,“driving”,“drove”,“driven”也能夠被搜到。因為在我們的索引 中,“driving”,“drove”,“driven”都會經過語言處理而變成“drive”,在搜索時,如果您輸入“driving”,輸入的查詢 語句同樣經過我們這里的一到三步,從而變為查詢“drive”,從而可以搜索到想要的文檔。
ZK實現分布式鎖- 是否存在,並且最小的
根據ZK中節點是否存在,可以作為分布式鎖的鎖狀態,以此來實現一個分布式鎖,下面是分布式鎖的基本邏輯:
客戶端調用create()方法創建名為“/dlm-locks/lockname/lock-”的臨時順序節點。
客戶端調用getChildren(“lockname”)方法來獲取所有已經創建的子節點。
客戶端獲取到所有子節點path之后,如果發現自己在步驟1中創建的節點是所有節點中序號最小的,那么就認為這個客戶端獲得了鎖。
如果創建的節點不是所有節點中需要最小的,那么則監視比自己創建節點的序列號小的最大的節點,進入等待。直到下次監視的子節點變更的時候,再進行子節點的獲取,判斷是否獲取鎖。
而且zk的臨時節點可以直接避免網絡斷開或主機宕機,鎖狀態無法清除的問題,順序節點可以避免驚群效應。這些特性都使得利用ZK實現分布式鎖成為了最普遍的方案之一。
Redus實現分布式鎖,使用setNX (set if not exists)
getset(先寫新值返回舊值,用於分辨是不是首次操作) 防止網絡斷開后 會設置超時
http://blog.csdn.net/ugg/article/details/41894947
SETNX 可以直接加鎖操作,比如說對某個關鍵詞foo加鎖,客戶端可以嘗試
SETNX foo.lock <current unix time>
如果返回1,表示客戶端已經獲取鎖,可以往下操作,操作完成后,通過
DEL foo.lock
命令來釋放鎖。
處理死鎖
在 上面的處理方式中,如果獲取鎖的客戶端端執行時間過長,進程被kill掉,或者因為其他異常崩潰,導致無法釋放鎖,就會造成死鎖。所以,需要對加鎖要做時 效性檢測。因此,我們在加鎖時,把當前時間戳作為value存入此鎖中,通過當前時間戳和Redis中的時間戳進行對比,如果超過一定差值,認為鎖已經時 效,防止鎖無限期的鎖下去,但是,在大並發情況,如果同時檢測鎖失效,並簡單粗暴的刪除死鎖,再通過SETNX上鎖,可能會導致競爭條件的產生,即多個客 戶端同時獲取鎖。
C1獲取鎖,並崩潰。C2和C3調用SETNX上鎖返回0后,獲得foo.lock的時間戳,通過比對時間戳,發現鎖超時。
C2 向foo.lock發送DEL命令。
C2 向foo.lock發送SETNX獲取鎖。
C3 向foo.lock發送DEL命令,此時C3發送DEL時,其實DEL掉的是C2的鎖。
C3 向foo.lock發送SETNX獲取鎖。
此時C2和C3都獲取了鎖,產生競爭條件,如果在更高並發的情況,可能會有更多客戶端獲取鎖。所以,DEL鎖的操作,不能直接使用在鎖超時的情況下,幸好我們有GETSET方法,假設我們現在有另外一個客戶端C4,看看如何使用GETSET方式,避免這種情況產生。
C1獲取鎖,並崩潰。C2和C3調用SETNX上鎖返回0后,調用GET命令獲得foo.lock的時間戳T1,通過比對時間戳,發現鎖超時。
C4 向foo.lock發送GESET命令,
GETSET foo.lock <current unix time>
並得到foo.lock中老的時間戳T2
如果T1=T2,說明C4獲得時間戳。
如果T1!=T2,說明C4之前有另外一個客戶端C5通過調用GETSET方式獲取了時間戳,C4未獲得鎖。只能sleep下,進入下次循環中。
現在唯一的問題是,C4設置foo.lock的新時間戳,是否會對鎖產生影響。其實我們可以看到C4和C5執行的時間差值極小,並且寫入foo.lock中的都是有效時間錯,所以對鎖並沒有影響。
為 了讓這個鎖更加強壯,獲取鎖的客戶端,應該在調用關鍵業務時,再次調用GET方法獲取T1,和寫入的T0時間戳進行對比,以免鎖因其他情況被執行DEL意 外解開而不知。以上步驟和情況,很容易從其他參考資料中看到。客戶端處理和失敗的情況非常復雜,不僅僅是崩潰這么簡單,還可能是客戶端因為某些操作被阻塞 了相當長時間,緊接着 DEL 命令被嘗試執行(但這時鎖卻在另外的客戶端手上)。也可能因為處理不當,導致死鎖。還有可能因為sleep設置不合理,導致Redis在大並發下被壓垮。 最為常見的問題還有
AOF重寫帶有子進程副本保證安全
Java虛擬機
Java內存結構,分區,每個區放置什么
程序計數器:(線程私有)當前線程所執行的字節碼的行號指示器,通過改變這個計數器的值來選取下一條需要執行的字節碼的指令,以程序中分支、循環和跳轉等流程的控制都離不開這個計數器的指示。
虛擬機棧:(線程私有),每個方法在執行時都會創建一個棧楨,用於存儲局部變量表、操作數棧、動態鏈接和方法出口等信息。一個方法從調用到執行完成的過程,對應的棧楨在虛擬機棧的進出過程。當線程結束時,虛擬機棧中的數據會被自動的釋放。
局部變量表:基本數據類型、對象的引用、返回地址,局部變量表鎖需要的內存空間是在程序編譯時就已經會被確定好的。
本地方法棧:(線程私有)虛擬機棧是為執行java方法所服務的,而本地方法棧是為了虛擬機使用到的本地方法鎖服務的。
堆區:(線程共享)java堆是被所有的線程所共享的一片區域,所有的對象的實例和數組都會在堆區盡心分配。java堆細分:新生代和老年代;也可能會划分出多個線程鎖共享額分配緩沖區TLAB;
Java堆可以在物理上不連續的內存空間中,只要邏輯上連續就可以。
方法區:(線程共享)存儲已經被虛擬機加載過的類的信息、常量、靜態變量和及時編譯器編譯后的代碼。在方法區中一個區域叫做:運行時常量池,用於存放編譯后生成的字面量和符號的引用。
堆的分代
(1)年輕代:
所有新生成的對象首先都是放在年輕代的。年輕代的目標就是盡可能快速的收集掉那些生命周期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區(一般而言)。
大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當一個Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當另一個Survivor區也滿了的時候,從前一個Survivor區復制過來的並且此時還存活的對象,將被復制“年老區(Tenured)”。
(2)年老代:
在年輕代中經歷了N(可配置)次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。
(3)持久代:
用於存放靜態數據,如 Java Class, Method 等。持久代對垃圾回收沒有顯著影響。
OOM異常的處理思路
對象的創建方法,對象的內存的分配,對象的訪問定位
對象的創建:
(1)第一步,當遇到一個new的指令,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,需要先執行相應的類加載過程;
(2)第二步,根據類加載完成后確定的內存大小,為對象分配內存;
(3)第三步,需要對分配到的內存空間都初始化為零值;
(4)第四步,虛擬機要對對象設置一些基本信息,如對象是那個類的實例、對象的哈希碼、對象的GC分代年齡信息、如何才能找到類的元數據信息等,到這里虛擬機創建對象的工作已經完成;
(5)第五步,從程序的角度,我們還需要對對象進行初始化操作。
對象的內存分配:
(1)對象頭:
存儲hashcode 、gc分代年齡以及一些必要的自身的運行時數據
(2)實例數據:
存儲真實的數據信息
(3)對齊填充:
僅僅起到占位符的作用
對象的訪問定位:
通過句柄池的訪問,在句柄池中保存着到對象實例數據的指針以及到對象類型的數據的指針
通過直接的指針服訪問,通過引用直接指向java堆的對象的實例數據
GC的三種收集方法:標記清除、標記整理、復制算法的原理與特點,分別用在什么地方,如果讓你優化收集方法,有什么思路?
標記清除法:
就是先標記哪些對象實例不用,然后直接清除。缺點就是產生大量的內存碎片,下次若要存儲一個大的對象,無法找到連續內存而又必須提前GC
標記整理:
也就是先標記,然后對存活的對象進行移動,全部移動到一端,然后再對其它的內存進行清理。
復制算法:
把內存分成相等的AB兩塊,每次只使用其中的一塊。比如當A內存使用完后,就把A中還存活着的對象復制到另外一塊內存中去(B),然后再把已經使用過的內存清理掉。優點:這樣就不用考慮內存碎片的問題了。缺點:內存減半,代價略高。
GC收集器有哪些?CMS收集器與G1收集器的特點。
對於新生代的收集器:
Serial單線程收集器 parnew多線程收集器 parallelSccavenge收集器
對於老年代的收集器:
CMS並發收集低停頓收集器 serial Old單線程收集器 parallel Old多線程收集器
CMS收集器:
優點:並發收集、低停頓
缺點:
(1)對cpu資源非常的敏感,在並發的階段雖然不會導致用戶的線程停頓,但是會由於占用一部分的線程導致應用程序變慢,總的吞吐量會降低;
(2)無法去處理浮動垃圾;
(3)基於“標記-清除”算法的收集器,所以會出現碎片。
G1收集器:
優點:
(1)能充分利用cpu、多核的優勢,使用多個cpu縮短停頓的時間;
(2)分代收集,不要其他收集器的配合便可以獨立管理整個的GC堆;
(3)空間整合:整體基於“標記-清理”算法的實現,局部是基於“復制”算法的實現;
(4)可以預測的停頓
Minor GC、Full GC分別在什么時候發生?
Minor GC:新生代GC,當jvm無法為一個新的對象分配空間時會觸發
Full GC:整個堆空間的GC
類加載的五個過程:加載、連接、初始化。
類的加載:將類的class文件讀入內存,並創建一個叫做java.lang.Class對象,當程序中使用任何類時,系統都會為之建立一個java.lang.Class對象。
這些類的class文件的來源:
(1)從本地文件系統中加載class文件
(2)從jar包中加載class文件,比如jdbc編程時
(3)通過網絡加載class文件
(4)把一個java源文件動態編譯,並執行加載
連接:
(1)驗證:驗證階段用於檢驗被加載的類是否具有正確的內部結構,並和其他的類協調一致
(2)准備:為類的類變量分配內存,並去設置默認的值
(3)解析:將類的二進制數據中的符號引用替換成直接引用。
初始化:
主要是對類變量進行初始化。
(1)如果該類還沒有被加載和連接,則先進行加載連接
(2)如果該類的直接父類還沒有被初始化,則先初始化其直接父類
(3)類中如果有初始化的語句則先去執行這些初始化語句。
反射
概念:在運行的狀態中,對於任何一個類或者對象,可以知道其任意的方法和屬性,這種動態地調用其屬性和方法的手段叫做反射。利用的反編譯的手段
一、通過三種方式來獲取Employee類型,獲取類:
(1)Class c1 = Class.forName(“Employee”);
(2)Class c2 =Employee.class;
(3)Employee e = new Employee(); Class c3 = e.getClass();
二、得到class的實例:
Object o = c1.newInstance();
三、獲取所有的屬性
Field[] fs = c.getDeclaredFields();
四、獲取所有的方法
GetDeclareMethods();
動態代理
J2se
多線程(線程鎖)
線程的狀態:
新建狀態、就緒狀態、運行狀態、阻塞狀態、死亡狀態(線程狀態轉換圖)
多線程的創建和啟動:
(1)繼承Thread類,重寫類的run方法,調用對象的start方法啟動
(2)實現Runnable接口,並重寫該接口的run方法,該方法同樣是線程的執行體,創建runnable實現類的實例,並以此實例作為Thread類的target來創建thread對象,該thread對象才是真的線程對象。
(3)使用Callable和Future接口創建線程。具體是創建Callable接口的實現類,並實現clall()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作為Thread對象的target來創建線程。
方法3:
// 創建MyCallable對象
Callable<Integer> myCallable = new MyCallable();
//使用FutureTask來包裝MyCallable對象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable);
//FutureTask對象作為Thread對象的target
Thread thread = new Thread(ft);
//線程進入到就緒狀態
thread.start();
線程同步的方法:sychronized、lock、reentrantLock等
synchronized修飾同步監視器:修飾可能被並發訪問的共享資源充當同步監視器;
synchronized修飾方法,同步方法的同步監視器是this,也就是調用該方法的對象;
synchronizedd可以用來修飾方法,可以修飾代碼塊,但是不能修飾構造器和成員變量;
使用lock鎖對象,每次只能有一個線程對lock對象進行加鎖和釋放鎖,線程開始訪問該鎖對象時必須先獲得鎖lock
基本用法:
Private final ReentrantLock lock = new ReentrantLock();
Lock.lock();
Try(){
}catch(Exception e){
}finally{}
Lock.unlock();
鎖的等級:內置鎖、對象鎖、類鎖、方法鎖。
內置鎖:每一個java對象都可以用做一個實現同步的鎖,這個鎖成為內置鎖。當一個線程進入同步代碼塊或者方法的時候會自動獲得該鎖,在退出同步代碼塊或者方法時會釋放該鎖。
獲得內置鎖的方法:進入這個鎖的保護的同步代碼塊或者方法
注意:java內置鎖是一個互斥鎖,最多只有一個線程能夠獲得該鎖。
對象鎖:對象鎖是用於對象實例方法,或者一個對象實例上的。
類鎖:類鎖用於類的靜態方法或者一個類的class對象上,一個類的對象實例有多個,但是每個類只有一個class對象,即不同對象實例的對象鎖是互不干擾的,每一個類都有一個類鎖。類鎖只是概念上的,並不是真實存在的。
方法鎖:synchronized修飾方法,同步方法的同步監視器是this,也就是調用該方法的對象;
ThreadLocal的設計理念與作用。
作用:
ThreadLocal類只能去創建一個被線程訪問的變量,如果一段代碼含有一個ThreadLocal變量的引用,即使兩個線程同時執行這段代碼,它們也無法訪問到對方的ThreadLocal變量。
創建ThreadLocal的方式:
private ThreadLocal myThreadLocal = new ThreadLocal();
我們可以看到,通過這段代碼實例化了一個ThreadLocal對象。
我們只需要實例化對象一次,並且也不需要知道它是被哪個線程實例化。
雖然所有的線程都能訪問到這個ThreadLocal實例,但是每個線程卻只能訪問到自己通過調用ThreadLocal的set()方法設置的值。即使是兩個不同的線程在同一個ThreadLocal對象上設置了不同的值,他們仍然無法訪問到對方的值。
如何為ThreadLocal對象賦值和取值:
一旦創建了一個ThreadLocal變量,你可以通過如下代碼設置某個需要保存的值:
myThreadLocal.set("A thread local value”);
可以通過下面方法讀取保存在ThreadLocal變量中的值:
String threadLocalValue = (String) myThreadLocal.get();
get()方法返回一個Object對象,set()對象需要傳入一個Object類型的參數。
初始化該ThreadLocal變量:
通過創建一個ThreadLocal的子類重寫initialValue()方法,來為一個ThreadLocal對象指定一個初始值。
ThreadPool用法與優勢。
優勢:
第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。但是要做到合理的利用線程池,必須對其原理了如指掌。
用法:
線程池的創建:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);
參數:corePoolSize:線程池的基本大小
maximumPoolSize:線程池的最大大小
runnableTaskQueue:任務隊列
keepAliveTime:線程活動保持時間
執行方式:
threadsPool.execute(handler);
threadsPool.submit(handler);
線程池的關閉:
Shutdown和shutdownNow方法實現
線程池的工作流程分析:
先將任務提交的順序為核心線程池、隊列、線程池、當這三個關節都不能執行用戶所提交的線程時,則拋出“無法執行的任務”。
字節流和字符流
(1)java中字節流處理的最基本的單位是單個字節。通常用來處理二進制數據,最基本的兩個字節流類是InputStream和OutputStream,這兩個類都為抽象類。
字節流在默認的情況下是不支持緩存的。每次調用一次read方法都會請求操作系統來讀取一個字節,往往會伴隨一次磁盤的IO,如果要使用內存提高讀取的效率,應該使用BufferedInputStream。
(2)字符流處理的最基本的單元是unicode(碼元),通常用來來處理文本數據。
輸入字符流(文件到內存):把要讀取的字節序列按照指定的編碼方式解碼為相應的字符序列,從而可以存在內存中。
輸出字符流(內存到文件):把要寫入文件的字符序列轉為指定的編碼方式下的字節序列,然后寫入文件中。
區別如下:
1、字節流操作的基本單元為字節;字符流操作的基本單元為Unicode碼元。
unicode的編碼范圍:0x0000~0XFFFF,在這個范圍的每個數字都有一個字符與之對應
2、字節流默認不使用緩沖區;字符流使用緩沖區。
3、字節流通常用於處理二進制數據,實際上它可以處理任意類型的數據,但它不支持直接寫入或讀取Unicode碼元;字符流通常處理文本數據,它支持寫入及讀取Unicode碼元。
序列化(常見的序列化操作)
含義:
java序列化:將java對象轉換為字節序列的過程;
java反序列化:將字節序列恢復為java對象的過程
序列化的目的:
實現數據的持久化,將數據永久地保存在磁盤上,通常放在文件中;
利用序列化實現遠程的通訊,在網絡上傳送對象的字節序列.
實現序列化的三種方法:
(1)某實體類僅僅實現了serializable接口(常用)
序列化步驟:
步驟一:創建一個對象輸出流,它可以包裝一個其它類型的目標輸出流,如文件輸出流:
ObjectOutputStream out = new ObjectOutputStream(new fileOutputStream(“D:\\objectfile.obj”));
步驟二:通過對象輸出流的writeObject()方法寫對象:
//Hello對象的字節流將輸入到文件
out.writeObject(“Hello”);
反序列化步驟:
步驟一:創建一個對象輸入流,它可以包裝一個其它類型輸入流,如文件輸入流:
ObjectInputStream in = new ObjectInputStream(new fileInputStream(“D:\\objectfile.obj”));
步驟二:通過對象輸出流的readObject()方法讀取對象:
//將從文件中讀取到字節序列轉化為對象
String obj1 = (String)in.readObject();
(2)若實體類僅僅實現了Serializable接口,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則采用以下方式進行序列化與反序列化。
ObjectOutputStream調用該對象的writeObject(ObjectOutputStream out)的方法進行序列化。
ObjectInputStream會調用該對象的readObject(ObjectInputStream in)的方法進行反序列化。
(3)若Student類實現了Externalnalizable接口,且Student類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。
ObjectOutputStream調用Student對象的writeExternal(ObjectOutput out))的方法進行序列化。
ObjectInputStream會調用Student對象的readExternal(ObjectInput in)的方法進行反序列化。
String,StringBuffer,StringBuilder的區別,應用場景
1)在執行的速度上:StringBuilder>StringBuffer>String
2)String是字符串常量 StringBuffer和StringBuilder是字符串變量
例子1:
String s = “abcd”;
s=s+1;
Syos(s);
底層執行:首先創建一個對象s,賦予abcd.然后又創建新的對象s,之前的對象並沒有發生變化,利用string操作字符串時,是在不斷創建新的對象,而原來的對象由於沒有了引用,會被GC,這樣執行的效率會很低。
例子2:
String str2 = “This is only a”;
String str3 = “ simple”;
String str4 = “ test”;
String str1 = str2 +str3 + str4;
同理:str2 str3 str3沒有被引用,但是創建了新的對象str1,執行速度上會很慢。
StringBuilder:線程非安全的
StringBuffer:線程安全的
例子3:
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
應用場景:
A:使用要操作少量的數據時,使用String
B:單線程操作字符串緩沖區下操作大量數據使用StringBulider
C:多線程操作字符串緩沖區下操作大量的數據使用StringBuffer
HashMap和HashTable的區別
HashMap是線程不安全的;允許有null的鍵和值;執行的效率高一點;方法不是synchronize的要提供外同步;包含有containsvalue和containskey的方法
HashTable是線程安全的;不允許有null的鍵和值;效率稍微低些;方法是synchronize的;包含contains方法
GC垃圾回收機制原理
(參看博客)
== 與 equals區別
==:對於基本數據類型的變量,直接比較存儲的值是否相等;作用於引用類型的變量,則比較的是該變量所指向的地址是否相同。
equals:不同作用於基本數據類型的變量,如果沒有對equals方法進行重寫,則比較的是引用類型的變量所指向的對象的地址(相當於直接使用父類的equals方法,而該方法則是用==進行的比較,所以結果和用==比較的效果是一樣的);但是比如String Date類對equals進行了重寫,比較的是字面量。
final關鍵字
對於基本的數據類型,使用final關鍵字將使得數值恆定不變;
對於對象引用,final則是引用恆定不變,一但被初始化指向一個對象,它就不會再指向另外一個對象,但是該對象本身是可以被修改的;
對於類,如果不想繼承某個類,可以將該類設置為fianl形式,該類不會有子類;
對於方法,final修飾的方法不會被重寫
對於空白的final,對於沒有給定初始值的fianl,編譯器會在使用前初始化該final修飾的變量
對於宏變量:被final修飾的變量為宏常量 在編譯的階段被其本身的值直接替換
short s1=1;s1 = s1+1;
表達式類型的自動提升,一個short類型的變量和一個int型的數在一起進行運算,會將short類型的數隱式轉換為int參與運算,但是該運算的結果為int類型是不同直接賦值給一個short類型的,必須進行強制的類型轉換,否則編譯是通不過的。
八種基本數據類型的大小,以及他們的封裝類。
類型轉換:byte (1字節)--->short(1)/char(2)--->int(4)--->long(8)--->float(4)--->double(8)
分裝類:Byte Short Character Integer Long Float Double
Switch能否用string做參數?(分版本討論)
(1)在jdk1.7版本前不支持string作為參數,僅僅支持byte、short、char,因為可以轉換為int,但是long和string不能轉換為int,所以不能使用。
(2)在jdk1.7之后,支持使用string作為case的參數,實際匹配的是該字符串的hash值,然后用equals進行安全性檢查。Switch支持String其實是一個語法糖,在編譯后的字節碼文件中都會被還原成原生的類型,並在相應的位置插入強制轉換的代碼,底層的JVM在switch上並沒有修改;當傳入switch是null時,在運行時對一個null調用hashcode()方法,會拋出空指針異常。
Object有哪些公用方法?
object是所有類的父類,任何類都默認繼承Object類
9、Clone
private保護方法,實現對象的淺復制,只有類實現了Cloneable接口才可以調用該方法,否則拋出CloneNotSupportException
10、Equals
在object中與==是一樣的,子類一般需要重寫該方法
11、hashCode
該方法用於哈希查找,重寫了equals方法一般都要重寫hashcode方法,這個方法在一些具有哈希功能的collection中使用
12、getClass
final方法,獲得運行時的類型
13、wait方法
使得當前的線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。Wait方法會一直等待,直到獲得鎖(到了睡眠的時間間隔也會喚醒自己)或者被中斷掉。
調用該方法,當前的線程會進入到睡眠的狀態,直到調用該對象的notify方法、notifyAll方法、調用interrupt中斷該線程,時間間隔到了。
14、Notify
喚醒在該對象上的等待的某個線程
15、notifyAll
喚醒在該對象上的等待到所有的線程
16、toString
把對象轉換成string類型進行輸出
Java的四種引用,強弱軟虛,用到的場景。
引用的級別:
強引用>軟引用>弱引用>虛引用
強引用:如果一個對象具有強引用,垃圾回收器絕對不會回收它。當內存空間不足時,jvm寧願拋出outofmemoryError,使得程序的異常終止。
軟引用:如果一個對象具有軟引用,則內存空間足夠,垃圾回收機制就不會去回收它,當內存不足時,就會進行回收。如果軟引用所引用的對象被垃圾回收器回收,java虛擬機就會把這個軟引用加入到與之關聯的引用隊列。
應用場景:實現內存敏感的高速緩存
弱引用:在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。
應用場景:gc運行后終止
虛引用:就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
寫出生產者消費者模式。
Hashcode的作用。
(1)Hashcode 的存在主要用於解決查找的快捷性,比如在hashmap、hashtable中,hashcode是用來在散列的存儲結構中確定對象的存儲的位置的。
(2)如果兩個對象相同,就是通過equals方法比較返回true,兩個對象裝在一個桶里,也就是hashcode也要一定相同。
(3)兩個對象的hashcode相同,並不一定代表兩個對象就是相同的,只能說明他們存儲在一個桶里。
(4)一般重寫了equals方法,也盡量去重寫hashcode方法,保證先找到該桶,再去找到對應的類,通過equals方法進行比較。
ArrayList、LinkedList、Vector的區別。
1、ArrayList:是基於動態數組的數據結構,LinkedList的基於鏈表的數據結構
2、對於隨機訪問get和set,ArrayList性能較好,因為LinkedList會去移動指針
3、對於新增和刪除的操作,linkedList只需要修改指針的指向,性能較好,但是arrayList會移動數據。
vector的特點:
1、vector的方法都是線程同步的,是線程安全的,但是arraylist和linkedlist不是,由於線程的同步必然會影響性能,所以vector的性能不太高。
2、當vector或者arraylist的元素超過它的初始的大小時,vector會將容量翻倍,但是arraylist只會增加50%,這樣有利於節約內存的空間。
Map、Set、List、Queue、Stack的特點與用法。
HashMap和ConcurrentHashMap的區別
hashMap不是線程安全的;
concurrentHashMap是線程安全的;在其中引入了“分段鎖”,而不是將所有的方法加上synchronized,因為那樣就變成了hashtable.
所謂“分段鎖”,就是把一個大的Map拆分成N個小的hashtable,根據key.hashcode()決定把key放在哪一個hashtable中。
通過把整個Map分為N個Segment(類似HashTable),可以提供相同的線程安全,但是效率提升N倍,默認提升16倍。
TreeMap、HashMap、LindedHashMap的區別。
HashMap:根據鍵的hashcode值進行存儲數據,根據鍵可以直接獲取它的值,具有快速訪問的特點,遍歷時取得數據是隨機的,hashmap最多只允許一條記錄的鍵為null(set無序不重復),允許多條記錄的值為Null;如果要保證線程的同步,應該使用Collections.synchronizedMap()方法進行包裝,或者使用ConcurrentHashMap
LinkedHashMap:保存了記錄的插入的順序,在迭代遍歷Linkedhashmap時,先得到的記錄肯定是先插入的,它遍歷的速度只和實際的數據有關和容量沒關。
TreeMap:實現的是SortMap,能夠把保存的記錄按照鍵進行排序,默認會按照鍵值的升序進行排序,當遍歷TreeMap時得到的記錄是排序過后的。
Collection包結構,與Collections的區別。
Collection是一個集合的接口,提供了對集合對象進行操作的通用的方法。
在它下面的子接口:set、list、map
java.util.Collections是一個包裝的類,包含有各種的有關集合操作的靜態方法,比如包含對集合的搜索、排序、線程安全化等一系列的操作,此類不能被實例化,相當於是操作集合的工具類,服務於java的collection的框架。
介紹下Concurrent包
concurrent包基本有3個package組成
(1)java.util.concurrent:提供大部分關於並發的接口和類,如BlockingQueue,Callable,ConcurrentHashMap,ExecutorService, Semaphore等
(2)java.util.concurrent.atomic:提供所有原子操作的類, 如AtomicInteger, AtomicLong等;
(3)java.util.concurrent.locks:提供鎖相關的類, 如Lock, ReentrantLock, ReadWriteLock, Condition等;
concurrent包的優點:
1. 首先,功能非常豐富,諸如線程池(ThreadPoolExecutor),CountDownLatch等並發編程中需要的類已經有現成的實現,不需要自己去實現一套; 畢竟jdk1.4對多線程編程的主要支持幾乎就只有Thread, Runnable,synchronized等
2. concurrent包里面的一些操作是基於硬件級別的CAS(compare and swap),就是在cpu級別提供了原子操作,簡單的說就可以提供無阻塞、無鎖定的算法; 而現代cpu大部分都是支持這樣的算法的;
Try-catch -finally,try里有return,finally還執行么?
任然會執行。
1、不管有木有出現異常,finally塊中代碼都會執行;
2、當try和catch中有return時,finally仍然會執行;
3、finally是在return后面的表達式運算后執行的(此時並沒有返回運算后的值,而是先把要返回的值保存起來,不管finally中的代碼怎么樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的;
4、finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。
Excption與Error包結構。OOM你遇到過哪些情況,SOF你遇到過哪些情況。
Java面向對象的三個特征與含義。
封裝:
是指將某事物的屬性和行為包裝到對象中,這個對象只對外公布需要公開的屬性和行為,而這個公布也是可以有選擇性的公布給其它對象。在Java中能使用private、protected、public三種修飾符或不用(即默認defalut)對外部對象訪問該對象的屬性和行為進行限制。
繼承:
是子對象可以繼承父對象的屬性和行為,亦即父對象擁有的屬性和行為,其子對象也就擁有了這些屬性和行為。
多態:
java的引用變量有兩種類型,一個是編譯時的類型,一個是運行時的類型,編譯時類型由申明該變量時的類型決定,運行時的類型由實際賦值給該變量的對象所決定,如果編譯時的類型和運行時的類型不一致就可能出現所謂的多態。
在java中把一個子類的對象直接賦值給一個父類的引用變量,當運行該引用變量的方法時,其方法行為總是表現出子類方法的行為特征,這就有可能出現,相同類型的變量,調用同一個方法時呈現多種不同的行為特征,出現了“多態”
Override和Overload的含義和區別。
Overload:方法重載,在同一個類中,方法名相同,參數列表不同,至於方法的修飾符,反回值的類型,與方法的重載沒有任何的聯系。
Override:方法重寫,兩同兩小一大
兩同:方法名稱相同、參數列表相同
兩小:返回值類型要小或者相等;拋出的異常要小或者相等
一大:子類方法的訪問權限要相等或者更大
Interface與abstract類的區別。
實例化:
都不能被實例化
類:一個類只能繼承一次abstract類;一個類可以實現多個interface
數據成員:可以有自己的;接口的數據成員必須定義成static final的
方法:可以有私有的,非abstract方法必須實現;接口中不可以有私有的方法,默認都是public abstract的
變量:可以有私有的,其值可以在子類中重新定義,也可以重新賦值;接口中不可以有私有的成員變量,默認是public static final 實現類中不能去重新定義和改變其值
Java IO與NIO
IO是面向流的,NIO是面向緩沖區
Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩沖區的。 Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前后移動流中的數據。如果需要前后移動從流中讀取的數據,需要先將它緩存到一個緩沖區。 Java NIO的緩沖導向方法略有不同。數據讀取到一個它稍后處理的緩沖區,需要時可在緩沖區中前后移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩沖區時,不要覆蓋緩沖區里尚未處理的數據。
阻塞與非阻塞IO
Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再干任何事情了。 Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用於在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
選擇器(Selectors)
Java NIO的選擇器允許一個單獨的線程來監視多個輸入通道,你可以注冊多個通道使用一個選擇器,然后使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已准備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。
反射的作用原理。
見博客
wait()和sleep()的區別。
(1)sleep方法,該方法屬於thread類;wait方法屬於object類
(2)sleep方法導致程序會暫停執行指定的時間,讓出cpu給其他的線程,但是他還是監控狀態的保持者,當到達指定的時間又會自動恢復運行。也就是調用sleep方法線程不會釋放對象鎖;調用wait方法會釋放對象鎖,進入到等待此對象的等待鎖定池,只有當針對此對象調用了notify()方法后,才會獲取對象鎖進入運行的狀態。
foreach與正常for循環效率對比。
For循環可以從前向后遍歷,也可以從后向前遍歷,可以不逐個遍歷,通常用於已知次數的循環。
foreach循環不能向迭代變量賦值,通常對集合對象從頭到位進行讀取,其有優化的存在。
Java與C++對比。
1、指針
java語言不提供指針,增加了自動的內存管理,有效的防止c/c++中的指針操作失誤。
2、多重繼承
C++支持多重繼承,java不支持多重繼承,但是允許實現多個接口。
3、數據類型和類
java將數據和方法結合起來,分裝到類中,每個對象都可以實現自己的特點和方法;而c++允許將函數和變量定義全局的。
4、內存管理
java可以對所有的對象進行內存管理,自動回收不再使用的對象的內存;c++必須由程序員顯式分配內存釋放內存。
5、操作符的重載
C++支持操作符的重載,java不允許進行操作符的重載。
6、預處理功能
java不支持預處理功能,c++有一個預編譯的階段,也就是預處理器。
7、字符串
C++不支持字符串,java中支持字符串,是java的類對象。
8、數組
java引入了真正的數組,不同於c++中利用指針實現的偽數組。
9、類型的轉換
C++中有時會出現數據類型的隱含轉換,設計到自動強制類型的轉換問題,比如存在將浮點數直接轉換為整數的情況,java不支持自動的強制類型轉換,如果需要,必須顯示進行強制的類型轉換。
10、異常
java中使用try{}catch(){}finally{}進行異常的處理,c++沒有。
HTTP和HTTPS的區別
https:是http的安全版本,利用ssl可以對所傳輸的數據進行加密,默認端口是443
cookie和session的區別
(1)cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
(2)cookie不是很安全,別人可以分析存放在本地的COOKIE並進行COOKIE欺騙,如果主要考慮到安全應當使用session。
(3)session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能,如果主要考慮到減輕服務器性能方面,應當使用COOKIE。
(4)單個cookie在客戶端的限制是4K,就是說一個站點在客戶端存放的COOKIE不能4K。
(5)所以:將登陸信息等重要信息存放為SESSION;其他信息如果需要保留,可以放在COOKIE中
網路
TCP三次握手、四次揮手,各個狀態的名稱和含義timewait的作用?
ACK:tcp協議規定,只有ack=1時才有效,在連接建立后所有發送的豹紋的ack=1
Syn(SYNchronization):在連接建立時用來同步序號。
當SYN=1而ACK=0:這是一個連接請求報文;
當對方同意建立連接時,則應該使得SYN=1而且ACK=1;
當SYN=1:這是一個連接請求或者連接接受報文
FIN(finis):終結的意思,用來釋放一個連接。當fin=1,表示次報文段的發送方的數據已經發送完畢並要求釋放連接。
A的狀態:關閉狀態--->同步已發送--->已建立
B的狀態:關閉狀態--->監聽狀態--->同步收到--->已建立
A:建立狀態--->終止等待1--->終止等待2--->等待2MSL
B:建立狀態--->關閉等待--->最后確認
Timewait的作用?
(1)為了保證A發送最后一個ACK報文能到達B,因為這個ACK報文有可能會丟失,這樣會使得處在最后確認階段的B收不到已經發送的FIN+ACK的確認信息,B會超時重傳該報文段,在2MSL的時間內,A會收到信息,重傳依次確認,重啟該計時器。
(2)保證在2MSL的時間內,所有在本網絡上產生的報文段都消失,使得在新的連接中不會出現舊的連接請求的報文段。
(2)SYN攻擊防范
TCP/IP層次架構,每層的作用和協議
OSI模型:應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層、物理層
TCP/IP模型:應用層、傳輸層、網絡互聯層、主機到網絡層
協議:
(1)應用層:FTP、TELNET、HTTP | SNMP、TFTP、NTP
將OSI模型的會話層和表示層整合成應用層,應用層面向不同的網絡應用引入了不同的應用層協議。
(2)傳輸層:TCP|UDP
功能是使得源端主機和目標端主機上的對等實體可以進行會話,定義了兩種服務質量不同的協議,分別是TCP和UDP協議。
TCP協議是一個面向連接的、可靠的協議。它將一台主機發出的字節流無差錯地發往互聯網上的其他主機。在發送端,它負責把上層傳送下來的字節流分成報文段並傳遞給下層。在接收端,它負責把收到的報文進行重組后遞交給上層。TCP協議還要處理端到端的流量控制,以避免緩慢接收的接收方沒有足夠的緩沖區接收發送方發送的大量數據。
UDP協議是一個不可靠的、無連接協議。主要適用於不需要對報文進行排序和流量控制的場合。
(3)網絡互聯層:IP
網絡互聯層是整個TCP/IP協議棧的核心。功能是把分組發往目標網絡或者主機。為了盡快發送分組,可能會沿着不同的路徑同時進行分組傳遞。因此,分組到達的順序和發送的順序可能會不一致,這就需要上層必須對分組進行排序。同時它可以將不同類型的網絡進行互聯,完成擁塞控制的功能。
(4)主機到網絡層:以太網、令牌環網、PPP
該層未被定義,具體的實現方式隨着網絡類型的不同而不同。
TCP擁塞控制
擁塞:計算機網絡中的帶寬、交換節點中的緩存和處理機都是網絡中的資源,當在某一個時間,對網絡中的某一個資源的需求超出了該資源所能提供的部分,網絡的性能會變壞,就出現了擁塞。
擁塞控制:防止過多的數據注入到網路,使得網絡中的路由器和鏈路不至於過載。擁塞控制是一個全局的過程,和流量控制不同,流量控制是點對點的通信量的控制。
慢開始和擁塞避免:
發送方維持一個叫做擁塞窗口的狀態變量,擁塞窗口取決於網絡的擁塞程度,並且會動態的變化。發送方讓自己的發送窗口等於擁塞窗口,考慮接受方的接受能力,發送窗口可能會小於擁塞窗口。
慢開始算法:不要一開始就發送大量的數據,先探測下網絡的擁塞程度,由小到大逐漸增加擁塞窗口的數量。
擁塞避免算法:讓擁塞窗口緩慢增長,每進過一個往返時間就把發送方的擁塞窗口cwnd+1,而不是加倍,此時擁塞窗口按照線性的規律緩慢增長。
結合使用:為了防止擁塞窗口增長過大引發網絡的擁塞,設置一個慢開始門限ssthresh狀態變量。其用法:
當cwnd<ssthresh,使用慢開始算法
當cwnd>ssthresh,使用擁塞避免算法
當cwnd=ssthresh,慢開始算法和擁塞避免算法隨意。
當遇到網絡擁塞時,就把慢開始門限設置為出現擁塞時發送窗口大小的一半,同時將擁塞的窗口設置為1,再重新開始執行慢開始算法。
滑動窗口是什么設計的?
窗口:是一段可以被發送者發送的字節序列,其連續的范圍稱為“窗口”
滑動:這段“允許發送的范圍”是隨着發送的過程而不斷變換的,表現的形式就是“按照順序滑動”
流量控制:
(1)TCP利用滑動窗口實現流量的控制機制
(2)如何考慮流量控制中的傳輸效率
流量控制,接受方傳遞信息給發送方,使其發送數據不要太快,是一種端到端的控制,主要的方式是返回的ack中會包含自己的接受的窗口的大小,發送方收到該窗口的大小時會控制自己的數據發送。
傳遞效率:單個發送字節單個確認,和窗口有一個空余即通知發送方發送一個字節,會增加網絡中許多不必要的報文,因為會為一個字節數據添加40個字節的頭部。
TCP/UDP的區別
1、TCP面向連接(如打電話要先撥號建立連接);UDP是無連接的,即發送數據之前不需要建立連接。
2、TCP提供可靠的服務。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付,即不保證可靠交付。
3、TCP面向字節流,實際上是TCP把數據看成一連串無結構的字節流;UDP是面向報文的
UDP沒有擁塞控制,因此網絡出現擁塞不會使源主機的發送速率降低(對實時應用很有用,如IP電話,實時視頻會議等)
4、每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信
5、TCP首部開銷20字節;UDP的首部開銷小,只有8個字節
6、TCP的邏輯通信信道是全雙工的可靠信道,UDP則是不可靠
TCP報文結構
緊急比特URG:URG=1,注解該報文應該盡快送達,而不需要按照原來的的隊列次序依次送達
確認比特ACK:只有當ACK=1,確認序號字段才有意義
急迫比特PSH:當PSH=1時,注解懇求遠地TCP將本報文段立即傳送給應用層
復位比特RST:當注解呈現嚴重錯誤時,必須開釋連接,進行重新的傳輸連接
同步比特SYN:當SYN=1而ACK=0時,這是一個連接請求報文段,若對方贊成連接請求會將SYN=1而且ACK=1
終止比特FIN:當FIN=1,注解字符串已經發送完畢,並請求開釋傳輸連接。
HTTP的報文結構(請求報文+響應報文)
HTTP請求報文:(1)請求行+(2)請求頭部+(3)請求正文
(1)請求行:請求方法+URL+協議版本
請求方法:常用GET、POST
協議版本:HTTP/主版本號.次版本號 常用HTTP/1.0和HTTP/1.1
(2)為請求報文添加的一些附加的信息,“名/值”組成,並且是每行一對 用冒號進行分割
在請求頭部存在空行,表示請求頭部的結束,接下來是請求正文!
區別get和post方式
對於get方式沒有請求的正文,對於post方式有請求的正文。
HTTP響應的報文格式:
(1)狀態行+(2)響應頭部+(3)響應正文
(1)狀態行:協議版本+狀態碼+狀態碼描述
(2)響應頭部:也是由鍵值對所組成
(3)響應正文,由服務器端接受數據
HTTP狀態碼的含義
http request的幾種類型(8種)
(1)OPTIONS:返回服務器針對特定資源所支持的HTTP請求方法。也可以利用向Web服務器發送'*'的請求來測試服務器的功能性。
(2)HEAD:向服務器索要與GET請求相一致的響應,只不過響應體將不會被返回。這一方法可以在不必傳輸整個響應內容的情況下,就可以獲取包含在響應消息頭中的元信息。
(3)GET:向特定的資源發出請求。
(4)POST:向指定資源提交數據進行處理請求(例如提交表單或者上傳文件)。數據被包含在請求體中。POST請求可能會導致新的資源的創建和/或已有資源的修改。
(5)PUT:向指定資源位置上傳其最新內容。
(6)DELETE:請求服務器刪除Request-URI所標識的資源。
(7)TRACE:回顯服務器收到的請求,主要用於測試或診斷。
(8)CONNECT:HTTP/1.1協議中預留給能夠將連接改為管道方式的代理服務器。
GET方式和POST方式對比:
GET方式:請求數據放在HTTP包頭;使用明文傳送,不安全;長度較小,一般為1024B;應用的場景為查詢數據;如果傳送的是英文數字或者是數字,直接發送,如果傳送的是中文字符或則是其他的字符,則會進行BASE64編碼
POST方式:請求數據放在HTTP正文;可明文或者密文傳送,較為安全;長度一般沒有限制;應用在修改數據上。
http1.0 和 http1.1的區別
(1)HTTP1.0 規定瀏覽與服務器只是保持短暫的連接,瀏覽器每次請求都需要和服務器建立一個TCP連接,服務器完成請求處理后立即斷開TCP連接,服務器不去跟蹤每個客戶也不去記錄每個客戶過去的請求。HTTP1.0 不支持HOST請求字段
(2)HTTP1.1支持久連接,在一個TCP上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲;
允許客戶端不用等待上一次請求的返回結果,就可以 去發送下一個請求,但是服務器端必須按照接受的客戶端請求的先后順序依次會送響應的結果,這樣客戶端才能夠區分出每次請求的響應的內容。
HTTP1.0 支持HOST請求字段,這樣就可以使用一個IP地址和端口號,在此基礎上使用不同的主機名創建多個虛擬的WEB站點;
HTTP1.1 提供了與身份認證、狀態管理和cache緩存等機制
http怎么去處理長連接
http1.1 默認支持長連接,即一次tcp連接,允許發送多個http請求。
當web服務器看到keep-alive值時,會建立長連接。
電腦上訪問一個網頁的整個過程是怎樣的?DNS、HTTP、TCP、OSPF、IP、ARP
步驟1:當訪問www.baidu.com時,會先從本地的host文件中獲取該域名對應的IP地址,如果找不到就會用DNS協議來獲取IP,在該DNS協議中,計算機會由本地的DNS服務器來解析該域名,最終找到對應的IP地址。
步驟2:接下來是使用TCP協議,建立TCP連接,在建立連接之前需要,為了將給服務器的消息帶給服務器,則需要OSPF\IP\ARP協議的支持,IP告訴該消息從哪里出發,去向那里;消息的傳送會經過一個個的路由器,OSPF會利用路由算法找出最佳的通往目的地址的路徑;ARP負責找到下一個節點的地址,ARP協議使用的MAC地址,整個的發送的過程涉及到每一個節點的MAP地址。
步驟3:通過步驟2的解析IP,現在可以和服務器建立TCP連接了,這時客戶端便可以將Http請求數據發送給服務器端。服務器端進行處理,然后以http response的形式發送給客戶端。
IP地址的分類
A類地址:1個字節的網絡號+3個字節的主機地址 0.0.0.0~126.255.255.255
B類地址:2個字節的網絡號+2個字節的主機地址 128.0.0.0~191.255.255.255
C類地址:3個字節的網絡號+1個字節的主機地址 192.0.0.0~223.255.255.255
D類地址:多播地址
E類地址:保留為今后使用
路由器和交換機的區別
交換機:為數據楨從一個端口到另外一個端口的轉發提供了低時延、低開銷的通路,使得任意端口接受的數據幀都能夠從其他的端口送出。
路由器:網絡連接和路由選擇,用於網絡層的數據轉發。
如何設計一個高並發的系統?
① 數據庫的優化,包括合理的事務隔離級別、SQL語句優化、索引的優化
② 使用緩存,盡量減少數據庫 IO
③ 分布式數據庫、分布式緩存
④ 服務器的負載均衡
設計模式
簡單工廠模式
有一個抽象的產品父類將所有的具體的產品抽象出來,達到復用的目的。同時有一個簡單工廠維護一個對抽象產品的依賴,在該簡單工廠中去負責實例的創建,在該工廠中去實例不同的對象,往往需要利用case判斷語句去動態實例化相關的類。
工廠方法模式
創建對象的接口,讓子類去決定具體實例化的對象,把簡單的內部邏輯的判斷,轉移到了客戶端,讓客戶端去動態地實例化相關的子類。工廠方法模式克服了簡單工廠違背開放-封閉原則的特點。
抽象工廠模式
提供創建一系列相關或者相互依賴對象的接口,而無需指定他們具體的類。
職責鏈模式
使得多個對象都有機會去處理請求,從而避免請求的 發送者和接受者之間的耦合關系,將這些對象連成一條鏈,並沿着這條鏈去傳遞該請求,直到有一個對象處理它為之。
單例模式
(2)惡漢式的單例模式
利用靜態static的方式進行實例化,在類被加載時就會創建實例。
/**
* 餓漢式實現單例模式
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
(6)懶漢式實現單例模式
在被第一次引用時才去創建對象。
/**
* 懶漢式實現單例模式
*/
public class Singleton {
private static Singleton instance;//創建私有的靜態變量
private Singleton() {//私有的構造函數
}
// synchronized方法,多線程情況下保證單例對象唯一
public static synchronized Singleton getInstance() {
//如果實例對象為空,就重新去實例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
分析:這中方法的實現,效率不高,因為該方法定義為同步的方法。
(7)雙重鎖實現的單例模式
/**
* DCL實現單例模式
*/
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 兩層判空,第一層是為了避免不必要的同步
// 第二層是為了在null的情況下創建實例
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
分析:資源的利用率較高,在需要的時候去初始化實例,而且可以保證線程的安全,該方法沒有去進行同步鎖,效率比較好。
(8)靜態內部類實現單例模式
/**
* 靜態內部類實現單例模式
*/
public class Singleton {
private Singleton() {
}
//返回實例的方法
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 靜態內部類
*/
private static class SingletonHolder {
//靜態私有的實例對象
private static Singleton instance = new Singleton();
}
}
分析:第一次加載類時不會去初始化instance,只有第一次調用getInstance()方法時,虛擬機才會加載內部類,初始化instance
可以保證線程的安全,單例對象的唯一,延遲了單例的初始化。
(9)枚舉單例
/**
* 枚舉實現單例模式
*/
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
System.out.println("do something");
}
}
分析:枚舉實例的創建是線程安全的,即使反序列化也不會生成新的實例,在任何的情況下都是單例的。
適配器模式
將一個類的接口轉換成客戶希望的另外一個接口,使得原本由於接口步兼容而不能一起工作的類變得可以一起工作。
target是我們所期望的接口的類型,包含一個request方法,通過使用adapter去實現該接口,並實現其中的request方法,在adapter中建立一個私有的adaptee對象,在adapter重寫的方法中去調用specificRequest方法,這樣適配器adapter就構建好了。只需要在客戶端,創建adapter實例,調用request方法就可以利用多態的方式,實現了specificRequest()方法。
觀察者模式
定義了一種一對多的依賴關系,讓多個觀察者可以同時去監聽某一個主題對象,這個主題對象在狀態發生變化時,會通知所有的觀察者對象,使得他們能夠自動更新自己。
Subject:把所有對觀察者對象的引用保存在一個聚集里,每個主題都可以有任何數量的觀察者,可以增加刪除觀察者對象。
Observer:抽象觀察者,為所有的具體的觀察者定義一個接口,在得到主題時更新自己。
concreteObserver:具體的觀察者,實現更新的方法
concreteSubject:具體的主題
作用:應用在一個對象改變時,需要改變其他的對象,而且具體不知道有多少個對象需要改變,將耦合的雙方都依賴於抽象而不是依賴於具體,從而使得各自的變化都不會影響到另外一邊的變化。
大整數BigInteger 大浮點數 BigDecimal
java運算的優先級:優先級相同時比較結合性: