競態條件會致使程序在併發狀況下出現一些bugs。多線程對一些資源的競爭的時候就會產生競態條件,若是首先要執行的程序競爭失敗排到後面執行了, 那麼整個程序就會出現一些不肯定的bugs。這種bugs很難發現並且會重複出現,由於線程間的隨機競爭。一個例子就是無序處理html
競態條件(Race Condition):計算的正確性取決於多個線程的交替執行時序時,就會發生競態條件。java
最多見的競態條件爲:mysql
一,先檢測後執行。執行依賴於檢測的結果,而檢測結果依賴於多個線程的執行時序,而多個線程的執行時序一般狀況下是不固定不可判斷的,從而致使執行結果出現各類問題。linux
對於main線程,若是文件a不存在,則建立文件a,可是在判斷文件a不存在以後,Task線程建立了文件a,這時候先前的判斷結果已經失效,(main線程的執行依賴了一個錯誤的判斷結果)此時文件a已經存在了,可是main線程仍是會繼續建立文件a,致使Task線程建立的文件a被覆蓋、文件中的內容丟失等等問題。c++
多線程環境中對同一個文件的操做要加鎖。面試
和大多數併發錯誤同樣,競態條件不老是會產生問題,還須要不恰當的執行時序 算法
1. synchronized關鍵字spring
2. 使用繼承自Object類的wait、notify、notifyAll方法
3. 使用線程安全的API和集合類:sql
4. 使用原子變量、volatile變量等
5. 使用Concurrent包中提供的信號量Semaphore、閉鎖Latch、柵欄Barrier、交換器Exchanger、Callable&Future、阻塞隊列BlockingQueue等.
6. 手動使用Lock實現基於鎖的併發控制
7. 手動使用Condition或AQS實現基於條件隊列的併發控制
8. 使用CAS和SPIN等實現非阻塞的併發控制數據庫
1. 管道( pipe ):管道是一種半雙工的通訊方式,數據只能單向流動,並且只能在具備親緣關係的進程間使用。進程的親緣關係一般是指父子進程關係。
2. 有名管道 (named pipe) : 有名管道也是半雙工的通訊方式,可是它容許無親緣關係進程間的通訊。
3. 信號量( semophore ) : 信號量是一個計數器,能夠用來控制多個進程對共享資源的訪問。它常做爲一種鎖機制,防止某進程正在訪問共享資源時,其餘進程也訪問該資源。所以,主要做爲進程間以及同一進程內不一樣線程之間的同步手段。
4. 消息隊列( message queue ) : 消息隊列是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
5. 信號 ( sinal ) : 信號是一種比較複雜的通訊方式,用於通知接收進程某個事件已經發生。
6. 共享內存( shared memory ) :共享內存就是映射一段能被其餘進程所訪問的內存,這段共享內存由一個進程建立,但多個進程均可以訪問。共享內存是最快的 IPC 方式,它是針對其餘進程間通訊方式運行效率低而專門設計的。它每每與其餘通訊機制,如信號兩,配合使用,來實現進程間的同步和通訊。
7. 套接字( socket ) : 套解口也是一種進程間通訊機制,與其餘通訊機制不一樣的是,它可用於不一樣機器間的進程通訊。
ConcurrentHashMap是線程安全的HashMap,內部採用分段鎖來實現,默認初始容量爲16,裝載因子爲0.75f,分段16,每一個段的HashEntry<K,V>[]大小爲2。鍵值都不能爲null。每次擴容爲原來容量的2倍,ConcurrentHashMap不會對整個容器進行擴容,而只對某個segment進行擴容。在獲取size操做的時候,不是直接把全部segment的count相加就能夠可到整個ConcurrentHashMap大小,也不是在統計size的時候把全部的segment的put, remove, clean方法所有鎖住,這種方法過低效。在累加count操做過程當中,以前累加過的count發生變化的概率很是小,全部ConcurrentHashMap的作法是先嚐試2(RETRIES_BEFORE_LOCK)次經過不鎖住Segment的方式統計各個Segment大小,若是統計的過程當中,容器的count發生了變化,再採用加鎖的方式來統計全部的Segment的大小。
ABA問題發生在相似這樣的場景:線程1轉變使用CAS將變量A的值替換爲C,在此時,線程2將變量的值由A替換爲C,又由C替換爲A,而後線程1執行CAS時發現變量的值仍爲A,因此CAS成功。但實際上這時的現場已經和最初的不一樣了。大多數狀況下ABA問題不會產生什麼影響。若是有特殊狀況下因爲ABA問題致使,可用採用AtomicStampedReference來解決,原理:樂觀鎖+version。能夠參考下面的案例來了解其中的不一樣
1 public class ABAQuestion 2 { 3 private static AtomicInteger atomicInt = new AtomicInteger(100); 4 private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100,0); 5 6 public static void main(String[] args) throws InterruptedException 7 { 8 Thread thread1 = new Thread(new Runnable(){ 9 @Override 10 public void run() 11 { 12 atomicInt.compareAndSet(100, 101); 13 atomicInt.compareAndSet(101, 100); 14 } 15 }); 16 17 Thread thread2 = new Thread(new Runnable(){ 18 @Override 19 public void run() 20 { 21 try 22 { 23 TimeUnit.SECONDS.sleep(1); 24 } 25 catch (InterruptedException e) 26 { 27 e.printStackTrace(); 28 } 29 boolean c3 = atomicInt.compareAndSet(100, 101); 30 System.out.println(c3); 31 } 32 }); 33 34 thread1.start(); 35 thread2.start(); 36 thread1.join(); 37 thread2.join(); 38 39 Thread thread3 = new Thread(new Runnable(){ 40 @Override 41 public void run() 42 { 43 try 44 { 45 TimeUnit.SECONDS.sleep(1); 46 } 47 catch (InterruptedException e) 48 { 49 e.printStackTrace(); 50 } 51 atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1); 52 atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1); 53 } 54 }); 55 56 Thread thread4 = new Thread(new Runnable(){ 57 @Override 58 public void run() 59 { 60 int stamp = atomicStampedRef.getStamp(); 61 try 62 { 63 TimeUnit.SECONDS.sleep(2); 64 } 65 catch (InterruptedException e) 66 { 67 e.printStackTrace(); 68 } 69 boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1); 70 System.out.println(c3); 71 } 72 }); 73 thread3.start(); 74 thread4.start(); 75 } 76 }
輸出結果:
true false
Servlet不是線程安全的。要解釋爲何Servlet爲何不是線程安全的,須要瞭解Servlet容器(即Tomcat)是如何響應HTTP請求的。
當Tomcat接收到Client的HTTP請求時,Tomcat從線程池中取出一個線程,以後找到該請求對應的Servlet對象並進行初始化,以後調用service()方法。要注意的是每個Servlet對象在Tomcat容器中只有一個實例對象,便是單例模式。若是多個HTTP請求請求的是同一個Servlet,那麼着兩個HTTP請求對應的線程將併發調用Servlet的service()方法。
好比下面的Servlet中的 name
和 i
變量就會引起線程安全問題。
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class ThreadSafeServlet extends HttpServlet { public static String name = "Hello"; //靜態變量,可能發生線程安全問題 int i; //實例變量,可能發生線程安全問題 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); @Override public void init() throws ServletException { super.init(); System.out.println("Servlet初始化"); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.printf("%s:%s[%s]\n", Thread.currentThread().getName(), i, format.format(new Date())); i++; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s:%s[%s]\n", Thread.currentThread().getName(), i, format.format(new Date())); resp.getWriter().println("<html><body><h1>" + i + "</h1></body></html>"); } }
一種方案: 先把 | 替換成換行符,wc -l 統計時候的的數量就必定是result + 1 最後把1減掉
unix系統用 sed 's/|/\n/g' 沒法替換(linux 能夠),因此考慮用tr替換
echo $(echo "10.1.4.90|15/Jan/2018:08:44:38|GET|pi.easyond/|" | tr '|' '\n' | wc -l)-1 | bc
在用迭代器遍歷一個集合對象時,若是遍歷過程當中對集合對象的內容進行了修改(增長,刪除,修改), 則會拋出 ConcurrentModificationException
原理: 迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個modCount變量。集合在被遍歷期間若是內容發生變化,就會改變modCount的值,每當迭代器使用hasNext()/next()遍歷下一個元素以前,都會檢測modCount變量是否爲expectedModCount值,是的話就返回遍歷,不然拋出異常,終止遍歷。
下面以AbstractList的實現舉例,其餘原理同,再也不累述
注意:這裏異常的拋出條件是檢測到this.modCount != l.modCount, 若是集合發生變化時又剛好修改了modCount的值使上述條件語句知足,那麼異常不會拋出。所以,不能依賴這個異常是否拋出的邏輯進行併發操做的編程,這個異常只建議用於檢測併發修改的bug
場景 java.util包下的集合類都是快速失敗的,不能在多線程下發生併發修改(迭代過程當中被修改)
採用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先複製原有集合內容,在拷貝的集合上進行遍歷
原理: 因爲迭代時時對原集合的拷貝進行遍歷,因此在遍歷過程當中對原集合所作的修改並不能被迭代器檢測到,因此不會觸發ConcurrentModificationException
缺點: 基於拷貝內容的優勢是避免了ConcurrentModificationException,但相對地,迭代器並不能訪問到修改後的內容。即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器無從得知。
場景: java.util.concurrent包下的容器都是安全失敗,能夠在多線程下併發使用,併發修改。
思路:能夠估計每一個文件的大小爲5G*64=300G,遠大於4G。因此不可能將其徹底加載到內存中處理。考慮採起分而治之的方法。 遍歷文件a,對每一個url求取hash(url)%1000,而後根據所得值將url分別存儲到1000個小文件(設爲a0,a1,...a999)當中。這樣每一個小文件的大小約爲300M。遍歷文件b,採起和a相同的方法將url分別存儲到1000個小文件(b0,b1....b999)中。這樣處理後,全部可能相同的url都在對應的小文件(a0 vs b0, a1 vs b1....a999 vs b999)當中,不對應的小文件(好比a0 vs b99)不可能有相同的url。而後咱們只要求出1000對小文件中相同的url便可。 好比對於a0 vs b0,咱們能夠遍歷a0,將其中的url存儲到hash_map當中。而後遍歷b0,若是url在hash_map中,則說明此url在a和b中同時存在,保存到文件中便可。 若是分紅的小文件不均勻,致使有些小文件太大(好比大於2G),能夠考慮將這些太大的小文件再按相似的方法分紅小小文件便可
有一個包含100億個URL的文件,假設每一個URL佔用64B,請找出其中全部重複的URL。
將文件經過哈希函數成多個小的文件,因爲哈希函數全部重複的URL只可能在同一個文件中,在每一個文件中利用一個哈希表作次數統計。就能找到重複的URL。這時候要注意的就是給了多少內存,咱們要根據文件大小結合內存大小決定要分割多少文件
topK問題和重複URL實際上是同樣的,重複的多了纔會變成topK。其實就是在上述方法後得到全部的重複URL排個序,可是有點不必,由於咱們要找topK時,最極端的狀況也就是topK在用一個文件中,因此咱們只須要每一個文件的topK個URL,以後再進行排序,這樣就比找出所有的URL再排序好。還有一個topK個URL到最後仍是須要排序,因此咱們在找每一個文件的topK時,是否只須要找到topK個,其它順序不用管,那麼咱們就能夠用大小爲K的小根堆遍歷哈希表。這樣又能夠下降查找的時間。
有不少指標,最基本的好比CPU利用率,內存佔用率,網絡帶寬等。軟件層面好比QPS等。
容量設計的時候須要考慮突發流量,通常要保留20%到30%的備用容量。
集羣還要考慮災備,好比部分機器發生故障的時候接負載均衡如何正確引流限流,避免雪崩式故障(cascading failure)。
至於工具的話,要看現有的技術框架。主要的目的就是作好實時監控和預警。
按內存大小排序(%MEM爲第三列,%CPU爲第二列,不在雷述)
ps -aux默認是按PID從小到大顯示的,若想按佔用內存大小排序則須要藉助另外sort命令針對第4列(內存所在列)排序:
ps -aux | sort -k4rn
咱們還能夠藉助awk來指定顯示哪幾列信息:
ps -aux |awk '{print $2, $4, $11}' | sort -k2rn | head -n 10
在命令行提示符執行top命令.輸入大寫P,則結果按CPU佔用降序排序。輸入大寫M,結果按內存佔用降序排序
public static void sort(int[] a) { DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); }
DualPivotQuicksort翻譯過來就是雙軸快速排序,再點進去發現有這樣的判斷
if (right - left < QUICKSORT_THRESHOLD) { sort(a, left, right, true); return; }
能夠發現若是數組的長度小於QUICKSORT_THRESHOLD
的話就會使用這個雙軸快速排序,而這個值是286。
那若是大於286,它會檢測數組的連續上升/降低的特性好很差,好的話用歸併排序,壞的話用快排
再回到上面的雙軸快速排序,點進去
sort(int[] a, int left, int right, boolean leftmost)這個方法發現
若是數據長度小於(值爲47)的話,就會用插入排序INSERTION_SORT_THRESHOLD
總結:
長度大於等於286且連續性好->歸併
長度大於等於286且連續性很差->雙軸快速排序
長度小於286且大於等於47->雙軸快速排序
長度小於47->插入排序
一路點進去發現到了Arrays類中
public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { sort(a); } else { if (LegacyMergeSort.userRequested) legacyMergeSort(a, c); else TimSort.sort(a, 0, a.length, c, null, 0, 0); } }
會發現若是LegacyMergeSort.userRequested
爲true的話就會使用歸併排序,能夠經過下面代碼設置爲true
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
不過方法legacyMergeSort的註釋上有這麼一句話,說明之後傳統歸併可能會被移除了。
/** To be removed in a future release. */
若是不爲true的話就會用一個叫TimSort
的排序算法。
public方法: getClass() hashCode() equals() toString() notify()系列 wait()系列
protected方法: clone() finalize()
private方法: registerNatives, 該方法做用是將不一樣平臺c/c++實現的方法映射到java的native方法
private static native void registerNatives(); static { registerNatives(); }
java經過可達性分析來判斷對象是否存活,基本思想是經過一系列稱爲"GC roots"的對象做爲起始點,能夠做爲根節點的是:
(1) 虛擬機棧(棧幀中的本地變量表)中引用的對象
(2) 本地方法棧中JNI(即native方法)引用的對象
(3) 方法區中靜態屬性引用的對象
(4) 方法區中常量引用的對象
做爲GC roots的節點主要在全局性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表)中。
我我的理解: 想下java內存模型的5塊,gc主要是收集堆和持久代,剛好對應棧分配的堆對象和靜態&常量所申請的持久代
()
方法
java中class.forName()和classLoader均可用來對類進行加載。
class.forName()前者除了將類的.class文件加載到jvm中以外,還會對類進行解釋,執行類中的static塊。
而classLoader只幹一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance纔會去執行static塊。
1.調用運行時類自己的.class屬性
Class clazz = String.class;
2,經過運行時類的對象獲取
Person p = new Person();
Class clazz = p.getClass();
3.經過Class的靜態方法獲取:體現反射的動態性
String className = 「java.util.commons」;
Class clazz = Class.forName(className);
4.經過類的加載器
String className = 「java.util.commons」;
ClassLoader classLoader = this.getClass().getClassLoader();
Class clazz = classLoader.loadClass(className);
final的出現是爲了避免想改變,而不想改變的理由有兩點: 設計(安全)或者效率
1 設計安全
確保不會在子類中改變語義。若是有一個String的引用,它引用的必定是一個string對象,而不多是其餘類的對象
2 效率
設計成final,jvm不用對相關方法在虛函數表中查詢,而直接定位到String類的相關方法,提升了執行效率
一旦建立是不能被修改的。字符串對象是不可改變的,那麼它們能夠共享
傳統的隊列在服務器上保存有序的消息,若是多個consumers同時從這個服務器消費消息,服務器就會以消息存儲的順序向consumer分發消息。雖然服務器按順序發佈消息,可是消息是被異步的分發到各consumer上,因此當消息到達時可能已經失去了原來的順序,這意味着併發消費將致使順序錯亂。爲了不故障,這樣的消息系統一般使用「專用consumer」的概念,其實就是隻容許一個消費者消費消息,固然這就意味着失去了併發性。
在這方面Kafka作的更好,經過分區的概念,Kafka能夠在多個consumer組併發的狀況下提供較好的有序性和負載均衡。將每一個分區分只分發給一個consumer組,這樣一個分區就只被這個組的一個consumer消費,就能夠順序的消費這個分區的消息。由於有多個分區,依然能夠在多個consumer組之間進行負載均衡。注意consumer組的數量不能多於分區的數量,也就是有多少分區就容許多少併發消費。
Kafka只能保證一個分區以內消息的有序性,在不一樣的分區之間是不能夠的,這已經能夠知足大部分應用的需求。若是須要topic中全部消息的有序性,那就只能讓這個topic只有一個分區,固然也就只有一個consumer組消費它。
從jdk1.2版本開始,把對應的引用分爲4種級別,由高到低分別爲: 強引用 > 軟引用 > 弱引用 > 虛引用.
(1) 強引用
這是最廣泛的引用。當內存空間不足時,java虛擬機寧願拋出OutOfMemoryError使程序異常終止,也不會隨意回收具備強引用的對象來解決內存不足問題
(2) 軟引用
軟引用在內存空間足夠時,垃圾回收器不會進行回收。但內存不足時就會進行回收這些對象的內存。軟引用可用來實現內存敏感的高速緩存
(3) 弱引用
具備比軟引用更短的生命週期,在垃圾回收器線程掃描它所管轄的內存區域時,一旦有弱引用,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器時一個優先級很低的線程,所以不必定會發現那些只具備弱引用的對象
(4)虛引用
顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收。
特別注意,在程序設計中通常不多使用弱引用與虛引用,使用軟引用的狀況較多,這是由於軟引用能夠加速JVM對垃圾內存的回收速度,能夠維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生。
(16)分類清晰的簡單面試題 https://juejin.im/entry/5838f9f1128fe1006bdaa456
(17)tcp粘包 http://www.javashuo.com/article/p-otqxlujw-cd.html
(18)lru cache的算法實現 http://www.javashuo.com/article/p-tvkmduth-bv.html
(19)單點登陸sso http://www.cnblogs.com/ywlaker/p/6113927.html