互聯網面試題之Java鎖

公平鎖、非公平鎖

公平鎖:是指多個線程按照申請鎖的順序來獲取鎖,類似排隊,先來後到。

非公平鎖:是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。在高併發的情況下,有可能會造成優先級反轉或者飢餓現象。

在Java併發包中ReentrantLock的創建可以指定構造函數的boolean類型來得到公平鎖或非公平鎖,默認是非公平鎖。

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。
synchronized也是一種非公平鎖。

區別:
公平鎖:公平鎖就是很公平,在併發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果爲空,獲取當前線程是等待隊列的第一個,就佔有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中渠道自己。
非公平鎖:非公平鎖比較粗魯,上來就直接嘗試佔有鎖,如果嘗試失敗,就再採用類似公平鎖那種方式。

尚硅谷大數據全套視頻教程40階段(2019.6月線下班)

可重入鎖(遞歸鎖)

可重入鎖(也叫做遞歸鎖),指的是同一線程外層函數獲得鎖之後,內層遞歸函數仍然能獲取該鎖的代碼。同一個線程在外層方法獲取到鎖的時候,在進入內層方法會自動獲取鎖。
也就是說,線程可以進入任何一個它已經擁有的鎖所同步的代碼塊。

ReentrantLock、synchronized 就是一個典型的可重入鎖,可重入鎖最大作用是避免死鎖


import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Foo {

    private Lock lock = new ReentrantLock();

    public void print1() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", print1()");

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            this.print2();

        }finally {
            lock.unlock();
        }
    }

    private void print2() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + ", print2()");
        }finally {
            lock.unlock();
        }
    }

}


public class ReentrantLockDemo {

    public static void main(String[] args) throws InterruptedException {

        Foo foo = new Foo();

        Thread t1 = new Thread(()-> {
            foo.print1();
        }, "thread-1");

        Thread t2 = new Thread(()-> {
            foo.print1();
        }, "thread-2");

        t1.start();
        t2.start();

        // main方法等待t1 t2線程執行結束
        t1.join();
        t2.join();
    }
}

自旋鎖

自旋鎖(spinlock)是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。

Unsafe類中的getAndAddInt方法

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

手寫自旋鎖

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

class MySpinLock {

    private AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + " 正在獲取鎖");
        while (!atomicReference.compareAndSet(null, thread)) {
        }
        System.out.println(thread.getName() + " 獲取到鎖");
    }

    public void unLock() {
        Thread thread = Thread.currentThread();
        boolean result = atomicReference.compareAndSet(thread, null);
        System.out.println(thread.getName() + " 釋放鎖 result = " + result);
    }

}

public class SpinLockDemo {

    public static void main(String[] args) throws InterruptedException {
        MySpinLock lock = new MySpinLock();

        Thread t1 = new Thread(()-> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " do something");

            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.unLock();

        }, "t1");
        t1.start();

        TimeUnit.SECONDS.sleep(1);

        Thread t2 = new Thread(()-> {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " do something");

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            lock.unLock();

        }, "t2");
        t2.start();

        t1.join();
        t2.join();
    }

}

獨佔鎖、共享鎖

獨佔鎖:指該鎖一次只能被一個線程持有,ReentrantLock和synchronized 都是獨佔鎖。
共享鎖:指該鎖可被多個線程所持有。
ReentrantReadWriteLock 其讀鎖是共享鎖,寫鎖是獨佔鎖。
讀鎖的共享鎖可保證併發讀是非常高效的,讀寫、寫讀、寫寫的過程是互斥的。

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