1. 程式人生 > >【JAVA面試】java面試題整理(4)

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

                                           java面試題整理(4)

JAVA常考點4

目錄

  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))) {

                 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方法。

2、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不能夠互用,因為他們兩種不同的資料型別;

3、介面可以繼承介面嗎?抽象類可以繼承介面嗎?

  介面不僅可以繼承介面,而且一個介面可以繼承多個介面,介面是特殊的抽象類。

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

4、資料庫索引的作用

優點:建立索引主要可以大大提高系統性能,主要優勢有以下5點:

通過建立唯一性索引,可以保證資料庫表中每一行資料的唯一性。

可以大大加快 資料的檢索速度,這也是建立索引的最主要的原因。

可以加速表和表之間的連線,特別是在實現資料的參考完整性方面特別有意義。

在使用分組和排序 子句進行資料檢索時,同樣可以顯著減少查詢中分組和排序的時間。

通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的效能。

缺點:

建立索引和維護索引要耗費時間,這種時間隨著資料 量的增加而增加。 

索引需要佔物理空間,除了資料表佔資料空間之外,每一個索引還要佔一定的物理空間,如果要建立聚簇索引,那麼需要的空間就會更大。 

當對錶中的資料進行增加、刪除和修改的時候,索引也要動態的維護,這樣就降低了資料的維護速度。

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

5、如何修改資料庫中的欄位型別

  ALTER TABLE 表名  MODIFY COLUMN 欄位名 欄位型別定義;(注意不是update)

6、having的作用

  HAVING 就像WHERE條件一樣,按指定要求來取資料集。只不過WHERE一般資料查詢來指定條件,HAVING是用在GROUP BY 分組來指定條件。HAVING 子句運做起來非常象 WHERE 子句, 只用於對那些滿足 HAVING 子句裡面給出的條件的組進行計算。 其實,WHERE 在分組和聚集之前過濾掉我們不需要的輸入行, 而 HAVING 在 GROUP 之後那些不需要的組. 因此,WHERE 無法使用一個聚集函式的結果. 而另一方面,我們也沒有理由寫一個不涉及聚集函式的 HAVING. 如果你的條件不包含聚集,那麼你也可以把它寫在 WHERE 裡面, 這樣就可以避免對那些你準備拋棄的行進行的聚集運算.

7、快速排序與歸併排序的區別

  歸併排序:簡單來說就是先將陣列不斷細分成最小的單位,然後每個單位分別排序,排序完畢後合併,重複以上過程最後就可以得到排序結果。複雜度:O(nlogn)   穩當

  快速排序:簡單來說就是先選定一個基準元素,然後以該基準元素劃分陣列,再在被劃分的部分重複以上過程,最後可以得到排序結果。           複雜度:O(nlogn)   不穩定

兩者都是用分治法的思想,不過最後歸併排序的合併操作比快速排序的要繁瑣。

8、final關鍵詞作用有哪些?

  final修飾類,類不能被繼承

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

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

9、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方法的區別:

10、線性表的的順序儲存與鏈式儲存的特點

a)順序儲存結構:

優點:

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

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

缺點:

插入、刪除操作需要移動大量元素,效率低(時間複雜度為O(n));

表的長度難以確定

b)鏈式儲存結構. 

優點:

插入、刪除不需要移動資料,效率高(時間複雜度為O(1));

缺點:

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

順序儲存結構一般用於:頻繁查詢,很少插入、刪除;而鏈式儲存結構一般用於頻繁插入、刪除;

11、詳解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的時候,也就是synchronized16、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)偏向鎖

關於最後這兩種,我希望留個有緣的讀者自己去查詢,我不希望我把一件事情描述的那麼詳細,自己動手得到才是你自己的,博主可以告訴你的是,最後兩種並不難。。加油吧,各位。

12、悲觀鎖,樂觀鎖,行鎖,表鎖,頁鎖,共享鎖,排他鎖

悲觀鎖:

  顧名思義,很悲觀,就是每次拿資料的時候都認為別的執行緒會修改資料,所以在每次拿的時候都會給資料上鎖。上鎖之後,當別的執行緒想要拿資料時,就會阻塞,直到給資料上鎖的執行緒將事務提交或者回滾。傳統的關係型資料庫裡就用到了很多這種鎖機制,比如行鎖,表鎖,共享鎖,排他鎖等,都是在做操作之前先上鎖。

行鎖:

  下面演示行鎖,開啟兩個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不一致,就知道有別的執行緒更新了資料,這個時候就會進行業務邏輯的處理。

通常情況下,寫操作較少時,使用樂觀鎖,寫操作較多時,使用悲觀鎖。