【JAVA面試】java面試題整理(4)

版權聲明:轉載請註明 https://blog.csdn.net/qq_33591903/article/details/83473779

                                           java面試題整理(4)

JAVA常考點4css

文件夾html

一、 Set集合怎樣保證不反覆 1java

二、Java中Integer型和int型的差異 3mysql

三、接口可以繼承接口嗎?抽象類可以繼承接口嗎? 4面試

四、數據庫索引的做用 4算法

五、怎樣改動數據庫中的字段類型 4sql

六、having的做用 5數據庫

七、高速排序與歸併排序的差異 5數組

八、final關鍵詞做用有哪些? 5安全

九、servlet裏面有哪些方法?說一說get和post方法的差異 5

十、線性表的的順序存儲與鏈式存儲的特色 6

十一、具體解釋synchronized與Lock的差異與使用 6

十二、悲觀鎖。樂觀鎖,行鎖。表鎖,頁鎖,共享鎖。排他鎖 17

 

 

 

 

  1. Set集合怎樣保證不反覆

弄清怎麼個邏輯達到元素不反覆的,源代碼先上

HashSet 類中的add()方法:

 

public boolean add(E e) {

    return map.put(e, PRESENT)==null;

  }

 

類中map和PARENT的定義:

 

private transient HashMap<E,Object> map;

 // Dummy value to associate with an Object in the backing Map用來匹配Map中後面的對象的一個虛擬值

private static final Object PRESENT = new Object();

 

put()方法:

 

 public V put(K key, V value) {

        if (key == null)

            return putForNullKey(value);

        int hash = hash(key.hashCode());

        int i = indexFor(hash, table.length);

        for (Entry<K,V> e = table[i]; e != null; e = e.next) {

            Object k;

            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

                 。V oldValue = e.value;

                 。e.value = value;

                 。e.recordAccess(this);

                 return oldValue;

            }

        }

        modCount++;

        addEntry(hash, key, value, i);

        return null;

    }

 

 

可以看到for循環中,遍歷table中的元素,

假設hash碼值不一樣樣。說明是一個新元素,存。

 

假設沒有元素和傳入對象(也就是add的元素)的hash值相等,那麼就以爲這個元素在table中不存在,將其加入進table;

 

假設hash碼值一樣,且equles推斷相等,說明元素已經存在,不存;

 

假設hash碼值一樣,且equles推斷不相等。說明元素不存在,存;

 

假設有元素和傳入對象的hash值相等,那麼,繼續進行equles()推斷,假設仍然相等,那麼就以爲傳入元素已經存在,再也不加入。結束,不然仍然加入;

 

可見hashcode()和equles()在此顯得很是關鍵了,如下就來解讀一下hashcode和equles:

 

首先要明白:僅僅經過hash碼值來推斷兩個對象時否一樣合適嗎?答案是不合適的,因爲有可能兩個不一樣的對象的hash碼值一樣;

什麼是hash碼值?

在java中存在一種hash表結構,它經過一個算法,計算出的結果就是hash碼值;這個算法叫hash算法。

hash算法是怎麼計算的呢?

是經過對象中的成員來計算出來的結果;

假設成員變量是基本數據類型的值。 那麼用這個值 直接參與計算;

假設成員變量是引用數據類型的值,那麼獲取到這個成員變量的哈希碼值後。再參數計算

 

如:新建一個Person對象。重寫hashCode方法

 

public int hashCode() {

        final int prime = 31;

        int result = 1;

        result = prime * result + age;

        result = prime * result + ((name == null) ?

0 : name.hashCode());

        return result;

    }

 

可以看出,Person對象內兩個參數name,age。hash碼值是這二者計算後的記過,那麼全然有可能兩個對象name。age都不一樣,hash碼值一樣;

如下看下equles()方法:

 

public boolean equals(Object obj) {

        if (this == obj)

            return true;

        if (obj == null)

            return false;

        if (getClass() != obj.getClass())

            return false;

        Person other = (Person) obj;

        if (age != other.age)

            return false;

        if (name == null) {

            if (other.name != null)

                return false;

        } else if (!name.equals(other.name))

            return false;

        return true;

    }

 

equles方法內部是分別對name,age進行推斷,是否相等。

 

綜合上述,在向hashSet中add()元素時。推斷元素是否存在的根據,不只僅是hash碼值就可以肯定的,同一時候還要結合equles方法。

 

 

 

二、Java中Integer型和int型的差異

 

    a.Java 中的數據類型分爲基本數據類型和引用數據類型。int是基本數據類型,直接存放值。而而Integer是對象。用一個引用指向這個對象。Ingeter是int的包裝類。int的初值爲0。Ingeter的初值爲null。

 

    b.初始化 

 

int i =1。

Integer i= new Integer(1)。

  有了本身主動裝箱和拆箱。使得對Integer類也可以使用:

Integer i= 100;

  實際上上面這個代碼調用了:

 

Integer i = Integer.valueOf(100);

    c.Integer是int的封裝類,int和Integer都可以表示某一個數值,int和Integer不可以互用,因爲他們兩種不一樣的數據類型。

 

三、接口可以繼承接口嗎?抽象類可以繼承接口嗎?

  接口不只可以繼承接口,而且一個接口可以繼承多個接口,接口是特殊的抽象類。

  抽象類不可以繼承接口。但可以實現接口。抽象類可以繼承實體類,但是不能繼承接口。

 

四、數據庫索引的做用

 

長處:建立索引主要可以大大提升系統性能,主要優點有如下5點:

 

經過建立惟一性索引,可以保證數據庫表中每一行數據的惟一性。

可以大大加快 數據的檢索速度,這也是建立索引的最基本的緣由。

可以加速表和表之間的鏈接,特別是在實現數據的參考完整性方面特別有意義。

在使用分組和排序 子句進行數據檢索時,一樣可以顯著下降查詢中分組和排序的時間。

經過使用索引。可以在查詢的過程當中。使用優化隱藏器,提升系統的性能。

缺點:

 

建立索引和維護索引要耗費時間。這樣的時間隨着數據 量的添加而添加。 

索引需要佔物理空間,除了數據表佔數據空間以外,每一個索引還要佔必定的物理空間,假設要創建聚簇索引,那麼需要的空間就會更大。

 

當對錶中的數據進行添加、刪除和改動的時候,索引也要動態的維護。這樣就下降了數據的維護速度。

索引主要有兩個特徵:惟一性索引和複合索引。

 

五、怎樣改動數據庫中的字段類型

 

  ALTER TABLE 表名  MODIFY COLUMN 字段名 字段類型定義;(注意不是update)

 

六、having的做用

 

  HAVING 就像WHERE條件同樣。按指定要求來取數據集。

僅僅只是WHERE通常數據查詢來指定條件,HAVING是用在GROUP BY 分組來指定條件。

HAVING 子句運作起來很是象 WHERE 子句。 僅僅用於對那些知足 HAVING 子句裏面給出的條件的組進行計算。

事實上,WHERE 在分組和彙集以前過濾掉咱們不需要的輸入行, 而 HAVING 在 GROUP 以後那些不需要的組. 所以,WHERE 沒法使用一個彙集函數的結果. 而另外一方面,咱們也沒有理由寫一個不涉及彙集函數的 HAVING. 假設你的條件不包括彙集。那麼你也可以把它寫在 WHERE 裏面。 這樣就可以避免對那些你準備拋棄的行進行的彙集運算.

 

七、高速排序與歸併排序的差異

 

  歸併排序:簡單來講就是先將數組不斷細分紅最小的單位,而後每一個單位分別排序,排序完成後合併,反覆以上過程最後就可以獲得排序結果。

複雜度:O(nlogn)   妥當

  高速排序:簡單來講就是先選定一個基準元素,而後以該基準元素劃分數組,再在被劃分的部分反覆以上過程,最後可以獲得排序結果。

           複雜度:O(nlogn)   不穩定

二者都是用分治法的思想。只是最後歸併排序的合併操做比高速排序的要繁瑣。

 

八、final關鍵詞做用有哪些?

 

  final修飾類,類不能被繼承

 

  final修飾方法。方法不能被覆寫

 

  final修飾的變量初始化後則不能被改動。

 

 

九、servlet裏面有哪些方法?說一說get和post方法的差異

 

  init() 、destroy() 、service()等。

 

 

  在servlet開發中,以doGet()和doPost()分別處理get和post方法。另外另外一個doService(),  它是一個調度方法,當一個請求發生時,首先運行doService(),不管是get仍是post。在HttpServlet這個基類中實現了一個角度。首先推斷是請求時get仍是post,假設是get就調用doGet(),  假設是post就調用doPost()。

你也可以直接過載doService()方法,這樣你可以不管是get仍是post。都會運行這種方法。

   

 

   get和post方法的差異:

 

 

十、線性表的的順序存儲與鏈式存儲的特色

 

a)順序存儲結構:

長處:

 

隨機讀取(時間複雜度爲O(1))

無需爲表示表中元素之間的邏輯關係而添加額外的存儲空間

缺點:

 

插入、刪除操做需要移動大量元素。效率低(時間複雜度爲O(n))。

表的長度難以肯定

b)鏈式存儲結構. 

長處:

 

插入、刪除不需要移動數據。效率高(時間複雜度爲O(1));

缺點:

 

存取時需要遍歷,效率低(時間複雜度爲O(n));

順序存儲結構通常用於:頻繁查找,很是少插入、刪除;而鏈式存儲結構通常用於頻繁插入、刪除;

 

 

 

十一、具體解釋synchronized與Lock的差異與使用

引言:

昨天在學習別人分享的面試經驗時,看到Lock的使用。

想起本身在上次面試也遇到了synchronized與Lock的差異與使用。

因而。我整理了二者的差異和使用狀況,同一時候,對synchronized的使用過程一些常見問題的總結。最後是參照源代碼和說明文檔。對Lock的使用寫了幾個簡單的Demo。

請你們批評指正。

 

技術點:

 

(1)線程與進程:

 

在開始以前先把進程與線程進行區分一下。一個程序最少需要一個進程,而一個進程最少需要一個線程。

關係是線程–>進程–>程序的大體組成結構。因此線程是程序運行流的最小單位。而進程是系統進行資源分配和調度的一個獨立單位。如下咱們所有討論的都是創建在線程基礎之上。

 

(2)Thread的幾個重要方法:

 

咱們先了解一下Thread的幾個重要方法。

a、start()方法,調用該方法開始運行該線程;b、stop()方法,調用該方法強制結束該線程運行。c、join方法,調用該方法等待該線程結束。d、sleep()方法,調用該方法該線程進入等待。

e、run()方法,調用該方法直接運行線程的run()方法,但是線程調用start()方法時也會運行run()方法。差異就是一個是由線程調度運行run()方法,一個是直接調用了線程中的run()方法!

 

看到這裏,可能有些人就會問啦。那wait()和notify()呢?要注意。事實上wait()與notify()方法是Object的方法。不是Thread的方法。!同一時候,wait()與notify()會配合使用,分別表示線程掛起和線程恢復。

 

這裏另外一個非常常見的問題,順帶提一下:wait()與sleep()的差異。簡單來講wait()會釋放對象鎖而sleep()不會釋放對象鎖。

這些問題有很是多的資料,再也不贅述。

 

(3)線程狀態:

 

線程總共同擁有5大狀態,經過上面第二個知識點的介紹。理解起來就簡單了。

 

新建狀態:新建線程對象。並無調用start()方法以前

 

就緒狀態:調用start()方法以後線程就進入就緒狀態。但是並不是說僅僅要調用start()方法線程就當即變爲當前線程,在變爲當前線程以前都是爲就緒狀態。

值得一提的是,線程在睡眠和掛起中恢復的時候也會進入就緒狀態哦。

 

運行狀態:線程被設置爲當前線程,開始運行run()方法。就是線程進入運行狀態

 

堵塞狀態:線程被暫停,比方說調用sleep()方法後線程就進入堵塞狀態

 

死亡狀態:線程運行結束

 

(4)鎖類型

 

可重入鎖:在運行對象中所有同步方法不用再次得到鎖

 

可中斷鎖:在等待獲取鎖過程當中可中斷

 

公平鎖: 按等待獲取鎖的線程的等待時間進行獲取,等待時間長的具備優先獲取鎖權利

 

讀寫鎖:對資源讀取和寫入的時候拆分爲2部分處理。讀的時候可以多線程一塊兒讀,寫的時候必須同步地寫

 

synchronized與Lock的差異

(1))我把二者的差異分類到了一個表中,方便你們對照:

也許,看到這裏還對LOCK所知甚少,那麼接下來,咱們進入LOCK的深刻學習。

 

Lock具體介紹與Demo

如下是Lock接口的源代碼。筆者修剪以後的結果:

 

public interface Lock {

 

    /**

     * Acquires the lock.

     */

    void lock();

 

    /**

     * Acquires the lock unless the current thread is

     * {@linkplain Thread#interrupt interrupted}.

     */

    void lockInterruptibly() throws InterruptedException;

 

    /**

     * Acquires the lock only if it is free at the time of invocation.

     */

    boolean tryLock();

 

    /**

     * Acquires the lock if it is free within the given waiting time and the

     * current thread has not been {@linkplain Thread#interrupt interrupted}.

     */

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

 

    /**

     * Releases the lock.

     */

    void unlock();

 

}

 

從Lock接口中咱們可以看到主要有個方法。這些方法的功能從凝視中可以看出:

 

lock():獲取鎖,假設鎖被暫用則一直等待

 

unlock():釋放鎖

 

tryLock(): 注意返回類型是boolean。假設獲取鎖的時候鎖被佔用就返回false,不然返回true

 

tryLock(long time, TimeUnit unit):比起tryLock()就是給了一個時間期限,保證等待參數時間

 

lockInterruptibly():用該鎖的得到方式,假設線程在獲取鎖的階段進入了等待,那麼可以中斷此線程,先去作別的事

 

經過 以上的解釋,大體可以解釋在上個部分中「鎖類型(lockInterruptibly())」,「鎖狀態(tryLock())」等問題。還有就是前面子所獲取的過程我所寫的「大體就是可以嘗試得到鎖,線程可以不會一直等待」用了「可以」的緣由。

 

如下是Lock通常使用的樣例,注意ReentrantLock是Lock接口的實現。

 

lock():

 

 

package com.brickworkers;

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class LockTest {

    private Lock lock = new ReentrantLock();

 

    //需要參與同步的方法

    private void method(Thread thread){

        lock.lock();

        try {

            System.out.println("線程名"+thread.getName() + "得到了鎖");

        }catch(Exception e){

            e.printStackTrace();

        } finally {

            System.out.println("線程名"+thread.getName() + "釋放了鎖");

            lock.unlock();

        }

    }

 

    public static void main(String[] args) {

        LockTest lockTest = new LockTest();

 

        //線程1

        Thread t1 = new Thread(new Runnable() {

 

            @Override

            public void run() {

                lockTest.method(Thread.currentThread());

            }

        }, "t1");

 

        Thread t2 = new Thread(new Runnable() {

 

            @Override

            public void run() {

                lockTest.method(Thread.currentThread());

            }

        }, "t2");

 

        。t1.start();

        。t2.start();

    }

}

//運行狀況:線程名t1得到了鎖

//         線程名t1釋放了鎖

//         線程名t2得到了鎖

//         線程名t2釋放了鎖

 

tryLock():

 

package com.brickworkers;

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class LockTest {

    private Lock lock = new ReentrantLock();

 

    //需要參與同步的方法

    private void method(Thread thread){

/*      lock.lock();

        try {

            System.out.println("線程名"+thread.getName() + "得到了鎖");

        }catch(Exception e){

            e.printStackTrace();

        } finally {

            System.out.println("線程名"+thread.getName() + "釋放了鎖");

            lock.unlock();

        }*/

 

 

        if(lock.tryLock()){

            try {

                System.out.println("線程名"+thread.getName() + "得到了鎖");

            }catch(Exception e){

                e.printStackTrace();

            } finally {

                System.out.println("線程名"+thread.getName() + "釋放了鎖");

                lock.unlock();

            }

        }else{

            System.out.println("我是"+Thread.currentThread().getName()+"有人佔着鎖,我就不要啦");

        }

    }

 

    public static void main(String[] args) {

        LockTest lockTest = new LockTest();

 

        //線程1

        Thread t1 = new Thread(new Runnable() {

 

            @Override

            public void run() {

                lockTest.method(Thread.currentThread());

            }

        }, "t1");

 

        Thread t2 = new Thread(new Runnable() {

 

            @Override

            public void run() {

                lockTest.method(Thread.currentThread());

            }

        }, "t2");

 

        。t1.start();

        。t2.start();

    }

}

 

//運行結果: 線程名t2得到了鎖

//         我是t1有人佔着鎖,我就不要啦

//         線程名t2釋放了鎖

 

看到這裏相信你們也都會使用怎樣使用Lock了吧。關於tryLock(long time, TimeUnit unit)和lockInterruptibly()再也不贅述。前者主要存在一個等待時間,在測試代碼中寫入一個等待時間。後者主要是等待中斷,會拋出一箇中斷異常,常用度不高,喜歡探究可以本身深刻研究。

 

前面比較重提到「公平鎖」。在這裏可以提一下ReentrantLock對於平衡鎖的定義。在源代碼中有這麼兩段:

 

 

 /**

     * Sync object for non-fair locks

     */

    static final class NonfairSync extends Sync {

        private static final long serialVersionUID = 7316153563782823691L;

 

        /**

         * Performs lock.  Try immediate barge, backing up to normal

         * acquire on failure.

         */

        final void lock() {

            if (compareAndSetState(0, 1))

                setExclusiveOwnerThread(Thread.currentThread());

            else

                acquire(1);

        }

 

        protected final boolean tryAcquire(int acquires) {

            return nonfairTryAcquire(acquires);

        }

    }

 

    /**

     * Sync object for fair locks

     */

    static final class FairSync extends Sync {

        private static final long serialVersionUID = -3000897897090466540L;

 

        final void lock() {

            acquire(1);

        }

 

        /**

         * Fair version of tryAcquire.  Don't grant access unless

         * recursive call or no waiters or is first.

         */

        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;

        }

    }

 

從以上源代碼可以看出在Lock中可以本身控制鎖是否公平,而且,默認的是非公平鎖,如下是ReentrantLock的構造函數:

 

   public ReentrantLock() {

        sync = new NonfairSync();//默認非公平鎖

    }

 

尾記錄:

筆者水平通常,只是此博客在引言中的目的已所有達到。這僅僅是筆者在學習過程當中的總結與歸納,如存在不對的,歡迎你們批評指出。

 

延伸學習:對於LOCK底層的實現。你們可以參考:

點擊Lock底層介紹博客

 

兩種同步方式性能測試,你們可以參考:

點擊查看兩種同步方式性能測試博客

 

博主18年3月新增:

回來看本身博客。

發現東西闡述的不夠完整。這裏在作補充,因爲這篇博客訪問較大,因此爲了避免誤導你們,儘可能介紹給你們正確的表述:

(1)兩種鎖的底層實現方式:

synchronized:咱們知道java是用字節碼指令來控制程序(這裏不包括熱點代碼編譯成機器碼)。在字節指令中,存在有synchronized所包括的代碼塊。那麼會造成2段流程的運行。

咱們點擊查看SyncDemo.java的源代碼SyncDemo.class,可以看到例如如下:

如上就是這段代碼段字節碼指令。沒你想的那麼難吧。言歸正傳。咱們可以清晰段看到。事實上synchronized映射成字節碼指令就是添加來兩個指令:monitorenter和monitorexit。

當一條線程進行運行的遇到monitorenter指令的時候,它會去嘗試得到鎖。假設得到鎖那麼鎖計數+1(爲何會加一呢,因爲它是一個可重入鎖,因此需要用這個鎖計數推斷鎖的狀況),假設沒有得到鎖,那麼堵塞。當它遇到monitorexit的時候,鎖計數器-1,當計數器爲0。那麼就釋放鎖。

 

那麼有的朋友看到這裏就疑惑了,那圖上有2個monitorexit呀?當即回答這個問題:上面我曾經寫的文章也有表述過,synchronized鎖釋放有兩種機制,一種就是運行完釋放;第二種就是發送異常。虛擬機釋放。圖中第二個monitorexit就是發生異常時運行的流程,這就是我開頭說的「會有2個流程存在「。

而且,從圖中咱們也可以看到在第13行。有一個goto指令,也就是說假設正常運行結束會跳轉到19行運行。

 

這下,你對synchronized是否是瞭解的很是清晰了呢。接下來咱們再聊一聊Lock。

 

Lock:Lock實現和synchronized不同。後者是一種悲觀鎖,它膽子很是小,它很是怕有人和它搶吃的,因此它每次吃東西前都把本身關起來。

而Lock呢底層事實上是CAS樂觀鎖的體現,它無所謂。別人搶了它吃的,它又一次去拿吃的就好啦,因此它很是樂觀。具體底層怎麼實現,博主不在細述。有機會的話。我會對concurrent包如下的機制好好和你們說說,假設面試問起。你就說底層主要靠volatile和CAS操做實現的。

 

現在,纔是我真正想在這篇博文後面加的,我要說的是:儘量去使用synchronized而不要去使用LOCK

 

什麼概念呢?我和你們打個比方:你叫jdk,你生了一個孩子叫synchronized,後來呢,你領養了一個孩子叫LOCK。起初。LOCK剛來到新家的時候,它很是乖,很是懂事。各個方面都表現的比synchronized好。

你很是開心,但是你心裏深處又有一點淡淡的憂傷。你不但願你本身親生的孩子竟然還不如一個領養的孩子乖巧。這個時候,你對親生的孩子教育更加深入了。你想證實。你的親生孩子synchronized並不會比領養的孩子LOCK差。(博主僅僅是打個比方)

 

那怎樣教育呢?

在jdk1.6~jdk1.7的時候,也就是synchronized1六、7歲的時候。你做爲爸爸,你給他優化了,具體優化在哪裏呢:

 

(1)線程自旋和適應性自旋

咱們知道。java’線程事實上是映射在內核之上的。線程的掛起和恢復會極大的影響開銷。

而且jdk官方人員發現,很是多線程在等待鎖的時候。在很是短的一段時間就得到了鎖,因此它們在線程等待的時候,並不需要把線程掛起,而是讓他無目的的循環。通常設置10次。這樣就避免了線程切換的開銷,極大的提高了性能。

而適應性自旋,是賦予了自旋一種學習能力。它並不固定自旋10次一下。

他可以根據它前面線程的自旋狀況,從而調整它的自旋,甚至是不通過自旋而直接掛起。

 

(2)鎖消除

什麼叫鎖消除呢?就是把沒必要要的同步在編譯階段進行移除。

那麼有的小夥伴又迷糊了,我本身寫的代碼我會不知道這裏要不要加鎖?我加了鎖就是表示這邊會有同步呀?

並不是這樣。這裏所說的鎖消除並不必定指代是你寫的代碼的鎖消除,我打一個比方:

在jdk1.5曾經。咱們的String字符串拼接操做事實上底層是StringBuffer來實現的(這個你們可以用我前面介紹的方法。寫一個簡單的demo,而後查看class文件裏的字節碼指令就清晰了),而在jdk1.5以後,那麼是用StringBuilder來拼接的。

咱們考慮前面的狀況,比方例如如下代碼:

 

String str1="qwe";

String str2="asd";

String str3=str1+str2;

 

底層實現會變成這樣:

 

StringBuffer sb = new StringBuffer();

sb.append("qwe");

sb.append("asd");

 

咱們知道。StringBuffer是一個線程安全的類。也就是說兩個append方法都會同步。經過指針逃逸分析(就是變量不會外泄),咱們發現在這段代碼並不存在線程安全問題。這個時候就會把這個同步鎖消除。

 

(3)鎖粗化

在用synchronized的時候,咱們都講究爲了不大開銷,儘可能同步代碼塊要小。那麼爲何還要加粗呢?

咱們繼續以上面的字符串拼接爲例,咱們知道在這一段代碼中,每一個append都需要同步一次。那麼我可以把鎖粗化到第一個append和最後一個append(這裏不要去糾結前面的鎖消除。我僅僅是打個比方)

 

(4)輕量級鎖

 

(5)偏向鎖

 

關於最後這兩種,我但願留個有緣的讀者本身去查找,我不但願我把一件事情描寫敘述的那麼具體,本身動手獲得纔是你本身的,博主可以告訴你的是。最後兩種並不難。

。加油吧,各位。

 

 

 

 

 

十二、悲觀鎖,樂觀鎖。行鎖,表鎖,頁鎖,共享鎖。排他鎖

悲觀鎖:

 

  顧名思義,很是悲觀,就是每次拿數據的時候都以爲別的線程會改動數據,因此在每次拿的時候都會給數據上鎖。上鎖以後,當別的線程想要拿數據時。就會堵塞。直到給數據上鎖的線程將事務提交或者回滾。傳統的關係型數據庫裏就用到了很是多這樣的鎖機制,比方行鎖。表鎖。共享鎖,排他鎖等,都是在作操做以前先上鎖。

 

行鎖:

 

  如下演示行鎖,打開兩個mysql命令行界面。兩個線程分別運行例如如下操做:(左邊先運行)

 

  左邊的線程,在事務中經過select for update語句給sid = 1的數據行上了鎖。右邊的線程此時可以使用select語句讀取數據,但是假設也使用select for update語句,就會堵塞,使用update。add,delete也會堵塞。

  當左邊的線程將事務提交(或者回滾),右邊的線程就會獲取鎖,線程再也不堵塞:

 

  此時,右邊的線程獲取鎖。左邊的線程假設運行類似操做,也會被堵塞:

 

表鎖:

 

  上述樣例中,假設使用例如如下語句就是使用的表鎖:

 

select * from student for update;

頁鎖:

 

  行鎖鎖指定行。表鎖鎖整張表。頁鎖是折中實現。即一次鎖定相鄰的一組記錄。

 

共享鎖:

 

  共享鎖又稱爲讀鎖,一個線程給數據加上共享鎖後,其它線程僅僅能讀數據,不能改動。

 

排他鎖:

 

  排他鎖又稱爲寫鎖。和共享鎖的差異在於。其它線程既不能讀也不能改動。

 

樂觀鎖:

 

  樂觀鎖事實上不會上鎖。顧名思義。很是樂觀,它默認別的線程不會改動數據,因此不會上鎖。

僅僅是在更新前去推斷別的線程在此期間有沒有改動數據。假設改動了。會交給業務層去處理。

  常用的實現方式是使用版本號戳,好比在一張表中加入一個整型字段version,每更新version++,比方某個時刻version=1,線程A讀取了此version=1,線程B也讀取了此version=1,當線程A更新數據以前,推斷version仍然爲1。更新成功。version++變爲2。但是當線程B再提交更新時,發現version變爲2了,與以前讀的version=1不一致,就知道有別的線程更新了數據,這個時候就會進行業務邏輯的處理。

 

一般狀況下,寫操做較少時,使用樂觀鎖,寫操做較多時,使用悲觀鎖。

相關文章
相關標籤/搜索