帶你理解Lock鎖原理

呼嚕葫蘆發表於2020-07-23

同樣是鎖,先說說synchronized和lock的區別:

  1. synchronized是java關鍵字,是用c++實現的;而lock是用java類,用java可以實現
  2. synchronized可以鎖住程式碼塊,物件和類,但是執行緒從開始獲取鎖之後開發者不能進行控制和了解;lock則用起來非常靈活,提供了許多api可以讓開發者去控制加鎖和釋放鎖等等。

寫個Demo

static Lock lock = new ReentrantLock();
public
static void main(String[] args) throws InterruptedException { lock.lock();//其他沒拿到鎖的卡住不動 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("start to get lock Interruptibly"); lock.unlock(); //看看會發生什麼,註釋掉再看看 lock.lock(); System.out.println("拿到鎖"); lock.unlock(); System.out.println("釋放鎖"); } }); thread.start(); Thread.sleep(3000); lock.unlock(); }

我們自己來手寫一下lock介面的tryLock()、lock()和unLock()方法,實現我們自己的myLock。

public class MyLock implements Lock {
    //多併發呼叫  0-未佔用 大於0-佔用
    AtomicInteger state = new AtomicInteger();

    Thread ownerThread = new Thread();

    //等待鎖的佇列
    LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue();

    @Override
    public void lock() {
        if (!tryLock()) {  //先搶鎖,所以是非公平鎖
            //沒拿到鎖,放到佇列中去進行排隊
            waiters.add(Thread.currentThread());
            //等待被喚醒
            for (; ; ) {
                if (tryLock()) {  //非公平鎖情況下,喚醒過來繼續獲取鎖
                    waiters.poll(); //獲取鎖成功把自己從佇列中取出來
                    return;
                } else    //獲取鎖失敗
                    LockSupport.park();  //執行緒阻塞
            }
        }
    }


    @Override
    public boolean tryLock() {
        if (state.get() == 0) { //如果鎖沒被佔用
            if (state.compareAndSet(0, 1)) {  //如果成功拿到鎖
                ownerThread = Thread.currentThread();   //佔用鎖執行緒改為當前執行緒
                return true;
            }
        }
        return false;
    }

    @Override
    public void unlock() {

        if (ownerThread != Thread.currentThread())  //佔用鎖執行緒不是當前執行緒無法釋放鎖
            throw new RuntimeException("非法呼叫,當前鎖不屬於你");

        if (state.decrementAndGet() == 0)  //如果成功釋放鎖
            ownerThread = null;  //佔用鎖執行緒置空
        //通知其他執行緒
//        Thread thread = null;
//
//        while ((thread = waiters.peek()) != null)
//            LockSupport.unpark(thread);
        Thread thread = waiters.peek(); //獲取佇列頭部執行緒,執行緒還留在佇列中
        if (thread != null) {
            LockSupport.unpark(thread); //取消阻塞
        }
    }



    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }


    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }
}

幾個注意點:

  • 鎖的佔用狀態state是AtomicInteger型別,底層原理是CAS,這是為了保證在多併發情況下執行緒安全問題;
  • 當執行緒1釋放鎖成功時,獲取佇列頭部執行緒但並不取出,因為非公平鎖模式下,佇列頭部執行緒不一定能獲取到鎖;
  • LockSupport的park()和unPark()方法是native方法,可以阻塞,喚醒執行緒;

Lock預設是非公平鎖,上面實現的也是非公平鎖,小夥伴們可以試一試。

公平鎖和非公平鎖區別:

先等待先獲取鎖是公平鎖;先等待也不一定先獲取鎖,可能被突然到來的執行緒獲取到是非公平鎖;

公平鎖的實現:

  @Override
    public void lock() {
       checkQueue();//執行緒來的時候先不獲取鎖,而是先檢查佇列中有沒有等待的執行緒,如果有,直接放入佇列,如果沒有,再去獲取鎖
        if (!tryLock()) {  //先搶鎖,所以是非公平鎖
            //沒拿到鎖,放到佇列中去進行排隊
            waiters.add(Thread.currentThread());
            //等待被喚醒
            for (; ; ) {
                if (tryLock()) {  //非公平鎖情況下,喚醒過來繼續獲取鎖
                    waiters.poll(); //獲取鎖成功把自己從佇列中取出來
                    return;
                } else    //獲取鎖失敗
                    LockSupport.park();  //執行緒阻塞
            }
        }
    }

看完的小夥伴可以去看JDK提供的Lock原始碼啦。。

 

相關文章