面試小結(jié)之并發(fā)篇

最近面試一些公司造壮,被問到的關(guān)于Java并發(fā)編程的問題,以及自己總結(jié)的回答绪商。

Java線程的狀態(tài)及如何轉(zhuǎn)換苛谷。

線程狀態(tài)及其轉(zhuǎn)換圖

多個線程之間如何協(xié)調(diào)?

  • wait()格郁、notify()腹殿、notifyAll():這三個方法用于協(xié)調(diào)多個線程對共享數(shù)據(jù)的存取独悴,所以必須在同步語句塊內(nèi)使用。wait方法要等待notify/notifyAll的線程釋放鎖后才能開始繼續(xù)往下執(zhí)行锣尉。
// 等待方
synchronized(lockObj){
    while(condition is false){
        lockObj.wait();
    }
    
    // do business
}

// 通知方
synchronized(lockObj){
    // change condition
    
    lockObj.notifyAll();
}

說說Java的線程池是如何實現(xiàn)的刻炒?

  • 創(chuàng)建線程要花費昂貴的資源和時間,如果任務(wù)來了才創(chuàng)建線程那么響應(yīng)時間會變長自沧,而且一個進(jìn)程能創(chuàng)建的線程數(shù)有限坟奥。為了避免這些問題,在程序啟動的時候就創(chuàng)建若干線程來響應(yīng)處理暂幼,它們被稱為線程池筏勒,里面的線程叫工作線程。
  • maximumPoolSize和corePoolSize的區(qū)別:這個概念很重要旺嬉,maximumPoolSize為線程池最大容量管行,也就是說線程池最多能起多少Worker。corePoolSize是核心線程池的大小邪媳,當(dāng)corePoolSize滿了時捐顷,同時workQueue full(ArrayBolckQueue是可能滿的) 那么此時允許新建Worker去處理workQueue中的Task,但是不能超過maximumPoolSize雨效。超過corePoolSize之外的線程會在空閑超時后終止迅涮。可以通過beforeExecute和afterExecute實現(xiàn)線程池的監(jiān)聽徽龟;
線程池
線程池處理流程
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

關(guān)于BlockingQueue和TransferQueue的異同叮姑。

  • TransferQueue繼承了BlockingQueue并擴(kuò)展了一些新方法。BlockingQueue是Java 5中加入的接口据悔,它是指這樣的一個隊列:當(dāng)生產(chǎn)者向隊列添加元素但隊列已滿時传透,生產(chǎn)者會被阻塞;當(dāng)消費者從隊列移除元素但隊列為空時极颓,消費者會被阻塞朱盐。
  • TransferQueue則更進(jìn)一步,生產(chǎn)者會一直阻塞直到所添加到隊列的元素被某一個消費者所消費(不僅僅是添加到隊列里就完事)菠隆,新添加的transfer方法用來實現(xiàn)這種約束兵琳。顧名思義,阻塞就是發(fā)生在元素從一個線程transfer到另一個線程的過程中骇径,它有效地實現(xiàn)了元素在線程之間的傳遞(以建立Java內(nèi)存模型中的happens-before關(guān)系的方式)躯肌。
  • TransferQueue還包括了其他的一些方法:兩個tryTransfer方法,一個是非阻塞的既峡,另一個帶有timeout參數(shù)設(shè)置超時時間的羡榴。還有兩個輔助方法hasWaitingConsumer()和getWaitingConsumerCount()。
  • TransferQueue相比SynchronousQueue用處更廣运敢、更好用校仑,因為你可以決定是使用BlockingQueue的方法(例如put方法)還是確保一次傳遞完成(即transfer方法)忠售。在隊列中已有元素的情況下,調(diào)用transfer方法迄沫,可以確保隊列中被傳遞元素之前的所有元素都能被處理稻扬。Doug Lea說從功能角度來講,LinkedTransferQueue實際上是ConcurrentLinkedQueue羊瘩、SynchronousQueue(公平模式)和LinkedBlockingQueue的超集泰佳。而且LinkedTransferQueue更好用,因為它不僅僅綜合了這幾個類的功能尘吗,同時也提供了更高效的實現(xiàn)逝她。

談?wù)凥ashMap的實現(xiàn)。

  • 從結(jié)構(gòu)實現(xiàn)來講睬捶,HashMap是數(shù)組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現(xiàn)的黔宛,如下如所示。
HashMap
從源碼可知擒贸,HashMap類中有一個非常重要的字段臀晃,就是 Node[] table,即哈希桶數(shù)組介劫;
Node是HashMap的一個內(nèi)部類徽惋,實現(xiàn)了Map.Entry接口,本質(zhì)是就是一個映射(鍵值對)座韵。
HashMap就是使用哈希表來存儲的险绘。為解決沖突,Java中HashMap采用了鏈地址法誉碴,簡單來說隆圆,就是數(shù)組加鏈表的結(jié)合。在每個數(shù)組元素上都一個鏈表結(jié)構(gòu)翔烁,當(dāng)數(shù)據(jù)被Hash后,得到數(shù)組下標(biāo)旨涝,把數(shù)據(jù)放在對應(yīng)下標(biāo)元素的鏈表上蹬屹。
Node[] table的初始化長度默認(rèn)值是16,Load factor為負(fù)載因子(默認(rèn)值是0.75)白华,threshold是HashMap所能容納的最大數(shù)據(jù)量的Node(鍵值對)個數(shù)慨默。threshold = length * Load factor。也就是說弧腥,在數(shù)組定義好長度之后厦取,負(fù)載因子越大,所能容納的鍵值對個數(shù)越多管搪。
  • 確定哈希桶數(shù)組索引位置:取key的hashCode值虾攻、高位運算(通過hashCode()的高16位異或低16位實現(xiàn)的)铡买、取模運算。
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
數(shù)組索引位置
  • HashMap的put方法執(zhí)行過程可以通過下圖來理解霎箍。
put方法執(zhí)行過程
    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為空則創(chuàng)建
        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;
            // 步驟③:節(jié)點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轉(zhuǎn)換為紅黑樹進(jìn)行處理
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    // key已經(jīng)存在直接覆蓋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;
        // 步驟⑥:超過最大容量 就擴(kuò)容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
  • 擴(kuò)容機(jī)制:擴(kuò)容(resize)就是重新計算容量漂坏,向HashMap對象里不停的添加元素景埃,而HashMap對象內(nèi)部的數(shù)組無法裝載更多的元素時,對象就需要擴(kuò)大數(shù)組的長度顶别,以便能裝入更多的元素谷徙。當(dāng)然Java里的數(shù)組是無法自動擴(kuò)容的,方法是使用一個新的數(shù)組代替已有的容量小的數(shù)組驯绎。在擴(kuò)充HashMap的時候完慧,不需要像JDK1.7的實現(xiàn)那樣重新計算hash,只需要看看原來的hash值新增的那個bit是1還是0就好了条篷,是0的話索引沒變骗随,是1的話索引變成“原索引+oldCap”。這個設(shè)計確實非常的巧妙赴叹,既省去了重新計算hash值的時間鸿染,而且同時,由于新增的1bit是0還是1可以認(rèn)為是隨機(jī)的乞巧,因此resize的過程涨椒,均勻的把之前的沖突的節(jié)點分散到新的bucket了。
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) {
            // 超過最大值就不再擴(kuò)充了绽媒,就只好隨你碰撞去吧
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // 沒超過最大值蚕冬,就擴(kuò)充為原來的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;
    }

談?wù)劸€程安全的ConcurrentHashMap的實現(xiàn)原理。

  • ConcurrentHashMap在jdk1.8中主要做了2方面的改進(jìn):改進(jìn)一是取消segments字段是辕,直接采用transient volatile HashEntry<K,V>[] table保存數(shù)據(jù)囤热,采用table數(shù)組元素作為鎖,從而實現(xiàn)了對每一行數(shù)據(jù)進(jìn)行加鎖获三,進(jìn)一步減少并發(fā)沖突的概率旁蔼;改進(jìn)二是將原先table數(shù)組+單向鏈表的數(shù)據(jù)結(jié)構(gòu),變更為table數(shù)組+單向鏈表+紅黑樹的結(jié)構(gòu)疙教,對于hash表來說棺聊,最核心的能力在于將key hash之后能均勻的分布在數(shù)組中,如果hash之后散列的很均勻贞谓,那么table數(shù)組中的每個隊列長度主要為0或者1限佩。但實際情況并非總是如此理想,雖然ConcurrentHashMap類默認(rèn)的加載因子為0.75,但是在數(shù)據(jù)量過大或者運氣不佳的情況下祟同,還是會存在一些隊列長度過長的情況作喘,如果還是采用單向列表方式,那么查詢某個節(jié)點的時間復(fù)雜度為O(n)耐亏;因此徊都,對于個數(shù)超過8(默認(rèn)值)的列表,jdk1.8中采用了紅黑樹的結(jié)構(gòu)广辰,那么查詢的時間復(fù)雜度可以降低到O(logN)暇矫,可以改進(jìn)性能。
  • TreeNode類:樹節(jié)點類择吊,另外一個核心的數(shù)據(jù)結(jié)構(gòu)李根。當(dāng)鏈表長度過長的時候,會轉(zhuǎn)換為TreeNode几睛。但是與HashMap不相同的是房轿,它并不是直接轉(zhuǎn)換為紅黑樹,而是把這些結(jié)點包裝成TreeNode放在TreeBin對象中所森,由TreeBin完成對紅黑樹的包裝囱持。而且TreeNode在ConcurrentHashMap繼承自Node類,而并非HashMap中的集成自LinkedHashMap.Entry焕济。
  • 二叉查找樹纷妆,也稱有序二叉樹(ordered binary tree),是指一棵空樹或者具有下列性質(zhì)的二叉樹:
1.若任意節(jié)點的左子樹不空晴弃,則左子樹上所有結(jié)點的值均小于它的根結(jié)點的值掩幢;
2.若任意節(jié)點的右子樹不空,則右子樹上所有結(jié)點的值均大于它的根結(jié)點的值上鞠;
3.任意節(jié)點的左际邻、右子樹也分別為二叉查找樹。
4.沒有鍵值相等的節(jié)點(no duplicate nodes)芍阎。
  • 紅黑樹雖然本質(zhì)上是一棵二叉查找樹世曾,但它在二叉查找樹的基礎(chǔ)上增加了著色和相關(guān)的性質(zhì)使得紅黑樹相對平衡,從而保證了紅黑樹的查找谴咸、插入度硝、刪除的時間復(fù)雜度最壞為O(log n)。但它是如何保證一棵n個結(jié)點的紅黑樹的高度始終保持在logn的呢寿冕?這就引出了紅黑樹的5個性質(zhì):
1.每個結(jié)點要么是紅的要么是黑的。  
2.根結(jié)點是黑的椒袍。  
3.每個葉結(jié)點(葉結(jié)點即指樹尾端NIL指針或NULL結(jié)點)都是黑的驼唱。  
4.如果一個結(jié)點是紅的,那么它的兩個兒子都是黑的驹暑。  
5.對于任意結(jié)點而言玫恳,其到葉結(jié)點樹尾端NIL指針的每條路徑都包含相同數(shù)目的黑結(jié)點辨赐。

什么是一致性哈希?

  • 環(huán)形Hash空間:按照常用的hash算法來將對應(yīng)的key哈希到一個具有232次方個桶的空間中京办,即0~(232)-1的數(shù)字空間中∠菩颍現(xiàn)在我們可以將這些數(shù)字頭尾相連,想象成一個閉合的環(huán)形惭婿;
  • 把數(shù)據(jù)通過一定的hash算法處理后映射到環(huán)上不恭;
  • 將機(jī)器通過hash算法映射到環(huán)上(一般情況下對機(jī)器的hash計算是采用機(jī)器的IP或者機(jī)器唯一的別名作為輸入值),然后以順時針的方向計算财饥,將所有對象存儲到離自己最近的機(jī)器中换吧;
  • 機(jī)器的刪除與添加:普通hash求余算法最為不妥的地方就是在有機(jī)器的添加或者刪除之后會照成大量的對象存儲位置失效,這樣就大大的不滿足單調(diào)性了钥星。通過對節(jié)點的添加和刪除的分析沾瓦,一致性哈希算法在保持了單調(diào)性的同時,還是數(shù)據(jù)的遷移達(dá)到了最小谦炒,這樣的算法對分布式集群來說是非常合適的贯莺,避免了大量數(shù)據(jù)遷移,減小了服務(wù)器的的壓力宁改。
  • 平衡性:在一致性哈希算法中缕探,為了盡可能的滿足平衡性,其引入了虛擬節(jié)點透且。它實際上是節(jié)點在hash空間的復(fù)制品撕蔼,一實際個節(jié)點對應(yīng)了若干個“虛擬節(jié)點”,這個對應(yīng)個數(shù)也成為“復(fù)制個數(shù)”秽誊,“虛擬節(jié)點”在hash空間中以hash值排列鲸沮。
一致性哈希

Java有哪些實現(xiàn)鎖的方式?

  • synchronized同步鎖:它無法中斷一個正在等候獲得鎖的線程锅论,也無法通過投票得到鎖讼溺,如果不想等下去,也就沒法得到鎖最易。但除非對鎖的某個高級特性有明確的需要怒坯,或者有明確的證據(jù)表明在特定情況下,同步已經(jīng)成為瓶頸藻懒,否則還是應(yīng)當(dāng)繼續(xù)使用synchronized剔猿。
  • volatile是比synchronized更輕量,因為它沒有上下文切換嬉荆;其實現(xiàn)是通過lock指令將緩存行數(shù)據(jù)寫到系統(tǒng)內(nèi)存且讓其他緩存數(shù)據(jù)無效归敬;
  • ReentrantLock可以支持公平鎖,當(dāng)然公平鎖性能會有影響,默認(rèn)為非公平的汪茧;
// 對于非公平鎖椅亚,會執(zhí)行該方法:
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();//獲取狀態(tài)變量
    if (c == 0) {//表明沒有線程占有該同步狀態(tài)
        if (compareAndSetState(0, acquires)) {//以原子方式設(shè)置該同步狀態(tài)
            setExclusiveOwnerThread(current);//該線程擁有該FairSync同步狀態(tài)
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {//當(dāng)前線程已經(jīng)擁有該同步狀態(tài)
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);//重復(fù)設(shè)置狀態(tài)變量(鎖的可重入特性)
        return true;
    }
    
    return false;
}

// 而對于公平鎖,該方法則是這樣:
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //先判斷該線程節(jié)點是否是隊列的頭結(jié)點
        //是則以原子方式設(shè)置同步狀態(tài)舱污,獲取鎖
        //否則失敗返回
        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;
}
  • AQS管理的FIFO等待隊列呀舔,獲取鎖狀態(tài)失敗的線程會被放入該隊列,等待再次嘗試獲取鎖扩灯。而state成員變量媚赖,代表著鎖的同步狀態(tài),一個線程成功獲得鎖驴剔,這個行為的實質(zhì)就是該線程成功的設(shè)置了state變量的狀態(tài)省古。
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分支其實是一種優(yōu)化:CAS操作失敗的話才進(jìn)入enq中的循環(huán)。
    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);
    }
}
  • ReentrantReadWriteLock對重入鎖再進(jìn)一步分離為讀鎖和寫鎖丧失,在讀多寫少的場景下能顯著提升性能豺妓。
  • ReadLock可以被多個線程持有并且在作用時排斥任何的WriteLock,而WriteLock則是完全的互斥布讹。
  • 寫線程獲取寫入鎖后可以獲取讀取鎖琳拭,然后釋放寫入鎖,這樣就從寫入鎖變成了讀取鎖描验,從而實現(xiàn)鎖降級的特性白嘁。讀取鎖是不能直接升級為寫入鎖的。因為獲取一個寫入鎖需要釋放所有讀取鎖膘流,所以如果有兩個讀取鎖視圖獲取寫入鎖而都不釋放讀取鎖時就會發(fā)生死鎖絮缅。
  • 如果讀取執(zhí)行情況很多,寫入很少的情況下呼股,使用ReentrantReadWriteLock可能會使寫入線程遭遇饑餓問題耕魄,也就是寫入線程吃吃無法競爭到鎖定而一直處于等待狀態(tài)。
  • StampedLock控制鎖有三種模式(寫彭谁,讀吸奴,樂觀讀),一個StampedLock狀態(tài)是由版本和模式兩個部分組成缠局,鎖獲取方法返回一個數(shù)字作為票據(jù)stamp则奥,它用相應(yīng)的鎖狀態(tài)表示并控制訪問,數(shù)字0表示沒有寫鎖被授權(quán)訪問狭园。在讀鎖上分為悲觀鎖和樂觀鎖读处。所謂的樂觀讀模式,也就是若讀的操作很多唱矛,寫的操作很少的情況下档泽,你可以樂觀地認(rèn)為俊戳,寫入與讀取同時發(fā)生幾率很少,因此不悲觀地使用完全的讀取鎖定馆匿,程序可以查看讀取資料之后,是否遭到寫入執(zhí)行的變更燥滑,再采取后續(xù)的措施(重新讀取變更信息渐北,或者拋出異常) ,這一個小小改進(jìn)铭拧,可大幅度提高程序的吞吐量赃蛛!
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)) { //檢查發(fā)出樂觀讀鎖后同時是否有其他寫鎖發(fā)生?
        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) { //循環(huán)呕臂,檢查當(dāng)前狀態(tài)是否符合
         long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉(zhuǎn)為寫鎖
         if (ws != 0L) { //這是確認(rèn)轉(zhuǎn)為寫鎖是否成功
           stamp = ws; //如果成功 替換票據(jù)
           x = newX; //進(jìn)行狀態(tài)改變
           y = newY; //進(jìn)行狀態(tài)改變
           break;
         }
         else { //如果不能成功轉(zhuǎn)換為寫鎖
           sl.unlockRead(stamp); //我們顯式釋放讀鎖
           stamp = sl.writeLock(); //顯式直接進(jìn)行寫鎖 然后再通過循環(huán)再試
         }
       }
     } finally {
       sl.unlock(stamp); //釋放讀鎖或?qū)戞i
     }
   }
 }

AtomicLong、LongAdder和LongAccumulator的實現(xiàn)有何不同肪跋?

  • AtomicLong是Java 5引入的基于CAS的無鎖的操作長整形值的工具類歧蒋;
  • LongAdder是Java 8提供的累加器,基于Striped64實現(xiàn)州既。它常用于狀態(tài)采集谜洽、統(tǒng)計等場景。AtomicLong也可以用于這種場景吴叶,但在線程競爭激烈的情況下阐虚,LongAdder要比AtomicLong擁有更高的吞吐量,但會耗費更多的內(nèi)存空間蚌卤。
  • Striped64的設(shè)計核心思路就是通過內(nèi)部的分散計算來避免競爭(比如多線程CAS操作時的競爭)实束。Striped64內(nèi)部包含一個基礎(chǔ)值和一個單元哈希表。沒有競爭的情況下逊彭,要累加的數(shù)會累加到這個基礎(chǔ)值上咸灿;如果有競爭的話,會將要累加的數(shù)累加到單元哈希表中的某個單元里面诫龙。所以整個Striped64的值包括基礎(chǔ)值和單元哈希表中所有單元的值的總和析显。
/** 
 * 存放Cell的hash表,大小為2的冪签赃。 
 */  
transient volatile Cell[] cells;  

/** 
 * 基礎(chǔ)值谷异,沒有競爭時會使用(更新)這個值,同時做為初始化競爭失敗的回退方案锦聊。 
 * 原子更新歹嘹。 
 */  
transient volatile long base;  

/** 
 * 自旋鎖,通過CAS操作加鎖孔庭,用于保護(hù)創(chuàng)建或者擴(kuò)展Cell表尺上。 
 */  
transient volatile int cellsBusy;  
  • LongAccumulator和LongAdder類似材蛛,也基于Striped64實現(xiàn)。但要比LongAdder更加靈活(要傳入一個函數(shù)接口)怎抛,LongAdder相當(dāng)于是LongAccumulator的一種特例卑吭。

CompletableFuture對比Future有哪些改進(jìn),怎么用马绝?

  • Future對象代表一個尚未完成異步操作的結(jié)果豆赏。從Java 5以來,JUC包一直提供著最基本的Future富稻,不過它太雞肋了掷邦,除了get、cancel椭赋、isDone和isCancelled方法之外就沒有其他的操作了抚岗,對于結(jié)果的獲取很不方便,只能通過阻塞或者輪詢的方式得到任務(wù)的結(jié)果哪怔。阻塞的方式顯然和我們的異步編程的初衷相違背宣蔚,輪詢的方式又會耗費無謂的CPU資源,而且也不能及時地得到計算結(jié)果蔓涧,這樣很不方便件已。
  • 好在Java 8中引入了具有函數(shù)式風(fēng)格的CompletableFuture,支持一系列的函數(shù)式的組合元暴、運算操作篷扩,非常方便,可以寫出函數(shù)式風(fēng)格的代碼而擺脫callback hell茉盏。
  • 主動完成計算:CompletableFuture類實現(xiàn)了CompletionStage和Future接口鉴未,所以你還是可以像以前一樣通過阻塞或者輪詢的方式獲得結(jié)果,盡管這種方式不推薦使用鸠姨。
  • 主要的API如下所示:
supplyAsync/runAsync -- 創(chuàng)建CompletableFuture對象铜秆;
whenComplete/whenCompleteAsync/exceptionally -- 計算完成或者拋出異常的時可以執(zhí)行特定的Action债沮;
thenApply/thenApplyAsync -- 對數(shù)據(jù)進(jìn)行一些處理或變換畴椰;
thenAccept/thenAcceptAsync -- 純消費上祈,不返回新的計算值靠胜;
thenAcceptBoth/thenAcceptBothAsync/runAfterBoth -- 當(dāng)兩個CompletionStage都正常完成計算的時候,就會執(zhí)行提供的Action呻右;
thenCompose/thenComposeAsync -- 這個Function的輸入是當(dāng)前的CompletableFuture的計算值傲隶,返回結(jié)果將是一個新的CompletableFuture英上。
記住祟峦,thenCompose返回的對象并不一是函數(shù)fn返回的對象罚斗,如果原來的CompletableFuture還沒有計算出來,
它就會生成一個新的組合后的CompletableFuture宅楞≌胱耍可以用來實現(xiàn)異步pipline袱吆;
thenCombine/thenCombineAsync - 并行執(zhí)行的,它們之間并沒有先后依賴順序距淫,和thenAcceptBoth的區(qū)別在于有返回值绞绒;
allOf/anyOf -- 所有的/其中一個CompletableFuture都執(zhí)行完后執(zhí)行計算;

其他面試小結(jié)

掃一掃 關(guān)注我的微信公眾號
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末榕暇,一起剝皮案震驚了整個濱河市处铛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拐揭,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕塑,死亡現(xiàn)場離奇詭異堂污,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)龄砰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門盟猖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人换棚,你說我怎么就攤上這事式镐。” “怎么了固蚤?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵娘汞,是天一觀的道長。 經(jīng)常有香客問我夕玩,道長你弦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任燎孟,我火速辦了婚禮禽作,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揩页。我一直安慰自己旷偿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布爆侣。 她就那樣靜靜地躺著萍程,像睡著了一般。 火紅的嫁衣襯著肌膚如雪累提。 梳的紋絲不亂的頭發(fā)上尘喝,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機(jī)與錄音斋陪,去河邊找鬼朽褪。 笑死置吓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缔赠。 我是一名探鬼主播衍锚,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嗤堰!你這毒婦竟也來了戴质?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤踢匣,失蹤者是張志新(化名)和其女友劉穎告匠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體离唬,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡后专,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了输莺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片戚哎。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嫂用,靈堂內(nèi)的尸體忽然破棺而出型凳,到底是詐尸還是另有隱情,我是刑警寧澤嘱函,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布甘畅,位于F島的核電站,受9級特大地震影響实夹,放射性物質(zhì)發(fā)生泄漏橄浓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一荸实、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缴淋,春花似錦准给、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钟沛,卻和暖如春畔规,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恨统。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工叁扫, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留三妈,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓莫绣,卻偏偏與公主長得像畴蒲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子对室,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實習(xí)到現(xiàn)在模燥,面了一些公司,掛了不少掩宜,但最終還是拿到小米蔫骂、百度、阿里牺汤、京東纠吴、新浪、CVTE慧瘤、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,246評論 11 349
  • Java8張圖 11、字符串不變性 12固该、equals()方法锅减、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • Java SE 基礎(chǔ): 封裝伐坏、繼承怔匣、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務(wù))結(jié)合為一個獨立的整體,并盡...
    Jayden_Cao閱讀 2,109評論 0 8
  • 在經(jīng)過一次沒有準(zhǔn)備的面試后桦沉,發(fā)現(xiàn)自己雖然寫了兩年的android代碼每瞒,基礎(chǔ)知識卻忘的差不多了。這是程序員的大忌纯露,沒...
    猿來如癡閱讀 2,841評論 3 10
  • 7月24日 星期一 天氣:陰 跟閨女坐在一起剿骨,閨女經(jīng)常說:‘媽媽,你好矮呀埠褪!’我不以為然浓利,今天閨女...
    官越媽媽閱讀 256評論 0 2