每日十道面試題(五)

每日十道面試題(五)

這兩天的面試題都很硬核阿,掌握即增添實力,有期待哪方面的面試題可以下方評論,儘量安排
後期還會規劃算法方面的面試題

1.volatile有什麼用?講一下它的特點

它能保證數據可見性,但不保證原子性,這一點在JMM上面有很好的體現,工作內存中數據的共享就需要數據可見,但是不保證原子性,這個需要加鎖或者使用原子類,原子引用
2.它在內存上給數據加上兩個內存屏障,防止指令重排

2.什麼是樂觀鎖,什麼是悲觀鎖,他們之間有什麼區別嗎

悲觀鎖: 在操作數據之前先上鎖,比如數據庫操作的讀寫鎖,for update排他鎖
樂觀鎖: 先操作數據,在提交數據的時候纔去檢測數據是否衝突,能否提交,比如時間戳和version
樂觀鎖最經典就是CAS了,比較並交換
樂觀鎖並未真正加鎖,效率高。一旦鎖的粒度掌握不好,更新失敗的概率就會比較高,容易發生業務失敗。
悲觀鎖依賴數據庫鎖,效率低。更新失敗的概率比較低。

3.你說你知道樂觀鎖,那CAS你知道嗎?

CAS就是compareAndSet,比較並交換,爲了使資源同步,保證我插入的數據是最新的值,要先比較一下原來的值,比如庫存是否正確,這裏就需要用到樂觀鎖,
但是這裏還有個問題就是ABA問題 如果別人改了庫存又加上去,等於沒碰過,那你又如何知道別人動過
再交換的時候比較一下version字段,能保證查看之前version誰改動了

4.生產者消費者模式知道嗎?能不能代碼實現

生產者和消費者就是典型的線程同步通信的案例,可以通過synchronized wait和notify或者lock,unlock+Condition+singal精準喚醒,還可以使用blockingQueue阻塞隊列也可以,而保證原子性的話,利用原子類AutomicInteger

package Old;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class BlockQueue {
    public static void main(String[] args) {
        // TODO 自動生成的方法存根
        Data6 data=new Data6(new ArrayBlockingQueue<>(3));
        new Thread(()-> {
            try {
                System.out.println("手機店生產啓動");
                data.product();
            } catch (InterruptedException e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
        },"手機店").start();
        new Thread(()->{
            System.out.println("顧客開始消費");
            try {
                data.consumer();
            } catch (InterruptedException e) {
                // TODO 自動生成的 catch 塊
                e.printStackTrace();
            }
        },"顧客").start();

        try {
            TimeUnit.SECONDS.sleep(6);
            data.stop();
        } catch (InterruptedException e) {
            // TODO 自動生成的 catch 塊
            e.printStackTrace();
        }
        System.out.println("6秒後活動結束大老闆叫停了");
    }
}
class Data6{
    private volatile boolean num=true;
    private AtomicInteger atomicInteger=new AtomicInteger();
    BlockingQueue<String> blockingQueue=null;
    Data6( BlockingQueue<String> blockingQueue){
        this.blockingQueue=blockingQueue;
        System.out.println("傳進來的隊列類型是"+blockingQueue.getClass().getName());
    }
    public  void product() throws InterruptedException  {
        String phonenum="";
        boolean flag;
        while(num) {//不用if防止虛假喚醒
            phonenum=atomicInteger.incrementAndGet()+"";
            flag=blockingQueue.offer(phonenum,2,TimeUnit.SECONDS);//2秒添加一個
            if(flag) {
                System.out.println(Thread.currentThread().getName()+"生產了"+phonenum+"個手機");
            }else {
                System.out.println(Thread.currentThread().getName()+"生產了"+phonenum+"個手機失敗");
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName()+"生產廠家停止生產手機");
    }
    public  void consumer() throws InterruptedException {
        String result=null;
        while(num) {//不用if防止虛假喚醒
            result=blockingQueue.poll(2,TimeUnit.SECONDS);//2秒添加一個
            if(null==result||result.equalsIgnoreCase("")) {
                num=false;
                System.out.println(Thread.currentThread().getName()+"拿不到手機了,不消費了");
                return;
            }
            System.out.println(Thread.currentThread().getName()+"消費了"+result+"個手機");
        }
    }
    public  void stop() {
        num=false;
    }
}

5.說一說Linux有哪些常用命令?

ps -ef|grep xxxx 看xx是否在運行
losf -i:888 看端口是否在運行
vim 文件 進入並查看文件
ll 查看目錄下文件的屬性
pwd 查看當前目錄
top 可以查看cpu資源
tar -zcvf tar.gz 安裝解壓鏡像
cat file1 從第一個字節開始正向查看文件的內容
ifconfig 查看ip
ps -ef | grep firefox或者netstat -p |grep firefox 或者sof -i:4723 查找某個進程
終止進程
kill -s 9 pid
pkill -9 firefox

6. 1.7的ConcurrentHashMap和1.8的原理,之間有什麼區別呢

ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,只要多個修改操作發生在不同的段上,它們就可以併發進行。而且Entry對象的成員變量都是volatile的,保證了可見,那麼高併發下可以獲取到最新的數據,get和put原理和hashmap一樣
JDK1.8的實現已經摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,併發控制使用Synchronized和CAS,如果沒有hash衝突就直接CAS插入,如果鏈表的長度大於8時就會進行紅黑樹的轉換

JDK1.8取消了segment數組,直接用table保存數據,鎖的粒度更小,減少併發衝突的概率。
JDK1.8存儲數據時採用了鏈表+紅黑樹的形式,純鏈表的形式時間複雜度爲O(n),紅黑樹則爲O(logn),性能提升很大。什麼時候鏈表轉紅黑樹?當key值相等的元素形成的鏈表中元素個數超過8個的時候。
JDK1.8的實現降低鎖的粒度,JDK1.7版本鎖的粒度是基於Segment的,包含多個HashEntry,而JDK1.8鎖的粒度就是HashEntry(首節點)
JDK1.8版本的數據結構變得更加簡單,使得操作也更加清晰流暢,因爲已經使用synchronized來進行同步,所以不需要分段鎖的概念,也就不需要Segment這種數據結構了,由於粒度的降低,實現的複雜度也增加了
JDK1.8使用紅黑樹來優化鏈表,基於長度很長的鏈表的遍歷是一個很漫長的過程,而紅黑樹的遍歷效率是很快的,代替一定閾值的鏈表,這樣形成一個最佳拍檔
JDK1.8爲什麼使用內置鎖synchronized來代替重入鎖ReentrantLock,我覺得有以下幾點
因爲粒度降低了,在相對而言的低粒度加鎖方式,synchronized並不比ReentrantLock差,在粗粒度加鎖中ReentrantLock可能通過Condition來控制各個低粒度的邊界,更加的靈活,而在低粒度中,Condition的優勢就沒有了
JVM的開發團隊從來都沒有放棄synchronized,而且基於JVM的synchronized優化空間更大,使用內嵌的關鍵字比使用API更加自然
在大量的數據操作下,對於JVM的內存壓力,基於API的ReentrantLock會開銷更多的內存,雖然不是瓶頸,但是也是一個選擇依據

7.爲什麼重寫了hashCode還要重寫equals方法

這個我們經常結合hashmap來講,
首先,hashmap根據hashcode後續操作算出數組下標的位置,然後再根據equals方法判斷鏈表上是否有同一個key值,有的話我們put就直接更新,沒有就插入
上面流程知道了之後,你就發現了,如果不重寫equals那麼鏈表上可能就會存在兩個值一模一樣的key,那這樣我們get的時候,就造成了數據的錯誤和丟失,並不能保證對象的一致性
反之如果只重寫equals(),也不行,因爲如果hashcode返回都一樣,而對象地址一樣那麼如果用eqals就會只看對象地址而不看hashcode,那麼map中存值就會發生哈希碰撞,根本不能保證數據一致性
所以兩者只要一個重寫,兩個都要重寫

8.如何有效減少哈希碰撞

Hash碰撞衝突,hash值如果相等了而本身是兩個不同對象,那麼就會產生哈希碰撞

  1. 重新計算哈希值,比如擴容的時候會rehash();

  2. 鏈地址法,如果哈希地址相同,根據判斷的值和地址,然後以鏈表或者紅黑樹的形式橫向擴展,

  3. 開放地址法,每次如果碰撞了,那麼把地址往下移然後再算hash直到不碰撞就存入值

9. == 和 equals的區別

  1. 前者是如果是基本類型的比較的話,就是值的比較,如果是對象的比較那麼就是地址比較了

  2. equals是比較兩個對象的內容是否相等,一般會和hashcode方法一起判斷是否爲同一個對象

  3. 一般我們基本類型都用==,對象的字面值比較用equals

10.抽象類和接口的特點,以及他們的區別?

  1. 抽象類主要用來抽象類別,接口主要用來抽象功能。

  2. 抽象類可以有具體的方法和屬性,接口只能有抽象方法和不可變常量。

  3. 接口中聲明的變量默認都是final的,成員函數默認是public的,抽象類的可以很多關鍵字修飾

  4. 抽象類要被子類繼承,接口要被類實現。類可以實現很多個接口,但是隻能繼承一個抽象類

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章