信號量是非負數,只有兩個操作wait,signal 互斥量是0,1,只能用于一個資源的互斥訪問 互斥量用于線程的互斥,信號線用于線程的同步。
有人做過如下類比: Mutex是一把鑰匙,一個人拿了就可進入一個房間,出來的時候把鑰匙交給隊列的第一個,一般的用法是用于串行化對臨界區代碼的訪問,保證這段代碼不會被并行的運行。 Semaphore是一件可以容納N人的房間,如果人不滿就可以進去,如果人滿了,就要等待有人出來。
是指兩個或兩個以上的進程在執行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。
死鎖的發生必須具備以下四個必要條件:
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。
三次握手過程:
第一次:A給B發送一個數據包,其中syn=1,ack=0,seq=1234(隨機),A進入SYN_SENT狀態。
第二次:B給A發送一個數據包,其中syn=1,ack=1,ack number=1235(第一個數據包的seq+1,表示確認了),seq=5678(隨機),B進入SYN_RCVD狀態。
第三次:A給B發送一個數據包,其中ack=1,ack number=5679(表示B給我的請求已經確認),這時同時進入ESTABLISHED狀態。
為啥不能是兩次或四次呢?
因為至少3次通信才能保證兩方的發信和收信功能都是正確的。
第一次:A方發送功能正常(因為發出去了)
第二次:B方接收功能正常(因為看到了A的請求)、B方發送功能也正常(因為發出去了)
第三次:A方接收功能正常(因為看到了B的請求)
知乎上看到一個段子形象的描述了三次握手:
三次握手: A:“喂,你聽得到嗎?” B:“我聽得到呀,你聽得到我嗎?” A:“我能聽到你,今天balabala……” 兩次握手:(注意此時A聽不到B說話??!) A:“喂,你聽得到嗎?” B:“我聽得到呀” A:“喂喂,你聽得到嗎?” B:“草,我聽得到呀?。。?!” A:“你TM能不能聽到我講話啊??!喂!” “……” 四次握手: A:“喂,你聽得到嗎?” B:“我聽得到呀,你聽得到我嗎?” A:“我能聽到你,你能聽到我嗎?” B:“……不想跟傻逼說話” 作者:匿名用戶 鏈接:https://www.zhihu.com/question/24853633/answer/114872771 來源:知乎 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
四次揮手過程:
第一次:A給B發送一個fin,進入FIN_WAIT_1狀態,序號為1234(隨機)。
第二次:B收到了A的fin,發送ACK,序號為1235,進入CLOSE_WAIT狀態。
第三次:B再發送一個fin,進入LAST_ACK狀態,序號為5678(隨機)。(表示B不再接收A的數據,只接收最后一個ACK,但還會發數據)
第四次:A收到了fin后,發送一個ACK給B,序號為5679,B收到后進入CLOSED狀態。(表示A不再接收B的數據,但還會發)
為啥是四次呢?
這個答案比較簡單了,因為保證雙向通信的關閉。即A->B和B->A都要關閉,每次都要雙方確認。
ArrayList繼承了抽象類AbstractList,實現了List,Randomaccess,Cloneable,Serializable接口。
RandomAccess標識其支持快速隨機訪問;Cloneable標識其支持對象復制;Serializable標識其可序列化;然后介紹一下具體實現:
首先底層是用一個數組實現的,數組的長度默認為10,不過也可以在new 的時候指定。
那么說說增刪改查吧~~
增嘛,就是add方法了,我們在lc里都非常熟悉。然后ArrayList的add源碼是醬紫:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true;}所以是先調用ensureCapacityInternal方法再往數組里面加東西。那么ensureCapacityInternal方法又長什么樣呢?
PRivate void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity);}首先判斷數組是不是空的,若是,則確保數組容量至少為初始值和size+1的較大值,然后再進入ensureExplicitCapacity方法。
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);}modCount我們已經很熟悉了,記錄修改次數的,防止在多線程環境下讀寫沖突,這里因為add操作調用了他,所以modCount++了。
然后就是關鍵了,如果minCapacity(傳進來的是size+1)比數組長度小,那就裝不下了,要grow。
grow方法如下:
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity);}恩我們看到了,在這里是首先把舊容量擴充到原來的1.5倍,然后討論了兩種情況: * 擴充了還是不夠:那么就擴大到minCapacity。 * 新容量比MAX_ARRAY_SIZE(這是個常量,為
然后把數組搬過去即可。
說完add再說與之對應的remove。
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue;}首先檢查一下index是不是不合法的,這里我覺得怪怪的。。。。。
講道理來說,對一個數組的越界訪問不管是偏大還是偏小都要拋出IndexOutOfBoundsException,但是在這段代碼里面,如果index偏大,是在rangeCheck方法里面拋出的,而如果是偏?。ɡ鐐饕粋€負數),則是在引用elementData[index]
里面拋出的,不知道這樣做的理由是什么。如果需要使用方法來約束數組范圍,那么負值也要考慮的;如果在引用數組下標elementData[index]`時約束范圍,那這個rangeCheck方法存在的意義又是什么?
源碼中的解釋是:It is always used immediately prior to an array access,which throws an ArrayIndexOutOfBoundsException if index is negative.哪位讀者能解釋一下? 如果下標是正確的,那么修改次數+1,然后取出舊值,把后面的往前搬一下,清空數組末尾的值(置空)。
然后Add還有這樣的方法:
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;}可是這里的rangeCheckForAdd又檢測負值了。。。。。。。。。。。。。
改和查很簡單,貼一下源碼就可以了。
public E get(int index) { rangeCheck(index); return elementData(index);}public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue;}indexOf和lastIndexOf也很簡單,就是一個從頭找, 一個從尾找。都是遍歷操作。
LinkedList的底層實現是個雙向鏈表,其中節點類描述如下:
private static class Node<E> { E item; Node<E> next; Node<E> prev; Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }然后對象中維護了頭結點和尾節點,因此就可以得到任意一個節點了。
同理,我們還是從增刪改查四個方面研究LinkedList:
先說add,二話不說上源碼:
public boolean add(E e) { linkLast(e); return true;}void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++;}相當簡單,學過數據結構里面鏈表相關章節的都很容易看明白。
然后上remove源碼:
public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false;}這里就是討論了一下節點內儲存了null的特殊情況,然后關鍵部分在unlink方法:
E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element;}同樣也很簡單,就是數據結構里面學過的雙端鏈表的刪除操作,需要注意的就是特殊情況,也就是要刪除的節點在邊上。
因為是雙端鏈表實現的嘛,LinkedList比ArrayList多了addFirst,addLast,removeFirst,removeLast方法,內部實現都差不多也就不再贅述。
然后get和set,先上源碼 :
public E get(int index) { checkElementIndex(index); return node(index).item;}public E set(int index, E element) { checkElementIndex(index); Node<E> x = node(index); E oldVal = x.item; x.item = element; return oldVal;}這個也都比較簡單,同樣是先檢查index是否越界再get或set。
最后他倆的區別就是我們在數據結構課上非常熟悉的,順序存儲和隨機存儲的優勢啦~~ArrayList是隨機存儲的,所以get和set會快,而LinkedList是順序存儲的,所以add和remove快。
這東西比較簡單,他是對一個類的封裝,被ThreadLocal封裝的變量每一個線程都有一個獨立的副本,每一個線程都可以獨立改變自己的副本而不影響別的線程。
在以前,底層實現是一個HashMap,key是當前線程,value是該實例。
但是現在的設計思路改了?。‖F在的底層實現是Thread個HashMap,每個HashMap的key是這個ThreadLocal實例,value是那個對象的副本。
為什么這樣搞呢?如果是原來的設計方案,那么在大型項目里有很多Thread和很多ThreadLocal的前提下,就會有ThreadLocal個HashMap,每個里面就有Thread個元素。在Thread很多的情況下性能會低。
還有一點,當一個線程停止時,對應的ThreadLocal副本都不存在了,可以銷毀一個HashMap。但用第一種設計思路的話這些HashMap都在。
sleep是Thread類的靜態方法。sleep的作用是讓線程休眠制定的時間,在時間到達時恢復,也就是說sleep將在接到時間到達事件事恢復線程執行.
wait是Object的方法,也就是說可以對任意一個對象調用wait方法,調用wait方法將會將調用者的線程掛起,直到其他線程調用同一個對象的notify方法才會重新激活調用者,例如:
sleep和wait的區別有:
這兩個方法來自不同的類分別是Thread和Object最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用 synchronized(x){ x.notify() //或者wait() }sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常finalize()是Object的protected方法,有點像c++里面的析構函數,它是在對象被回收之前調用的。垃圾回收器準備釋放內存的時候,會先調用finalize()。
但是和C++的析構函數不太一樣的是,C++中的析構函數調用的時機是確定的(對象離開作用域或delete掉),但Java中的finalize的調用具有不確定性。
在Java編譯器中,把原始類型(8種)自動轉換為封裝類的過程,稱為自動裝箱,相當于valueOf方法。
例如:
Integer i=10;
這行代碼是等價于Integer i=Integer.valueOf(10);
的。根據jdk源碼,valueOf方法是先去一個叫IntegerCache類(是一個Integer數組cache[]的再封裝)里面查找這個數是否被緩存了,如果有,直接拿出來,如果沒有則new一個對象。
IntegerCache的緩存范圍是從-128到high,這個high是通過JVM的啟動參數指定的。 那么看兩道題:
public class Hello { public static void main(String[] args) { int a = 1000, b = 1000; System.out.println(a == b); //true,因為是基本類型的比較 Integer c = 1000, d = 1000; System.out.println(c == d); //false,因為1000是沒緩存的,系統調用了兩次new,而對象類型比較的是地址,所以false Integer e = 100, f = 100; System.out.println(e == f); //true,100是緩存的,所以直接從cache里面拿的,是同一個地址 } } public class Main { public static void main(String[] args){ int a=0; Integer b=0; Integer c=new Integer(0); Integer d=Integer.valueOf(0); System.out.println(a==b);//true,因為涉及到基本類型的比較,要解包 System.out.println(a==c); System.out.println(b==c);//b是緩存區里拿出來的,c是new出來的,所以false System.out.println(c==d);//d也是緩存區里拿出來的,所以b==d是true,c==d是false System.out.println(b==d); }}單例模式有以下特點: 1. 單例類只能有一個實例。 2. 單例類必須自己創建自己的唯一實例。 3. 單例類必須給所有其他對象提供這一實例。
單例模式有兩種,分別叫做飽漢式和餓漢式。(起名字的一定是個粗糙的大叔,叫萌萌的妹子不好么?)
飽漢式就是說,這個人有錢,資源很多,所以什么時候需要這個實例什么時候new,寫法如下:
public class SingletonDemo{ private static SingletonDemo instance = null; private SingletonDemo(){ } public static SingletonDemo getInstance(){ if(instance==null){ instance = new SingletonDemo(); } return instance; }}餓漢式就是說,這個人很沒錢,怕餓死,所以在類加載的時候就new好,寫法如下:
public class SingletonDemo{ private static SingletonDemo instance = new SingletonDemo(); private SingletonDemo(){ } public static SingletonDemo getInstance(){ return instance; }}很顯然,如果是大型項目,飽漢式的單例類很多的情況下,會導致系統的啟動很慢。餓漢式的問題是在多線程下會出現線程不安全的情況。因為如果多個線程同時運行到if(instance==null)
這句里面,就都進來了,這樣就new了好多instance。
解決方法有3種:
方法一(利用同步方法):把飽漢式的public static Singleton getInstance()
加上synchronized關鍵字,但這種的粒度太大了,在并發很高的情況下會導致性能降低。 我們只需要控制new 語句就可以了,因此請看方法二:
方法二(雙重同步鎖):
public static SingletonDemo getInstance(){ if(null == instance ) { synchronized(SingletonDemo.class){ if(null == instance) instance = new SingletonDemo(); } } return instance;}首先解釋一下,為什么要判斷兩遍呢?進入同步塊不是已經確保了instance==null么?別忘了這是并發環境啊~~~接下來是見證奇跡的時刻?。?/p>
假設去掉同步塊里面的if語句啊。。。。設兩個線程1和2,同時調用了getInstance()方法,由于instance==null成立,則線程1進入同步塊,線程2等待,那么線程1退出同步塊的時候線程2就進入了,可是此時instance已經new出來了,線程2又new 了一遍!??!
所以這兩個if語句看起來逼格很高吧~~表示已經被帥到有沒有~
但是,這種方法也有問題?。。。?/strong>
問題出在哪里呢?這個希望能背下來,如果被問到了說不定可以hold住面試官哦~~
當然這種問題出現的前提是該單例類是很復雜的。假設線程A執行到了第2行,那么instance==null正確,進入同步塊,然后他開始new這個實例,但是 這里假設new 的開銷非常大?。?! 我們都知道jvm加載一個類是先尋找二進制文件,創建Class對象,再生成引用,再賦值、執行構造方法等操作。
那么靜態代碼塊非常復雜的時候,線程B也執行到了第二行的時候,已經生成了引用,那么instance 不為null,可是此時該實例還沒初始化完全?。?!就return了?。Σ粚Γ?!
那么有什么辦法呢,由此提出了方法三~~
方法三:靜態內部類
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return SingletonHolder.INSTANCE; }}這個用的是內部類,首先外部類被加載的時候內部類不立即加載,這樣就做到了延遲加載,而JVM在初始內部類的時候已經實現了線程安全,而不需要關鍵字來限定。
JVM的基本結構分為數據區、本地方法接口、執行引擎、類加載器。
數據區是最重要的一部分,分為如下幾部分:
(1)虛擬機棧VM Stack: 它主要用來存儲線程執行過程中的局部變量,方法的返回值,以及方法調用上下文。每一次函數調用都對應一個棧幀(stack frame),用于存儲當次函數調用的局部變量、返回值等信息。棧區不夠用了會拋出StackOverflowError,一般由于函數調用層數太高(如死遞歸)導致。每一個線程對應一個棧區。
(2)堆區Heap:它是所有線程共享的,用來保存各種對象。如數組,實例等等,然后根據存在時間還可以分為新生代和老生代。
(3)方法區Method:也是所有線程共享的,用于存儲已被加載的類信息、常量、靜態變量等信息。這里面還有個運行時常量池Runtime Constant Pool,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中。(How to understand?)
(4)程序計數器PC Register:這塊內存空間比較小,作用就是存儲當前線程所執行的代碼行號。是線程獨立的。
(5) 本地方法棧Native Method Stack:類似于VM Stack,但是這里是為Native方法服務,也是線程獨立的。
執行引擎:負責執行class文件中包含的字節碼指令.
本地方法接口:主要是調用C或C++實現的本地方法及返回結果。
類加載器:在JVM啟動時或者在類運行時將需要的class加載到JVM中。
GC主要有兩大塊內容,一是垃圾檢測,二是垃圾回收。所謂垃圾檢測,就是系統判斷什么內存空間是垃圾,一般有兩種方法: * 引用計數法:對每個對象增加一個計數器,該對象被引用了就+1,沒被引用就-1. 可是這樣有一個很嚴重的問題,例如有兩個對象ObjA和ObjB,他們之間是互相耦合的,但是沒有其他對象引用他們。事實上他們已經是垃圾了,可是引用計數不為0,就不能回收。 * 可達性分析算法:以根對象GC Root為起點進行搜索,如果有對象是不可達的,就是垃圾。GC Root集包括棧區中引用的、常量池中引用的、靜態區引用的對象等等。
垃圾檢測說完了,那么說說是怎么回收的,如果判定一個對象從Gc Root是不可達的,系統就會回收這些對象。那么如何回收呢,下面由淺入深介紹幾種策略:
(1)標記-清除(Mark-sweep):
若一個對象是垃圾,則標記起來,然后統一回收。但是這樣會產生大量碎片,如果再new一個很大的對象還要觸發內存整理。
(2)復制(Copying):
把內存分為兩部分,每次使用其中一個區域。當這一塊的內存需要清理了,則復制到另一個區域中。這樣就不產生碎片,但是有一半內存空間浪費了。
(3) 標記-整理(Mark-Compact): 標記的過程同策略1,但是清除垃圾對象的同時,把不是垃圾的對象“壓縮”到一起,這樣也解決了碎片,且不浪費一半空間。
分代gc:
為什么要分代回收?-
因為在實際工程中,不同對象的生命周期不一樣,例如socket連接、線程等對象是要從工程啟動開始就始終持續的,而如String List等對象往往使用一次就成為垃圾了。因此需要將對象按生命周期劃分,不同周期使用不同策略。
如何按周期劃分?
將對象按其生命周期的不同劃分成:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。然后Young又細分為Eden,From Survivor,To Survivor三部分,一個新對象優先是放在Eden區的,Eden區滿了的時候,觸發一次MinorGC,這時”存活”的對象將進入Survivor區,同時也判定一下Survivor區存活對象的”年齡”。對象在Survivor區每“熬過”一次MinorGC,就增加一歲。如果增加到一定年齡(默認為15歲),則進入Old Generation.
如下圖所示。(圖轉侵刪。)
由圖可知,兩個Survivor區是對稱的,沒有先后之分。
那么老年代滿了怎么辦?那就觸發一次Full GC,回收整個堆內存。
由上面介紹可知,年輕代的gc用到了復制算法, 老年代的gc用到的是標記-整理算法。
垃圾回收器
垃圾回收器是內存回收的具體實現。《深入理解jvm》一書中以hotspot JVM為例,介紹了7種收集器。
Serial(串行GC)收集器
Serial收集器是一個新生代收集器,單線程執行,使用復制算法。它在進行垃圾收集時,必須暫停其他所有的工作線程(用戶線程)。是Jvm client模式下默認的新生代收集器。對于限定單個CPU的環境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。
ParNew(并行GC)收集器
ParNew收集器其實就是serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其余行為與Serial收集器一樣。
Parallel Scavenge(并行回收GC)收集器
Parallel Scavenge收集器也是一個新生代收集器,它也是使用復制算法的收集器,又是并行多線程收集器。parallel Scavenge收集器的特點是它的關注點與其他收集器不同,CMS等收集器的關注點是盡可能地縮短垃圾收集時用戶線程的停頓時間,而parallel Scavenge收集器的目標則是達到一個可控制的吞吐量。吞吐量= 程序運行時間/(程序運行時間 + 垃圾收集時間),虛擬機總共運行了100分鐘。其中垃圾收集花掉1分鐘,那吞吐量就是99%。
Serial Old(串行GC)收集器
Serial Old是Serial收集器的老年代版本,它同樣使用一個單線程執行收集,使用“標記-整理”算法。主要使用在Client模式下的虛擬機。
Parallel Old(并行GC)收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。
CMS(并發GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器是基于“標記-清除”算法實現的,整個收集過程大致分為4個步驟:
①.初始標記(CMS initial mark)②.并發標記(CMS concurrenr mark)③.重新標記(CMS remark)④.并發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟任然需要停頓其他用戶線程。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,并發標記階段是進行GC ROOTS 根搜索算法階段,會判定對象是否存活。而重新標記階段則是為了修正并發標記期間,因用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比并發標記階段要短。由于整個過程中耗時最長的并發標記和并發清除過程中,收集器線程都可以與用戶線程一起工作,所以整體來說,CMS收集器的內存回收過程是與用戶線程一起并發執行的。CMS收集器的優點:并發收集、低停頓,但是CMS還遠遠達不到完美,主要有三個顯著缺點:* CMS收集器對CPU資源非常敏感。在并發階段,雖然不會導致用戶線程停頓,但是會占用CPU資源而導致引用程序變慢,總吞吐量下降。CMS默認啟動的回收線程數是:(CPU數量+3) / 4。 * CMS收集器無法處理浮動垃圾,可能出現“Concurrent Mode Failure“,失敗后而導致另一次Full GC的產生。由于CMS并發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出現在標記過程之后,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱為“浮動垃圾”。也是由于在垃圾收集階段用戶線程還需要運行,即需要預留足夠的內存空間給用戶線程使用,因此CMS收集器不能像其他收集器那樣等到老年代幾乎完全被填滿了再進行收集,需要預留一部分內存空間提供并發收集時的程序運作使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也可以通過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以降低內存回收次數提高性能。要是CMS運行期間預留的內存無法滿足程序其他線程需要,就會出現“Concurrent Mode Failure”失敗,這時候虛擬機將啟動后備預案:臨時啟用Serial Old收集器來重新進行老年代的垃圾收集,這樣停頓時間就很長了。所以說參數-XX:CMSInitiatingOccupancyFraction設置的過高將會很容易導致“Concurrent Mode Failure”失敗,性能反而降低。 * 最后一個缺點,CMS是基于“標記-清除”算法實現的收集器,使用“標記-清除”算法收集后,會產生大量碎片??臻g碎片太多時,將會給對象分配帶來很多麻煩,比如說大對象,內存空間找不到連續的空間來分配不得不提前觸發一次Full GC。為了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用于在Full GC之后增加一個碎片整理過程,還可通過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC之后,跟著來一次碎片整理過程。
G1收集器
G1(Garbage First)收集器是JDK1.7提供的一個新收集器,G1收集器基于“標記-整理”算法實現,也就是說不會產生內存碎片。還有一個特點之前的收集器進行收集的范圍都是整個新生代或老年代,而G1將整個Java堆(包括新生代,老年代)。
例如:”abcdeab”,找出”c”.
思路:先遍歷一遍字符串,用m[26]統計每個字符出現的次數,然后再掃一遍字符串只要m[a[i]-‘a’]==1就返回a[i].
http://blog.csdn.net/cmershen/article/details/51550304
方法一:把這些鏈表扔到優先隊列里面,然后每次取出的鏈表都是頭元素最小的那個,然后刪掉頭結點再扔回隊列里面去,直到隊列全空
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { val = x; } * } */public class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists==null||lists.length==0) return null; PriorityQueue<ListNode> queue= new PriorityQueue<ListNode>(lists.length,new Comparator<ListNode>(){ @Override public int compare(ListNode o1,ListNode o2){ if (o1.val<o2.val) return -1; else if (o1.val==o2.val) return 0; else return 1; } }); ListNode dummy = new ListNode(0); ListNode tail=dummy; for (ListNode node:lists) if (node!=null) queue.add(node); while (!queue.isEmpty()){ tail.next=queue.poll(); tail=tail.next; if (tail.next!=null) queue.add(tail.next); } return dummy.next; }}方法二:歸并排序(待完善)
Kanade算法:
用dp[n]代表結尾為n的連續子數組的和最大值,則有如下轉移方程:
即如果以n-1為結尾的子數組和是>0的,則前n-1項對n為結尾是“有貢獻”的,那么以n為結尾的子列和就帶上前面那個數組。否則從第n項開始。最后返回dp[n]里面最大值即可。因為dp[n]只跟dp[n-1]有關,故維護dp[n-1]一個變量就可以了。
public class Solution { public int maxSubArray(int[] nums) { int maxsum = -9999999; int sumk = 0; for(int num : nums) { sumk = (sumk + num > num) ? sumk + num : num; maxsum = (maxsum > sumk) ? maxsum : sumk; } return maxsum; }}快速冪取模算法,我都有模板了,其實本質就是二進制展開,如果這一位是0就沒關系,如果是1就乘進去
http://blog.csdn.net/u013486414/article/details/42318931
#include <bits/stdc++.h>using namespace std;typedef long long ll;int main() { int a,b; cin>>a>>b; int ans=1; while(b) { if(b&1) ans=(ans*a)%1000; a=(a*a)%1000; b>>=1; } cout<<ans<<endl;}先把頭結點后面的鏈表翻轉過來,再把頭結點安到末尾即可。
ListNode* reverseList(ListNode* head) { if(!head || !head->next) return head; else { ListNode *temp=head->next; ListNode *newHead=reverseList(head->next); temp->next=head; head->next=NULL; return newHead; }}方法一:排序+雙指針,時間O(nlogn),空間1(這樣找不到下標)
方法二:用Map存儲(如果不要求下標的話set也行),掃兩遍數組,第一遍記錄有哪些數(以及下標),第二遍去map(set)里面查sum-i在不在
聚集索引:表數據按照索引的順序來存儲的,也就是說索引項的順序與表中記錄的物理順序一致。對于聚集索引,葉子結點即存儲了真實的數據行,不再有另外單獨的數據頁。 在一張表上最多只能創建一個聚集索引,因為真實數據的物理順序只能有一種。
非聚集索引:表數據存儲順序與索引順序無關。對于非聚集索引,葉結點包含索引字段值及指向數據頁數據行的邏輯指針,其行數量與數據表行數據量一致。
事務(transaction)是由一系列操作序列構成的程序執行單元,這些操作要么都做,要么都不做,是一個不可分割的工作單位。 數據庫事務的四個基本性質(ACID) 1. 原子性(Atomicity) 事務的原子性是指事務中包含的所有操作要么全做,要么全不做(all or none)。
一致性(Consistency) 在事務開始以前,數據庫處于一致性的狀態,事務結束后,數據庫也必須處于一致性狀態。拿銀行轉賬來說,一致性要求事務的執行不應改變A、B 兩個賬戶的金額總和。如果沒有這種一致性要求,轉賬過程中就會發生錢無中生有,或者不翼而飛的現象。事務應該把數據庫從一個一致性狀態轉換到另外一個一致性狀態。
隔離性(Isolation) 事務隔離性要求系統必須保證事務不受其他并發執行的事務的影響,也即要達到這樣一種效果:對于任何一對事務T1 和 T2,在事務 T1 看來,T2 要么在 T1 開始之前已經結束,要么在 T1 完成之后才開始執行。這樣,每個事務都感覺不到系統中有其他事務在并發地執行。
持久性(Durability) 一個事務一旦成功完成,它對數據庫的改變必須是永久的,即便是在系統遇到故障的情況下也不會丟失。數據的重要性決定了事務持久性的重要性。
事務并發執行,不加鎖,會引起的四個問題:(線程不加鎖也會因此這四個問題)
1、丟失修改 事務T1,T2讀取同一個數據,之后先后將修改的內容,寫回數據庫,會導致一個事務丟失修改。 例子: 數據a = 1; T1, T2對a加1, 先后將讀取a的之后, 又先后將2寫進a,這樣導致丟失修改。正確的a的值應該是3。
2、臟讀 T1修改某個數據a,這是T2去讀,之后T1撤銷事務,a回到原來的值,這是T2讀到的a的值就是一個錯誤的值,即臟數據。
3、不可重復讀 T1讀取了一個數據之后,之后T2修改了這數據,T1在讀這個數據,發現和之前讀的不相同。
4、幻讀 T1按照某個條件從數據庫中查找出了某些數據,之后T2對表的記錄進行插入和刪除,T1在按相同的條件從數據庫中,查找數據,發現記錄條數多了或者少了,就像出現幻覺一樣。
IoC(Inversion of Control):IoC就是應用本身不依賴對象的創建和維護而是交給外部容器(這里為spring),這要就把應用和對象之間解耦,控制權交給了外部容器。看一個小例子:
package com.ljq.test;public interface HelloService { public void sayHello(); }那么現在我們需要HelloService里面的方法,我們先配置一下helloworld.xml:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- id 表示組件的名字,class表示組件類 --> <bean id="helloService" class="com.ljq.test.HelloServiceImpl" /></beans>我們看到了, 這里配置的Bean是他的實現類HelloServiceImpl?。。?!具體怎么實現的?I don’t care!!!
接下來我們在工程里使用這個方法:
public void testHelloWorld() { // 1、讀取配置文件實例化一個IOC容器 applicationContext context = new ClassPathXmlApplicationContext("helloworld.xml"); // 2、從容器中獲取Bean,注意此處完全“面向接口編程,而不是面向實現” HelloService helloService = context.getBean("helloService", HelloService.class); // 3、執行業務邏輯 helloService.sayHello();}那么如果sayHello需要改動,我們只需要改HelloServiceImpl即可,大不了換了個其他類,這樣就實現了低耦合。
AOP即面向切面編程,可以將一個與各組件關系不大的但需要大量重復的功能(如記錄日志)以類似于“切片”的方式插入系統的組件中,實現代碼的重用。
還是用一個例子來說明。一個人需要吃蘋果,那么吃蘋果之前要洗手。同理吃什么之前都要洗手,我們把”洗手”這個動作看做切片,注入到吃蘋果方法的前面: 首先寫一個接口IToDo,里面只有吃蘋果一個方法:
package com.service.imp;public interface IToDo { public String toEat();}實現出來,這里也只是輸出吃蘋果:
package com.service;import org.springframework.stereotype.Service;import com.service.imp.IToDo;@Servicepublic class ToDo implements IToDo { @Override public String toEat() { System.out.println("吃蘋果"); return "吃蘋果"; }}接下來完成洗手方法的接口和實現類(略):
package com.service.imp;public interface ipreDo { public String toPre();}applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:component-scan base-package="com.service" /> <aop:aspectj-autoproxy /> <aop:config proxy-target-class="true"> <aop:aspect ref="preDo"> <aop:pointcut expression="execution(* com.service.ToDo.toEat(..))" id="register" /> <aop:before method="toPre" pointcut-ref="register" /> </aop:aspect> </aop:config></beans>然后我們實現這個吃蘋果的工程:
public class application { public static void main(String[] args) { ApplicationContext appCtx = new ClassPathXmlApplicationContext("applicationContext.xml"); IToDo tdo = (IToDo)appCtx.getBean("toDo"); tdo.toEat(); }}顯然從容器里拿出來了一個叫IToDo的Bean,在這里我們只看到了吃蘋果,那么洗手是怎么來的呢?奧秘就在xml配置里面!
這里面配置了preDo類,作為com.service.ToDo.toEat方法的前置方法,且是在方法執行前切入。
那么如果我們吃梨、吃桃子、吃別的、以及其他需要洗手的動作執行前也加上洗手這一動作,只需要修改配置文件即可。不必在每個函數前面都加上洗手方法。
數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現通常使用B樹及其變種B+樹。
在數據之外,數據庫系統還維護著滿足特定查找算法的數據結構,這些數據結構以某種方式引用(指向)數據,這樣就可以在這些數據結構上實現高級查找算法。這種數據結構,就是索引。
新聞熱點
疑難解答