最近面試一些公司,被問到的關於Java併發編程的問題,以及本身總結的回答。javascript
// 等待方 synchronized(lockObj){ while(condition is false){ lockObj.wait(); } // do business } // 通知方 synchronized(lockObj){ // change condition lockObj.notifyAll(); }
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
從源碼可知,HashMap類中有一個很是重要的字段,就是 Node[] table,即哈希桶數組;
Node是HashMap的一個內部類,實現了Map.Entry接口,本質是就是一個映射(鍵值對)。 HashMap就是使用哈希表來存儲的。爲解決衝突,Java中HashMap採用了鏈地址法,簡單來講,就是數組加鏈表的結合。在每一個數組元素上都一個鏈表結構,當數據被Hash後,獲得數組下標,把數據放在對應下標元素的鏈表上。 Node[] table的初始化長度默認值是16,Load factor爲負載因子(默認值是0.75),threshold是HashMap所能容納的最大數據量的Node(鍵值對)個數。threshold = length * Load factor。也就是說,在數組定義好長度以後,負載因子越大,所能容納的鍵值對個數越多。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; // 步驟①:tab爲空則建立 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 步驟②:計算index,並對null作處理 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; // 步驟③:節點key存在,直接覆蓋value if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; // 步驟④:判斷該鏈爲紅黑樹 else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); // 步驟⑤:該鏈爲鏈表 else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //鏈表長度大於8轉換爲紅黑樹進行處理 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } // key已經存在直接覆蓋value if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 步驟⑥:超過最大容量 就擴容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { // 超過最大值就再也不擴充了,就只好隨你碰撞去吧 if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 沒超過最大值,就擴充爲原來的2倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 計算新的resize上限 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { // 把每一個bucket都移動到新的buckets中 for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; // 原索引 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } // 原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); // 原索引放到bucket裏 if (loTail != null) { loTail.next = null; newTab[j] = loHead; } // 原索引+oldCap放到bucket裏 if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }
1.若任意節點的左子樹不空,則左子樹上全部結點的值均小於它的根結點的值; 2.若任意節點的右子樹不空,則右子樹上全部結點的值均大於它的根結點的值; 3.任意節點的左、右子樹也分別爲二叉查找樹。 4.沒有鍵值相等的節點(no duplicate nodes)。
1.每一個結點要麼是紅的要麼是黑的。 2.根結點是黑的。 3.每一個葉結點(葉結點即指樹尾端NIL指針或NULL結點)都是黑的。 4.若是一個結點是紅的,那麼它的兩個兒子都是黑的。 5.對於任意結點而言,其到葉結點樹尾端NIL指針的每條路徑都包含相同數目的黑結點。
// 對於非公平鎖,會執行該方法: final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState();//獲取狀態變量 if (c == 0) {//代表沒有線程佔有該同步狀態 if (compareAndSetState(0, acquires)) {//以原子方式設置該同步狀態 setExclusiveOwnerThread(current);//該線程擁有該FairSync同步狀態 return true; } } else if (current == getExclusiveOwnerThread()) {//當前線程已經擁有該同步狀態 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc);//重複設置狀態變量(鎖的可重入特性) return true; } return false; } // 而對於公平鎖,該方法則是這樣: protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //先判斷該線程節點是不是隊列的頭結點 //是則以原子方式設置同步狀態,獲取鎖 //不然失敗返回 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {//重入 int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail; // 這個if分支實際上是一種優化:CAS操做失敗的話才進入enq中的循環。 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } //下面看看樂觀讀鎖案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //得到一個樂觀讀鎖 double currentX = x, currentY = y; //將兩個字段讀入本地局部變量 if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖後同時是否有其餘寫鎖發生? stamp = sl.readLock(); //若是沒有,咱們再次得到一個讀悲觀鎖 try { currentX = x; // 將兩個字段讀入本地局部變量 currentY = y; // 將兩個字段讀入本地局部變量 } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } //下面是悲觀讀鎖案例 void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { //循環,檢查當前狀態是否符合 long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉爲寫鎖 if (ws != 0L) { //這是確認轉爲寫鎖是否成功 stamp = ws; //若是成功 替換票據 x = newX; //進行狀態改變 y = newY; //進行狀態改變 break; } else { //若是不能成功轉換爲寫鎖 sl.unlockRead(stamp); //咱們顯式釋放讀鎖 stamp = sl.writeLock(); //顯式直接進行寫鎖 而後再經過循環再試 } } } finally { sl.unlock(stamp); //釋放讀鎖或寫鎖 } } }
/** * 存放Cell的hash表,大小爲2的冪。 */ transient volatile Cell[] cells; /** * 基礎值,沒有競爭時會使用(更新)這個值,同時作爲初始化競爭失敗的回退方案。 * 原子更新。 */ transient volatile long base; /** * 自旋鎖,經過CAS操做加鎖,用於保護建立或者擴展Cell表。 */ transient volatile int cellsBusy;
supplyAsync/runAsync -- 建立CompletableFuture對象;
whenComplete/whenCompleteAsync/exceptionally -- 計算完成或者拋出異常的時能夠執行特定的Action;
thenApply/thenApplyAsync -- 對數據進行一些處理或變換;
thenAccept/thenAcceptAsync -- 純消費,不返回新的計算值;
thenAcceptBoth/thenAcceptBothAsync/runAfterBoth -- 當兩個CompletionStage都正常完成計算的時候,就會執行提供的Action;
thenCompose/thenComposeAsync -- 這個Function的輸入是當前的CompletableFuture的計算值,返回結果將是一個新的CompletableFuture。 記住,thenCompose返回的對象並不一是函數fn返回的對象,若是原來的CompletableFuture尚未計算出來, 它就會生成一個新的組合後的CompletableFuture。能夠用來實現異步pipline; thenCombine/thenCombineAsync - 並行執行的,它們之間並無前後依賴順序,和thenAcceptBoth的區別在於有返回值; allOf/anyOf -- 全部的/其中一個CompletableFuture都執行完後執行計算